Scan#

Warning

This document is under construction.

Scan Patterns#

One of vortex’s most powerful features is its scan pattern generation capability. For vortex, a scan pattern pairs a trajectory, a uniformly-spaced time-series of waypoints across the imaging scene, and set of markers that annotate the trajectory with useful information. The core scan execution components in vortex enforce no further structure on scan patterns. There is no concept of a raster scan or a spiral scan; vortex works only with trajectories and markers.

This design strategy decouples the scanning and acquisition from artificial restrictions such as equal lengths for all segments or in order acquisition of a volume. vortex defers responsibility for formatting the data into a meaningful structure (e.g., a rectangular volume) until the processing stage. That said, vortex offers an extensive set of utility classes to generate and format common scan patterns, but you are free to design your own pattern by supplying the waypoints and markers.

Time-Optimal Trajectory Generation#

vortex incorporates the time-optimal motion planner Reflexxes. High-level scan pattern generation classes use Reflexxes to calculate optimally fast corners and flyback, once provided velocity and acceleration limits for the scanner hardware. With properly calibrated dynamics limits, this allows vortex to execute the scan pattern as fast as possible without introducing distortion or artifact. vortex aims to bring the scan hardware to the desired position in the imaging scene on time, every time.

vortex uses motion planning to generate the inactive portion of the scan pattern automatically. There is no need to manually specify or calculate flyback or to intentionally round corners in the scan pattern. vortex will generate an on-the-fly inactive segment that matches the entry and exit velocities of each active segment. This capability significantly reduces distortion at pattern edges because the scanner hardware enters the active region at the necessary velocity. That said, vortex offers alternate inactive policies should your application require a different approach.

Segmented Scans#

The set of positions through which a scan must pass is called its waypoints. The waypoints object is a list of lists of points, where each list of points describes the path of a single segment. A RasterScan, for example, would have a rectangular grid as its waypoints, such as plotted below. In the plot below, each “x” is a waypoint at which data collection occurs.

(Source code, svg, pdf)

../../_images/scan-1.svg

The order and direction in which to execute segments is called the scan’s pattern. Patterns can request features such as bidirectional segments or repetition of segments. The line in the plots below illustrates the time-optimal scan trajectory that passes through the waypoints from above using two different patterns.

(Source code, svg, pdf)

../../_images/scan-2.svg

Markers#

Markers encode the high-level structure of the scan pattern and additional useful information. Without them, the trajectory is merely a sequence of waypoints. Commonly, markers denote the status of a waypoint (active or inactive) and the boundaries between substructures within the scan pattern. For example, SegmentBoundary indicates a transition point between segments within the scan. More complex uses include Event, which allow the user to associate custom information with a waypoint.

vortex supports multiplexing multiple scan patterns or markers through the use of flags, implemented by Flags. This is a bitmask which processing code tests to determine if it should act on a marker. A typical usage scenario is to insert an aiming scan within a raster scan and then subject the data acquired during each scan to separate processing or display (see example).

Components#

Tip

For examples of how to use these scan components, download the source code that generated a given figure by clicking the “Source code” link near its top left.

Waypoints#

class vortex.scan.XYWaypoints#

Generate waypoints on a 2D uniform grid of waypoints defined by its origin, rotation about that origin, physical extents, and dimension in samples.

This class is present in C++ but is abstracted away in the Python bindings. It cannot be instantiated from Python.

property samples_per_segment: int#

Number of samples \(m\) within each segment. Default is 100.

property segments_per_volume: int#

Number of segments \(n\) in each volume. Default is 100.

property ascans_per_bscan: int#

Alias for samples_per_segment

property bscans_per_volume: int#

Alias for segments_per_volume.

property shape: list[int]#

Scan shape as [segments_per_volume, samples_per_segment].

property bscan_extent: Range#

Alias for segment_extent.

property extents: list[Range]#

Scan extents as [volume_extent, segment_extent].

property offset: tuple[float]#

Offset \((x_o, y_o)\) of the scan origin in \(x\) and \(y\), respectively. Default is (0, 0).

property angle: float#

