Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/motion_pvt_sequence_generation/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name = "pypi"
matplotlib = "*"
numpy = "*"
scipy = "*"
zaber-motion = "*"

[dev-packages]
black = "*"
Expand Down
33 changes: 32 additions & 1 deletion examples/motion_pvt_sequence_generation/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 90 additions & 46 deletions examples/motion_pvt_sequence_generation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

*By Jeff Homer, Firmware Team*

This repository contains code to complement our article [Motion Planning with Position-Velocity-Time](https://www.zaber.com/articles/motion-planning-with-position-velocity-time). The cubic polynomial PVT algorithm
described in the article is implemented in Python, along with helper functions to automatically
generate missing parameters, plot the generated path and trajectory, and save the results
to a CSV file that is compatible with [Zaber Launcher](https://zaber.com/zaber-launcher#download)'s PVT Viewer App.
This repository contains code to complement our article [Motion Planning with Position-Velocity-Time](https://www.zaber.com/articles/motion-planning-with-position-velocity-time). The cubic polynomial PVT algorithm described in the article is implemented in Python, along with helper functions to automatically generate missing parameters, plot the generated path and trajectory, and save the results to a CSV file that is compatible with [Zaber Launcher](https://zaber.com/zaber-launcher#download)'s PVT Viewer App.

## Hardware Requirements

Expand Down Expand Up @@ -69,7 +66,7 @@ The repository also contains some helper files that may be useful for extending

## The PVT File

The [pvt.py](pvt.py) file contains several helper classes and functions for creating and manipulating PVT sequences.
The [pvt.py](pvt.py) file contains several helper classes and functions for sampling and plotting PVT sequences.

Import all the helper classes and functions in this file under the `pvt` namespace by adding the following import to your file:

Expand Down Expand Up @@ -174,58 +171,99 @@ sequence.append_point(point4)
sequence.append_point(point5)
```

##### 2. Generate from a CSV file
##### 2. Generate from PvtSequenceData loaded from a CSV file

Initialize an instance of this class by providing a CSV file with position data, position-time data, or position-velocity-time data, by using the static class method `pvt.Sequence.from_csv()`.
Zaber Motion Library (ZML) provides a utility function for loading a CSV file with position data, position-time data, or position-velocity-time data, by using the static class method `PvtSequence.load_sequence_data()`.

For example, generate a PVT sequence from the sample position-only data file [spiral_2d.csv](sample_data/position_data/spiral_2d.csv):

```python
sequence = pvt.Sequence.from_csv("sample_data/position_data/spiral_2d.csv")
from zaber_motion.ascii import PvtSequence
import pvt

csv_data = PvtSequence.load_sequence_data("sample_data/position_data/spiral_2d.csv")
sequence = pvt.Sequence.from_sequence_data(csv_data.sequence_data)
```

##### 3. Generate from position data
##### 3. Generate using ZML's PVT Sequence Generation API

Note that because the `Sequence` class doesn't attempt to do any scaling based on units, all units used for position, velocity and acceleration must be of the same scale (i.e, [cm, cm/s, cm/s²], [°, °/s, °/s²]) and time must be specified in seconds.

For more information on ZML's PVT sequence generation API, please see our [how-to guide](https://software.zaber.com/motion-library/docs/guides/device_features/pvt_sequence_generation) and [API documentation](https://software.zaber.com/motion-library/api/py/ascii/pvtsequence#generatepositions).

Initialize an instance of this class by providing position data, by using the static class method `pvt.Sequence.generate_times_and_velocities()`.
###### a. From position data

Initialize an instance of this class from a `PvtSequenceData` object generated from position data, by using the static class method `PvtSequence.generate_velocities_and_times()`.

This method works by creating a [geometric path](#the-geometricpath-class) from the provided position data, which parameterizes the curve as a function of distance. The missing velocity and time parameters are then generated by traversing this geometric path using a trapezoidal speed profile.

For example, generate a 2-D PVT sequence from some x and y position vectors:

```python
x_positions = [1, 2, 3]
y_positions = [4, 5, 6]
sequence = pvt.Sequence.generate_times_and_velocities([x_positions, y_positions])
from zaber_motion import Units, Measurement, MeasurementSequence
from zaber_motion.ascii import PvtSequence
import pvt

target_speed = Measurement(1, Units.VELOCITY_CENTIMETRES_PER_SECOND)
target_accel = Measurement(0.5, Units.ACCELERATION_CENTIMETRES_PER_SECOND_SQUARED)
position_sequences = [
MeasurementSequence([1, 2, 3], Units.LENGTH_CENTIMETRES),
MeasurementSequence([4, 5, 6], Units.LENGTH_CENTIMETRES)
]
sequence_data = PvtSequence.generate_velocities_and_times(position_sequences, target_speed, target_accel)
sequence = pvt.Sequence.from_sequence_data(sequence_data)
```

##### 4. Generate from position-time or position-velocity-time data
###### b. From position-time or partial position-velocity-time data

Initialize an instance of this class by providing position-time or position-velocity-time data, by using the static class method `pvt.Sequence.generate_velocities()`.
Initialize an instance of this class from a `PvtSequenceData` object generated from position-time data, by using the static class method `PvtSequence.generate_velocities()`.

This method works by generating velocities such that acceleration is continuous at segment transitions. The velocity at the first and last point in the sequence will be set to zero if unspecified.

For example, generate a 1-D PVT sequence from position and time data:

```python
time = [0, 1, 2]
position = [4, 5, 6]
sequence = pvt.Sequence.generate_velocities(time, position)
from zaber_motion import Units, Measurement, MeasurementSequence
from zaber_motion.ascii import PvtSequence
import pvt

times = MeasurementSequence([0, 1, 2], Units.TIME_SECONDS)
positions = [MeasurementSequence([4, 5, 6], Units.LENGTH_CENTIMETRES)]
sequence_data = PvtSequence.generate_velocities(positions, times, times_relative=False)
sequence = pvt.Sequence.from_sequence_data(sequence_data)
```

Or with partially specified velocities:
```python
from zaber_motion import Units, Measurement, MeasurementSequence, OptionalMeasurementSequence
from zaber_motion.ascii import PvtSequence
import pvt

times = MeasurementSequence([0, 1, 2], Units.TIME_SECONDS)
positions = [MeasurementSequence([4, 5, 6, 4, 5], Units.LENGTH_CENTIMETRES)]
velocities = [OptionalMeasurementSequence([0, 1, None, None, 0], Units.VELOCITY_CENTIMETRES_PER_SECOND)]
sequence_data = PvtSequence.generate_velocities(positions, times, velocities, times_relative=False)
sequence = pvt.Sequence.from_sequence_data(sequence_data)
```

##### 5. Generate from velocity-time data
###### c. Generate from velocity-time data

Initialize an instance of this class by providing velocity-time data, by using the static class method `pvt.Sequence.generate_positions()`.
Initialize an instance of this class from a `PvtSequenceData` object generated from velocity-time data, by using the static class method `PvtSequence.generate_positions()`.

This method works by generating velocities such that acceleration is
continuous at segment transitions. The velocity at the first and last
point in the sequence will be set to zero if unspecified.
continuous at segment transitions.

For example, generate a 1-D PVT sequence from position and time data:

```python
time = [0, 1, 2]
velocities = [4, 5, 6]
sequence = pvt.Sequence.generate_positions(time, velocities)
from zaber_motion import Units, Measurement, MeasurementSequence
from zaber_motion.ascii import PvtSequence
import pvt

times = MeasurementSequence([0, 1, 2], Units.TIME_SECONDS)
velocities = [MeasurementSequence([4, 5, 6], Units.VELOCITY_CENTIMETRES_PER_SECOND)]
sequence_data = PvtSequence.generate_positions(velocities, times)
sequence = pvt.Sequence.from_sequence_data(sequence_data)
```

#### Class Properties
Expand All @@ -247,6 +285,32 @@ The class has the following methods:
- `acceleration(time)` - Return the position at any time in the sequence.
- `save_to_file(filename)` - Save the sequence to a CSV file.

#### Class Methods

- `position(u)` - The position at the given parameterization length.
- `direction(u)` - The unit vector describing the direction or tangent of the path at the given parameterization length.
- `segment_length(u0, uf)` - The arc length between some starting and some final parameterization length.

## The Visualization File

The [visualization.py](visualization.py) file contains several functions for plotting PVT sequence trajectories and paths:

- `plot_pvt_trajectory(sequence, ...)` - Plots the position, velocity, and acceleration profiles for a given PVT sequence.
- `plot_pvt_path(sequence, ...)` - Plots the 2-D or 3-D geometric path of a given PVT sequence and scaled arrows representing the velocity vector (or tangent) at each point.
- `plot_path_and_trajectory(sequence, ...)` - Combines the previous two functions and plots both the PVT trajectories and the geometric path for the given PVT sequence.

Import the functions in this file by adding the following import to your file:

```python
from visualization import plot_pvt_trajectory, plot_pvt_path, plot_path_and_trajectory
```

## The Sequence Generators File

The functions and utility classes in this file have now been ported directly to Zaber Motion Library. They remain here in case anyone is interested in reading and studying their implementation: we assume that even the most technically proficient user may find it easier to read and understand their implementation in python than in golang.

This file contains the original implementations of the functions, `PvtSequence.generate_velocities()`, `PvtSequence.generate_positions()` and `PvtSequence.generate_velocities_and_times()`, the `GeometricPath` helper class, and also a function for interpolating velocities using finite difference.

### The `GeometricPath` Class

A geometric path ties together a sequence of N-D position vectors, creating a continuous positional path between them. The generated path does not contain any velocity or time information, it creates a purely positional relationship between the points.
Expand All @@ -255,7 +319,7 @@ The following image shows a geometric path consisting of six points. Note that t

![A geometric path and its defining points](img/geometric-path.png "Geometric Path")

Under the hood, the geometric path is represented by an multi-dimensional B-spline. The generated curve is parameterized by a normalized parameterization length `u` that goes from 0, defining the first point, to 1, defining the last.
Under the hood, the geometric path is represented by an multi-dimensional B-spline (not-a-knot cubic splines). The generated curve is parameterized by a normalized parameterization length `u` that goes from 0, defining the first point, to 1, defining the last.

For more information about B-splines, see <https://en.wikipedia.org/wiki/B-spline>.

Expand All @@ -277,23 +341,3 @@ The class has the following read-only properties:

- `length` - The length of the path, from start to finish.
- `parameterized_lengths` - The normalized lengths corresponding to each of the points used to construct the path. These range from 0, for the first point, to 1, for the final point.

#### Class Methods

- `position(u)` - The position at the given parameterization length.
- `direction(u)` - The unit vector describing the direction or tangent of the path at the given parameterization length.
- `segment_length(u0, uf)` - The arc length between some starting and some final parameterization length.

## The Visualization File

The [visualization.py](visualization.py) file contains several functions for plotting PVT sequence trajectories and paths:

- `plot_pvt_trajectory(sequence, ...)` - Plots the position, velocity, and acceleration profiles for a given PVT sequence.
- `plot_pvt_path(sequence, ...)` - Plots the 2-D or 3-D geometric path of a given PVT sequence and scaled arrows representing the velocity vector (or tangent) at each point.
- `plot_path_and_trajectory(sequence, ...)` - Combines the previous two functions and plots both the PVT trajectories and the geometric path for the given PVT sequence.

Import the functions in this file by adding the following import to your file:

```python
from visualization import plot_pvt_trajectory, plot_pvt_path, plot_path_and_trajectory
```
2 changes: 1 addition & 1 deletion examples/motion_pvt_sequence_generation/article.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
authors:
- Jeff Homer
date: 2023-12-12
updated_date: 2023-12-12
updated_date: 2025-10-07
category: Motion
tags:
- Python
Expand Down
38 changes: 19 additions & 19 deletions examples/motion_pvt_sequence_generation/generate_pvt_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,22 @@
Generate the missing parameters for a given PVT sequence.

This script can be used to generate a fully-defined PVT sequence
from position data, position-time data, velocity-time data, or position-velocity-time
data (with some velocity values missing).
from position data, position-time data or velocity-time data.

Use the script settings to toggle visualization of the generated
sequence, and writing it to a CSV file compatible with Zaber
Launcher's PVT Viewer App.

Input data must be given in CSV form, with columns for time, position
in each dimension, and velocity in each dimension. Missing parameters
can only be generated in the following two cases:
- No velocity or time information is provided (i.e., there are
no such columns or all values in corresponding columns are
empty.) In this case, all velocity and time parameters will
be generated.
- No position information is provided (i.e., there are no such columns
or all values in the corresponding columns are empty.) In this case,
all position values will be generated.
- Some or all velocity information is missing (i.e., there are no
velocity columns at all, or there is a velocity column for
each position column, and some or all values are empty.) In this
case only the missing velocity parameters will be generated.
can only be generated in the following three cases:
- No velocity or time information is provided (i.e., there are no such
columns). In this case, all velocity and time parameters will be
generated.
- No position information is provided (i.e., there is no such column).
In this case, all position values will be generated.
- No velocity information is provided (i.e. there is no such column).
In this case, all velocity values will be generated.

1-D, 2-D, and 3-D sample data is also provided and can be found in the
subdirectory "sample_data".
Expand All @@ -32,16 +27,18 @@

import pvt
from visualization import plot_path_and_trajectory
from zaber_motion import Measurement, Units
from zaber_motion.ascii import PvtSequence

# ------------------- Script Settings ----------------------

DATA_DIRECTORY = "sample_data/position_velocity_time_data/"
"""The directory of the input file(s)"""
FILENAMES = ["wave_1d.csv", "spiral_2d.csv", "spiral_3d.csv"]
"""The names of the input files to read."""
TARGET_SPEED = 6
TARGET_SPEED = Measurement(6, Units.VELOCITY_CENTIMETRES_PER_SECOND)
"""The target speed to use when generating velocities and times."""
TARGET_ACCEL = 10
TARGET_ACCEL = Measurement(10, Units.ACCELERATION_CENTIMETRES_PER_SECOND_SQUARED)
"""The target aceleration to use when generating velocities and times."""
SHOW_PLOTS = True
"""Whether to plot the generated sequences."""
Expand All @@ -60,17 +57,20 @@ def main() -> None:
"""Generate complete PVT sequences from underdefined input data."""
for filename in FILENAMES:
# Generate the sequence
pvt_sequence = pvt.Sequence.from_csv(
sequence_data = pvt.sequence_data_from_csv(
os.path.join(DATA_DIRECTORY, filename), TARGET_SPEED, TARGET_ACCEL
)
if sequence_data is None:
return

if SHOW_PLOTS:
# Plot the sequence
plot_path_and_trajectory(pvt_sequence)
plot_path_and_trajectory(sequence_data, times_relative=True)
if OUTPUT_DIRECTORY is not None:
# Write the file with the same name plus a _generated suffix
base, extension = filename.rsplit(".", 1)
output_filename = f"{base}_generated.{extension}"
pvt_sequence.save_to_file(os.path.join(OUTPUT_DIRECTORY, output_filename))
PvtSequence.save_sequence_data(sequence_data, os.path.join(OUTPUT_DIRECTORY, output_filename))


if __name__ == "__main__":
Expand Down
Loading