Counter-clockwise rotation \(\theta\) about the scan origin (offset) in radians. Default is 0.

property warp: [NoWarp | AngularWarp | TelecentricWarp]#

Warp to apply to waypoints, which may transform the units of the scan waypoints. Default is NoWarp().

to_waypoints()#

Generate an \(n \times m \times 2\) grid of waypoints \(W_{ij} = (x_i, y_i)\) for \(i \in [0, n-1]\) and \(j \in [0, m-1]\). The specific waypoint positions are determined by the base class.

Returns

numpy.ndarray[numpy.float64] – The waypoints 3D array with shape [segments_per_volume, samples_per_segment, 2], where the channels are \(x\) and \(y\) positions.

Raster#

(Source code, svg, pdf)

../../_images/scan-3.svg
class vortex.scan.RasterWaypoints#

Base: XYWaypoints

Generate waypoints on a \(n \times m\) rectangular grid with \(n\) segments per volume and \(m\) samples per segment. The volume direction is along the positive the \(x\)-axis whereas the segment or B-scan direction is along the positive \(y\)-axis. The physical extents of the waypoints have no required units and may be interpreted as linear (e.g., millimeters) or angular (e.g., radians or degrees). The waypoint positions \(W_{ij} = (x_i, y_j)\) as described mathematically below.

\[\begin{split}\begin{align} u_i &= \left(\frac{i}{n - 1}\right) x_\mathrm{min} + \left(1 - \frac{i}{n-1}\right) x_\mathrm{max} & v_j &= \left(\frac{j}{m - 1}\right) y_\mathrm{min} + \left(1 - \frac{j}{m-1}\right) y_\mathrm{max} \\ x_i &= u_i \cos \theta - v_i \sin \theta + x_o & y_j &= u_j \sin \theta + v_j \cos \theta + y_o \end{align}\end{split}\]

The parameters that define \(W_{ij}\) are adjusted via the the following properties and those inherited from XYWaypoints.

property segment_extent: Range#

Unitless minimum \(y_\mathrm{min}\) and maximum \(y_\mathrm{max}\) position of each segment. To flip the segment direction, set a negative maximum and a positive minimum. No requirement for symmetry. Default is Range(-2, 2).

property volume_extent: Range#

Unitless minimum \(x_\mathrm{min}\) and maximum \(x_\mathrm{max}\) position of each volume. To flip the volume direction, set a negative maximum and a positive minimum. No requirement for symmetry. Default is Range(-1, 1).

Radial#

(Source code, svg, pdf)

../../_images/scan-4.svg
class vortex.scan.RadialWaypoints#

Base: XYWaypoints

Generate waypoints on a \(n \times m\) polar grid with \(n\) segments per volume and \(m\) samples per segment. The volume direction is along the positive the \(\theta\)-axis whereas the segment or B-scan direction is along the positive \(r\)-axis. The physical extents of the waypoints have no required units and may be interpreted as linear (e.g., millimeters) or angular (e.g., radians or degrees). The waypoint positions \(W_{ij} = (x_i, y_j)\) as described mathematically below.

\[\begin{split}\begin{align} u_i &= \left(\frac{i}{n - 1}\right) \theta_\mathrm{min} + \left(1 - \frac{i}{n-1}\right) \theta_\mathrm{max} & v_j &= \left(\frac{j}{m - 1}\right) r_\mathrm{min} + \left(1 - \frac{j}{m-1}\right) r_\mathrm{max} \\ x_i &= v_i \cos ( u_i + \theta ) + x_o & y_j &= v_j \sin ( u_j + \theta ) + y_o \end{align}\end{split}\]

The parameters that define \(W_{ij}\) are adjusted via the the following properties and those inherited from XYWaypoints.

property segment_extent: Range#

Unitless minimum \(r_\mathrm{min}\) and maximum \(r_\mathrm{max}\) position of each segment. To flip the segment direction, set a negative maximum and a positive minimum. No requirement for symmetry. Default is Range(-2, 2).

property volume_extent: Range#

Unitless minimum \(\theta_\mathrm{min}\) and maximum \(\theta_\mathrm{max}\) position of each volume. To flip the volume direction, set a negative maximum and a positive minimum. No requirement for symmetry. Default is Range(0, pi).

set_half_evenly_spaced(n)#

Adjust volume_extent and segments_per_volume yield n evenly spaced segments over 180 degrees, omitting the final segment. For segments that span the origin, this yields a scan that covers the full circle without overlap.

Parameters

n (int) – Number of segments to use.

set_evenly_spaced(n)#

Adjust volume_extent and segments_per_volume yield n evenly spaced segments over 360 degrees, omitting the final segment.

Parameters

n (int) – Number of segments to use.

set_aiming()#

Adjust volume_extent and segments_per_volume to yield two orthogonal segments. Internally calls set_half_evenly_spaced() with n = 2.

Patterns#

Sequential#

(Source code, svg, pdf)

../../_images/scan-5.svg
class vortex.scan.SequentialPattern#

Visit segments in sequential order with optional bidirectionality.

property bidirectional_segments: bool#

If True, flip the direction of every odd-indexed segment. Odd-indexed segments are marked as reversed for unflipping during formatting. If False, maintain the direction of each segment. Default is False.

property bidirectional_volumes: bool#

If True, produce a scan with one volume with the segments in order followed by a second volume using the reverse segment order. The second volume’s segments are marked with the physically-based destination index for reordering during formatting. If False, produce a scan with one volume with he segments in order. Default is False.

property flags: Flags#

The flags to apply to these segments. Default is Flags().

to_pattern(waypoints)#

Generate a scan pattern from the given waypoints.

Parameters

waypoints (numpy.ndarray[float] | List[numpy.ndarray[float]]) – Active sample positions as a 3D array or list of active segments as 2D arrays. If a 3D array is provided, the segments are extracted along the first axis.

Returns

List[Segment] – The scan pattern as a list of segments.

Repeated#

(Source code, svg, pdf)

../../_images/scan-6.svg
class vortex.scan.RepeatedPattern#

Repeat segments with specified repetition count and period.

property repeat_count: int#

Number of times to repeat each segment. Default is 3.

property repeat_period: int#

Number of segments to execute in order before repeating. Default is 2.

property repeat_strategy: [RepeatOrder | RepeatPack | RepeatFlags]#

The strategy for handling repeated segments in memory.

  • RepeatOrder: Scan is performed ABCABCABC and is stored in memory as ABCABCABC.

  • RepeatPack: Scan is performed ABCABCABC but is stored in memory as AAABBBCCC.

  • RepeatFlags: Each repeat is marked with different flags, allowing the user to route them to different processing.

Default is RepeatPack().

property bidirectional_segments: bool#

If True, flip the direction of every odd-indexed segment. Odd-indexed segments are marked as reversed for unflipping during formatting. If False, maintain the direction of each segment. Default is False.

Caution

An odd repeat period will cause repetitions of a given segment to alternate directions.

See also

See Repeated Scan with Bidirectional Segments for an example.

to_pattern(waypoints)#

Generate a scan pattern from the given waypoints.

Parameters

waypoints (numpy.ndarray[float] | List[numpy.ndarray[float]]) – Active sample positions as a 3D array or list of active segments as 2D arrays. If a 3D array is provided, the segments are extracted along the first axis.

Returns

List[Segment] – The scan pattern as a list of segments.

Inactive Policy#

The strategy for generating the inactive segments of a SegmentedScan is encapsulated by an inactive policy object. The available inactive policies are described below.

(Source code, svg, pdf)

../../_images/scan-7.svg
class vortex.scan.inactive_policy.MinimumDynamicLimited#

Generate minimum-duration inactive segments that satisfy the scan’s dynamics limits.

class vortex.scan.inactive_policy.FixedDynamicLimited#

Generate fixed-duration inactive segments that satisfy the scan’s dynamics limits.

__init__(inter_segment_samples=100, inter_volume_samples=100)#

Create a new object.

Parameters
property inter_segment_samples: int#

Number of samples between segments. Deafult is 100.

property inter_volume_samples: int#

Number of samples between volumes, for scans that loop. Deafult is 100.

class vortex.scan.inactive_policy.FixedLinear#

Generate fixed-duration inactive segments that follow a straight line.

__init__(inter_segment_samples=100, inter_volume_samples=100)#

Create a new object.

Parameters
property inter_segment_samples: int#

Number of samples between segments. Deafult is 100.

property inter_volume_samples: int#

Number of samples between volumes, for scans that loop. Deafult is 100.

Scans#

class vortex.scan.SegmentedScan#

A scan pattern that consists of an alternating sequence of active and inactive segments.

The active segments are instances of Segment, which define the segment’s active sample positions. This is the base class for all segmented scans. Typically, subclasses generate the active segments internally from some parameterization, such as RasterWaypoints or RadialWaypoints.

This class is present in C++ but is abstracted away in the Python bindings. It cannot be instantiated from Python.

initialize(config)#

Initialize the scan using the supplied configuration.

Parameters

config (SegmentedScanConfig) – New configuration to apply. Type must match the derived scan type.

change(config, restart=False, event_id=0)#

Change the scan parameters to those in this new configuration. The change takes effect immediately and will interrupt the current segment.

Warning

Changing channels_per_sample is not permitted.

Parameters
  • config (SegmentedScanConfig) – New configuration to apply. Type must match the derived scan type.

  • restart (bool) – If True, the current scan state is mapped onto the new scan state to the nearest segment boundary, such that a scan changed mid-volume will continue from the same segment index within that volume. If False, the scan starts from the first segment.

  • event_id (int) – Identifying number of the change event to insert into the scan at the instant the changes take effect.

prepare(count=None)#

Buffer and consolidate the complete scan for rapid generation. If count is specified, buffer at minimum the requested number of samples. Otherwise, buffer the complete scan. Consolidation will be performed if enabled and the scan becomes fully buffered.

Parameters

count (Optional[int]) – Number of samples to buffer.

restart(sample=0, position=None, velocity=None, include_start=True)#

Reset the scan internal state to the given sample, position, and velocity. Subsequent calls for pattern generation will include a pre-scan inactive segment from this state to the first active segment.

Note

Specify no arguments or all arguments. If no arguments are specified, the scan resets to the origin at rest.

Parameters
  • sample (int) – New sample time.

  • position (numpy.ndarray[float]) – New position for all channels.

  • velocity (numpy.ndarray[float]) – New velocity for all channels.

  • include_start (bool) – If True, generate a sample at this new state. If False, do not generate a starting sample, such as when another scan has already produced it.

scan_markers()#

Prepare the scan and return all markers.

Returns

List[ScanBoundary | VolumeBoundary | SegmentBoundary | ActiveLines | InactiveLines | Event] – List of all markers in the scan, sorted by sequence number.

scan_buffer()#

Prepare the scan and return the complete scan waveform for all channels.

Returns

numpy.ndarray[float] – Scan waveforms generated at the given sampling rate. Each row is a sample, and each column is a channel.

scan_segments()#

Return a list of segments in this scan.

Returns

List[Segment] – The scan segments.

property config: SegmentedScanConfig#

Copy of the active configuration. Type corresponds to the derived class.

class vortex.scan.Segment#

An active segment as part of a SegmentedScan.

property position: numpy.ndarray[float]#

An ordered sequence of each sample positions in this segment. The positions are specified as a 2D array where each row is a position and each column is a channel. For example, a 2D segment with N positions has shape [N, 2]. The number of channels must match that of the associated SegmentedScan.

property entry_position: numpy.ndarray[float]#

Accessor for the first sample position. Read only.

property exit_position: numpy.ndarray[float]#

Accessor for the last sample position. Read only.

entry_velocity(samples_per_second)#

Approximate the scan velocity at the first sample using the specified sampling rate and the first finite difference.

Parameters

samples_per_second (float) – Sampling rate for the velocity calculation.

Returns

numpy.ndarray[float] – The calculated velocity.

exit_velocity(samples_per_second)#

Approximate the scan velocity at the last sample using the specified sampling rate and the first finite difference.

Parameters

samples_per_second (float) – Sampling rate for the velocity calculation.

Returns

numpy.ndarray[float] – The calculated velocity.

property markers: List[ScanBoundary | VolumeBoundary | SegmentBoundary | ActiveLines | InactiveLines | Event]#

List of markers associated with this segment.

class vortex.scan.SegmentedScanConfig#

Shared configuration options for segmented scan classes.

This class is present in C++ but is abstracted away in the Python bindings. It cannot be instantiated from Python.

property channels_per_sample: int#

Number of channels per sample. Default is 2 for a 2D scan pattern.

property samples_per_second: int#

Number of samples per second for the scan. This is used to ensure that dynamic limits are met. Default is 100_000.

property sampling_interval: float#

Interval between samples computed from samples_per_second. Read-only.

property loop: bool#

Restart the scan after it completes. This produces an infinite scan pattern. Default is True.

property consolidate: bool#

Optimize internal scan buffers once the scan has looped once. Default is False.

property limits: List[Limits]#

Dynamics limits for each channel of the scan. These are used for limit checks and inactive segment policies. Default is [Limits(position=(-12.5, 12.5), velocity=8e3, acceleration=5e6)]*2.

property bypass_limits_check: bool#

If False, check that the scan pattern satisfies position, velocity, and acceleration limits. If True, skip this check. This check does not apply to generated inactive segments. Default is False.

property inactive_policy: [MinimumDynamicLimited | FixedDynamicLimited | FixedLinear]#

Policy to use for generating inactive segments. Default is MinimumDynamicLimited().

to_segments()#

Generate the segments described by this configuration.

Returns

List[Segment] – The pattern segments.

See also

See Interleave Different Scans for an example application.

validate()#

Check the configuration for errors.

Raises

RuntimeError – If the configuration is invalid.

class vortex.scan.Limits#

Dynamics limits for a single axis.

__init__(position=(- 10, 10), velocity=100, acceleration=10_000)#

Create a new object.

Parameters
property position: Range#

Upper and lower positions bounds.

property velocity: float#

Absolute value of velocity limit.

property acceleration: float#

Absolute value of velocity limit.

Raster Scan#

(Source code, svg, pdf)

../../_images/scan-8.svg
class vortex.scan.RasterScan#

Base: SegmentedScan

A raster scan with non-repeated segments.

All members are inherited.

class vortex.scan.RasterScanConfig#

Bases: RasterWaypoints, SequentialPattern, SegmentedScanConfig

Configuration object for RasterScan.

All members are inherited.

Repeated Raster Scan#

(Source code, svg, pdf)

../../_images/scan-9.svg
class vortex.scan.RepeatedRasterScan#

Base: SegmentedScan

A raster scan with repeated segments.

All members are inherited.

class vortex.scan.RepeatedRasterScanConfig#

Bases: RasterWaypoints, RepeatedPattern, SegmentedScanConfig

Configuration object for RepeatedRasterScan.

All members are inherited.

Radial Scan#

(Source code, svg, pdf)

../../_images/scan-10.svg
class vortex.scan.RadialScan#

Base: SegmentedScan

A radial scan with non-repeated segments.

All members are inherited.

class vortex.scan.RadialScanConfig#

Bases: RadialWaypoints, SequentialPattern, SegmentedScanConfig

Configuration object for RadialScan.

All members are inherited.

Repeated Radial Scan#

(Source code, svg, pdf)

../../_images/scan-11.svg
class vortex.scan.RepeatedRadialScan#

Base: SegmentedScan

A radial scan with repeated segments.

All members are inherited.

class vortex.scan.RepeatedRadialScanConfig#

Bases: RepeatedRadialWaypoints, SequentialPattern, SegmentedScanConfig

Configuration object for RepeatedRadialScan.

All members are inherited.

Freeform Scan#

(Source code, svg, pdf)

../../_images/scan-12.svg
class vortex.scan.FreeformScan#

Base: SegmentedScan

A scan pattern that allows user-specified active segments.

All members are inherited.

class vortex.scan.FreeformScanConfig#

Base: SegmentedScanConfig

Configuration object for FreeformScan.

property segments: List[Segment]#

Active segments in this scan pattern.