Skip to content

path

plantimager.path Link

CalibrationPath Link

CalibrationPath(path, n_points_line=11, x_lims=None, y_lims=None)

Bases: Path

Creates a calibration path for the Plant Imager.

Notes

The calibration path is made of the path to calibrate, plus four linear paths: 1. a "y-line" (from y_min to y_max) at x_min, facing x_max with n_points_line poses 2. a first "half x-line" (from x_min to x_max/2) at y_max/2, facing x_max with n_points_line/2 poses 3. a second "half x-line" (from x_max/2 to x_max) at y_max/2, facing x_min with n_points_line/2 poses 4. a "y-line" (from y_min to y_max) at x_max, facing x_min with n_points_line poses The central and extreme points of the "x-line" at y_max/2 are removed to avoid duplicates.

See Also

plantimager.tasks.colmap.use_calibrated_poses

Examples:

>>> from plantimager.path import CalibrationPath
>>> from plantimager.path import Circle
>>> n_points_circle = 36
>>> circular_path = Circle(300, 300, 50, 0, 250, n_points_circle)
>>> n_points_line = 11
>>> calib_path = CalibrationPath(circular_path, n_points_line, x_lims=[0, 600], y_lims=[0, 600])
>>> calib_path[36:]  # the calibration lines
>>> len(calib_path) == n_points_circle + n_points_line*3
>>> # View the Calibration points coordinates:
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x,y = np.array([(p.x, p.y) for p in calib_path]).T  # get the XY coordinates
>>> fig, ax = plt.subplots(figsize=(8,8))
>>> ax.scatter(x[:36], y[:36], marker='+', color=['r']*n_points_circle, label="Circle")
>>> ax.scatter(x[36:], y[36:], marker='x', color=['b']*(n_points_line*3-3), label="Lines")
>>> [ax.text(x[i], y[i], str(i)) for i in range(len(x))]
>>> ax.grid(True, which='major', axis='both', linestyle='dotted')
>>> ax.set_aspect('equal')
>>> ax.legend()
>>> ax.set_title("Calibration path")
>>> ax.set_xlabel("X-axis")
>>> ax.set_ylabel("Y-axis")

Parameters:

Name Type Description Default
path Path

A path to calibrate.

required
n_points_line int

The number of points per line, should be an odd number (or we will add one point). Defaults to 11 and should be greater or equal to 5.

11
x_lims list of int

Set the min/max x range for the calibration (do NOT apply to the path to calibrate). Else, will be set from the min/max of the path to calibrate on the x-axis.

None
y_lims list of int

Set the min/max y range for the calibration (do NOT apply to the path to calibrate). Else, will be set from the min/max of the path to calibrate on the y-axis.

None
Source code in plantimager/path.py
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def __init__(self, path, n_points_line=11, x_lims=None, y_lims=None):
    """
    Parameters
    ----------
    path : Path
        A path to calibrate.
    n_points_line : int, optional
        The number of points per line, should be an odd number (or we will add one point).
        Defaults to `11` and should be greater or equal to `5`.
    x_lims : list of int, optional
        Set the min/max `x` range for the calibration (do NOT apply to the path to calibrate).
        Else, will be set from the min/max of the path to calibrate on the x-axis.
    y_lims : list of int, optional
        Set the min/max `y` range for the calibration (do NOT apply to the path to calibrate).
        Else, will be set from the min/max of the path to calibrate on the y-axis.
    """
    super().__init__()
    # Check the `n_points_line` parameter:
    try:
        assert n_points_line >= 5
    except:
        raise ValueError(f"CalibrationPath require a number of point per line >= `5`, got {n_points_line}!")

    # - Start the calibration path with the path to calibrate:
    self.extend(path)

    p0 = path[0]  # get the first pose
    # Compute the X & Y range for calibration lines:
    if x_lims is None:
        x_coords = [pelt.x for pelt in path]
        x_min, x_max = min(x_coords), max(x_coords)
        # x_min = path[np.argmin([p_i.x - p0.x for p_i in path])].x
        # x_max = path[np.argmax([p_i.x - p0.x for p_i in path])].x
    else:
        x_min, x_max = x_lims
    if y_lims is None:
        y_coords = [pelt.y for pelt in path]
        y_min, y_max = min(y_coords), max(y_coords)
        # y_min = path[np.argmin([p_i.y - p0.y for p_i in path])].y
        # y_max = path[np.argmax([p_i.y - p0.y for p_i in path])].y
    else:
        y_min, y_max = y_lims

    # Get the middle coordinates in X & Y:
    mid_x = (x_max - x_min) // 2. + x_min
    mid_y = (y_max - y_min) // 2. + y_min
    # Make sure the number of point per line is an odd number:
    if n_points_line % 2 == 0:
        n_points_line += 1
    # Get the number of point to make a "half line":
    n_points_half_line = n_points_line // 2 + 1

    # Add the first Y-line at x-min, facing the x-max:
    self.extend(Line(x_min, y_min, p0.z, x_min, y_max, p0.z, 270., p0.tilt, n_points_line))
    # Add the first half X-line facing the x-max:
    #  - remove the first pose as it has been done during the first Y-line at x-min
    #  - remove the last pose as it would be at the center (and we want to exclude a central point)
    self.extend(Line(x_min, mid_y, p0.z, mid_x, mid_y, p0.z, 270., p0.tilt, n_points_half_line)[1:-1])
    # Add the second half X-line facing the x-min:
    #  - remove the first pose as it would be at the center (and we want to exclude a central point)
    #  - remove the last pose as it will be done during the second Y-line at x-max
    self.extend(Line(mid_x, mid_y, p0.z, x_max, mid_y, p0.z, 90., p0.tilt, n_points_half_line)[1:-1])
    # Add the second Y-line at x-max, facing the x-min:
    self.extend(Line(x_max, y_min, p0.z, x_max, y_max, p0.z, 90., p0.tilt, n_points_line))

Circle Link

Circle(center_x, center_y, z, tilt, radius, n_points, start_offset)

Bases: Path

A 2D circular path in the XY plane for the scanner, with the camera facing the center of the circle.

Notes

If an iterable is given for tilt, performs more than one camera acquisition at same xyz position.

See Also

plantimager.path.circle

Examples:

>>> from plantimager.path import Circle
>>> circular_path = Circle(200, 200, 50, 0, 200, 9)
>>> circular_path
[x: 0.0, y: 200.0, z: 50, pan: 270.0, tilt: 0, exact_pose: False,
 x: 46.791111376204384, y: 71.44247806269215, z: 50, pan: 310.0, tilt: 0, exact_pose: False,
 x: 165.27036446661393, y: 3.038449397558395, z: 50, pan: 350.0, tilt: 0, exact_pose: False,
 x: 299.99999999999994, y: 26.794919243112247, z: 50, pan: 29.999999999999986, tilt: 0, exact_pose: False,
 x: 387.93852415718163, y: 131.59597133486622, z: 50, pan: 70.0, tilt: 0, exact_pose: False,
 x: 387.9385241571817, y: 268.40402866513375, z: 50, pan: 110.0, tilt: 0, exact_pose: False,
 x: 300.0000000000001, y: 373.2050807568877, z: 50, pan: 149.99999999999997, tilt: 0, exact_pose: False,
 x: 165.270364466614, y: 396.96155060244166, z: 50, pan: 190.0, tilt: 0, exact_pose: False,
 x: 46.79111137620444, y: 328.5575219373079, z: 50, pan: 230.0, tilt: 0, exact_pose: False]
>>> circular_path = Circle(200, 200, 50, (0, 10), 200, 2)
>>> circular_path
[x: 0.0, y: 200.0, z: 50, pan: 270.0, tilt: 0, exact_pose: False,
 x: 0.0, y: 200.0, z: 50, pan: 270.0, tilt: 10, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 50, pan: 90.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 50, pan: 90.0, tilt: 10, exact_pose: False]

Initializes an object by generating a circular arrangement of points in 3D space.

Each path element is defined by the combination of the 2D circle coordinates, a fixed z-value, and specified tilt angles. This results in path elements with varying tilt angles, forming a complete circular motion in 3D space.

Parameters:

Name Type Description Default
center_x float

The x-coordinate (in millimeters) of the center of the circle.

required
center_y float

The y-coordinate (in millimeters) of the center of the circle.

required
z float

The fixed z-coordinate (in millimeters) for all points along the circle.

required
tilt Union[float, Iterable[float]]

One or more tilt angles (in degrees) to apply at each point.

required
radius float

The radius (in millimeters) of the circle.

required
n_points int

The number of points to generate around the circle.

required
start_offset float

The angular offset (in degrees) to shift the starting position along the circle. Measured counter-clockwise from the positive x-axis.

required
Source code in plantimager/path.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def __init__(self, center_x, center_y, z, tilt, radius, n_points, start_offset):
    """Initializes an object by generating a circular arrangement of points in 3D space.

    Each path element is defined by the combination of the 2D circle
    coordinates, a fixed z-value, and specified tilt angles. This results
    in path elements with varying tilt angles, forming a complete circular
    motion in 3D space.

    Parameters
    ----------
    center_x : float
        The x-coordinate (in millimeters) of the center of the circle.
    center_y : float
        The y-coordinate (in millimeters) of the center of the circle.
    z : float
        The fixed z-coordinate (in millimeters) for all points along the circle.
    tilt : Union[float, Iterable[float]]
        One or more tilt angles (in degrees) to apply at each point.
    radius : float
        The radius (in millimeters) of the circle.
    n_points : int
        The number of points to generate around the circle.
    start_offset : float
        The angular offset (in degrees) to shift the starting position
        along the circle. Measured counter-clockwise from the positive x-axis.
    """
    super().__init__()
    x, y, pan = circle(center_x, center_y, radius, n_points, start_offset)

    if not isinstance(tilt, Iterable):
        tilt = [tilt]

    for i in range(n_points):
        for t in tilt:
            self.append(PathElement(x[i], y[i], z, pan[i], t))

Cylinder Link

Cylinder(center_x, center_y, z_range, tilt, radius, n_points, n_circles=2, aligned=True)

Bases: Path

A cylinder-like path for the scanner as multiple circles, with the camera facing the center of the circle.

Makes as much circular paths as n_circles within the given z-range.

Notes

If an iterable is given for tilt, performs more than one camera acquisition at same xyz position.

See Also

plantimager.path.circle

Examples:

>>> from plantimager.path import Cylinder
>>> cylinder_path = Cylinder(200, 200, (0, 50), 0, 200, n_points=2, n_circles=2)
>>> cylinder_path
[x: 0.0, y: 200.0, z: 0, pan: 270.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 0, pan: 90.0, tilt: 0, exact_pose: False,
 x: 0.0, y: 200.0, z: 50, pan: 270.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 50, pan: 90.0, tilt: 0, exact_pose: False]
>>> cylinder_path = Cylinder(200, 200, (0, 50), 0, 200, n_points=2, n_circles=2, aligned=False)
>>> cylinder_path
>>> cylinder_path = Cylinder(200, 200, (0, 50), 0, 200, n_points=2, n_circles=3)
>>> cylinder_path
[x: 0.0, y: 200.0, z: 0.0, pan: 270.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 0.0, pan: 90.0, tilt: 0, exact_pose: False,
 x: 0.0, y: 200.0, z: 25.0, pan: 270.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 25.0, pan: 90.0, tilt: 0, exact_pose: False,
 x: 0.0, y: 200.0, z: 50.0, pan: 270.0, tilt: 0, exact_pose: False,
 x: 400.0, y: 199.99999999999997, z: 50.0, pan: 90.0, tilt: 0, exact_pose: False]

Initialization of a cylinder-like structure composed of multiple circles at different heights within a z-range.

This class constructor generates n_circles at varying heights within a given z-range and aligns the circular points optionally.

Parameters:

Name Type Description Default
center_x float

The x-coordinate (in millimeters) of the center of each circle comprising the cylinder.

required
center_y float

The y-coordinate (in millimeters) of the center of each circle comprising the cylinder.

required
z_range tuple of float

A Pair of values indicating the minimum and maximum z-coordinates (in millimeters) for the cylindrical structure.

required
tilt float

The tilt angle of each circle in degrees relative to its parallel orientation to the XY-plane.

required
radius float

The radius (in millimeters) of each circle forming the cylinder.

required
n_points int

The number of evenly spaced points that define each circle.

required
n_circles int

The total number of circles that make up the cylindrical structure. Defaults to 2.

2
aligned bool

If True, aligns the start points of all circles. Otherwise, offsets start points progressively based on the number of circles. Defaults to True.

True

Raises:

Type Description
ValueError

If n_circles is less than 2 because at least two circles are required to form a cylinder-like structure.

Source code in plantimager/path.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def __init__(self, center_x, center_y, z_range, tilt, radius, n_points, n_circles=2, aligned=True):
    """Initialization of a cylinder-like structure composed of multiple circles at different heights within a z-range.

    This class constructor generates `n_circles` at varying heights within a
    given z-range and aligns the circular points optionally.

    Parameters
    ----------
    center_x : float
        The x-coordinate (in millimeters) of the center of each circle comprising the cylinder.
    center_y : float
        The y-coordinate (in millimeters) of the center of each circle comprising the cylinder.
    z_range : tuple of float
        A Pair of values indicating the minimum and maximum z-coordinates (in millimeters)
        for the cylindrical structure.
    tilt : float
        The tilt angle of each circle in degrees relative to its parallel
        orientation to the XY-plane.
    radius : float
        The radius (in millimeters) of each circle forming the cylinder.
    n_points : int
        The number of evenly spaced points that define each circle.
    n_circles : int, optional
        The total number of circles that make up the cylindrical structure.
        Defaults to 2.
    aligned : bool, optional
        If True, aligns the start points of all circles. Otherwise, offsets
        start points progressively based on the number of circles. Defaults to True.

    Raises
    ------
    ValueError
        If `n_circles` is less than 2 because at least two circles
        are required to form a cylinder-like structure.
    """
    # Call the parent class (Path) initializer
    super().__init__()

    # Ensure `n_circles` is at least 2, as a minimum of two circles is required to form a cylinder
    try:
        assert n_circles >= 2
    except AssertionError:
        raise ValueError("You need a minimum of two circles to make a cylinder!")

    # Compute the angle (in degrees) between successive points on a single circle
    step_angle = 360 / n_points

    # Determine the phase offset between the starting point of circles,
    # differentiating aligned from offset configurations
    start_offset = 0 if aligned else step_angle / n_circles

    # Extract the minimum and maximum z-values (heights) for the cylinder
    min_z, max_z = z_range
    # Calculate and iterate over `n_circles` evenly spaced heights in the z-range
    for circle_idx, z_circle in enumerate(np.arange(min_z, max_z + 1, (max_z - min_z) / float(n_circles - 1))):
        # For each height, create a 2D circular path (Circle) and offset it if required
        self.extend(Circle(center_x, center_y, z_circle, tilt, radius, n_points, start_offset*circle_idx))

Line Link

Line(x_start, y_start, z_start, x_stop, y_stop, z_stop, pan, tilt, n_points)

Bases: Path

A 3D linear path with specified start and stop positions, camera parameters, and number of points along the path.

Notes

If an iterable is given for tilt, performs more than one camera acquisition at same xyz position.

Examples:

>>> from plantimager.path import Line
>>> linear_path = Line(0, 0, 0, 10, 10, 0, 180, 0, n_points=2)
>>> linear_path
[x: 0.0, y: 0.0, z: 0.0, pan: 180, tilt: 0, exact_pose: True,
 x: 10.0, y: 10.0, z: 0.0, pan: 180, tilt: 0, exact_pose: True]

Initializes an object by generating a linear arrangement of points in 3D space.

This class generates a linear sequence of points in 3D space, with each point associated with specific camera pan and tilt values. It can also accommodate multiple camera acquisitions at the same 3D position if multiple tilt values are provided.

Parameters:

Name Type Description Default
x_start float

The starting x-coordinate (in millimeters) of the 3D line path.

required
y_start float

The starting y-coordinate (in millimeters) of the 3D line path.

required
z_start float

The starting z-coordinate (in millimeters) of the 3D line path.

required
x_stop float

The ending x-coordinate (in millimeters) of the 3D line path.

required
y_stop float

The ending y-coordinate (in millimeters) of the 3D line path.

required
z_stop float

The ending z-coordinate (in millimeters) of the 3D line path.

required
pan float

The pan angle (in degrees) for all path elements.

required
tilt float or Iterable[float]

The tilt angle(s) (in degrees) to be applied at each point of the path. If a single float is provided, it will be used for all path elements. If an iterable is provided, each tilt value will be used in conjunction with the generated points.

required
n_points int

The number of points to generate along the 3D line path. Must be greater than or equal to 2.

required

Raises:

Type Description
ValueError

If n_points is less than 2, as at least two points are required to define a line.

TypeError

If tilt is not an iterable, nor a single float.

Source code in plantimager/path.py
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
def __init__(self, x_start, y_start, z_start, x_stop, y_stop, z_stop, pan, tilt, n_points):
    """Initializes an object by generating a linear arrangement of points in 3D space.

    This class generates a linear sequence of points in 3D space, with each point
    associated with specific camera pan and tilt values.
    It can also accommodate multiple camera acquisitions at the same 3D position
    if multiple tilt values are provided.

    Parameters
    ----------
    x_start : float
        The starting x-coordinate (in millimeters) of the 3D line path.
    y_start : float
        The starting y-coordinate (in millimeters) of the 3D line path.
    z_start : float
        The starting z-coordinate (in millimeters) of the 3D line path.
    x_stop : float
        The ending x-coordinate (in millimeters) of the 3D line path.
    y_stop : float
        The ending y-coordinate (in millimeters) of the 3D line path.
    z_stop : float
        The ending z-coordinate (in millimeters) of the 3D line path.
    pan : float
        The pan angle (in degrees) for all path elements.
    tilt : float or Iterable[float]
        The tilt angle(s) (in degrees) to be applied at each point of the path.
        If a single float is provided, it will be used for all path elements.
        If an iterable is provided, each tilt value will be used in conjunction with the generated points.
    n_points : int
        The number of points to generate along the 3D line path. Must be greater than or equal to 2.

    Raises
    ------
    ValueError
        If `n_points` is less than 2, as at least two points are required to define a line.
    TypeError
        If `tilt` is not an iterable, nor a single float.
    """
    super().__init__()
    try:
        assert n_points >= 2
    except AssertionError:
        raise ValueError("You need a minimum of two points to make a line!")

    if not isinstance(tilt, Iterable):
        tilt = [tilt]

    x, y, z = line_3d(x_start, y_start, z_start, x_stop, y_stop, z_stop, n_points)
    for i in range(n_points):
        for t in tilt:
            self.append(PathElement(x[i], y[i], z[i], pan, t, exact_pose=False))

Path Link

Path()

Bases: list

A path is an abstract class that should be a list of PathElement instances.

Source code in plantimager/path.py
169
170
def __init__(self):
    super().__init__()

PathElement Link

PathElement(x=None, y=None, z=None, pan=None, tilt=None, exact_pose=True)

Bases: Pose

Singleton for a Path class.

This class extends the basic coordinates of x, y, z with additional parameters for orientation (pan, tilt) and a boolean flag to specify if the pose must be treated as exact.

Attributes:

Name Type Description
x (float, optional)

The x-coordinate in 3D space. Initialized in the parent class.

y (float, optional)

The y-coordinate in 3D space. Initialized in the parent class.

z (float, optional)

The z-coordinate in 3D space. Initialized in the parent class.

pan (float, optional)

Angular orientation around the vertical axis, in degrees. Defaults to None.

tilt (float, optional)

Angular orientation around the horizontal axis, in degrees. Defaults to None.

exact_pose (bool, optional)

Specifies whether the pose represents an exact location and orientation. Defaults to True.

See Also

plantimager.path.Pose

Examples:

>>> from plantimager.path import PathElement
>>> elt = PathElement(50, 250, 80, 270, 0, True)
>>> print(elt)
x: 50, y: 250, z: 80, pan: 270, tilt: 0, exact_pose: True

Represents a 3D pose with pan and tilt angles, as well as an indicator for whether the pose is an exact reference or not.

Parameters:

Name Type Description Default
x float

The x-coordinate in 3D space. Defaults to None.

None
y float

The y-coordinate in 3D space. Defaults to None.

None
z float

The z-coordinate in 3D space. Defaults to None.

None
pan float

Angular orientation around the vertical axis, in degrees. Defaults to None.

None
tilt float

Angular orientation around the horizontal axis, in degrees. Defaults to None.

None
exact_pose bool

Specifies if this pose is treated as exact. Defaults to True.

True
Source code in plantimager/path.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def __init__(self, x=None, y=None, z=None, pan=None, tilt=None, exact_pose=True):
    """Represents a 3D pose with pan and tilt angles, as well as an
    indicator for whether the pose is an exact reference or not.

    Parameters
    ----------
    x : float, optional
        The x-coordinate in 3D space. Defaults to None.
    y : float, optional
        The y-coordinate in 3D space. Defaults to None.
    z : float, optional
        The z-coordinate in 3D space. Defaults to None.
    pan : float, optional
        Angular orientation around the vertical axis, in degrees.
        Defaults to None.
    tilt : float, optional
        Angular orientation around the horizontal axis, in degrees.
        Defaults to None.
    exact_pose : bool, optional
        Specifies if this pose is treated as exact. Defaults to True.

    """
    super().__init__(x, y, z, pan, tilt)
    self.exact_pose = exact_pose

attributes Link

attributes()

Returns a list of attribute names related to the object's position and orientation.

Returns:

Type Description
list of str

A list containing the names of the attributes: "x", "y", "z", "pan", and "tilt".

Source code in plantimager/path.py
88
89
90
91
92
93
94
95
96
def attributes(self):
    """Returns a list of attribute names related to the object's position and orientation.

    Returns
    -------
    list of str
        A list containing the names of the attributes: "x", "y", "z", "pan", and "tilt".
    """
    return ["x", "y", "z", "pan", "tilt"]

Pose Link

Pose(x=None, y=None, z=None, pan=None, tilt=None)

Bases: object

Abstract representation of a 'camera pose' as its 5D coordinates.

This class is meant to encapsulate a 3D point using Cartesian coordinates (x, y, z) along with additional properties for orientation (pan and tilt).

Attributes:

Name Type Description
x (float, optional)

The x-coordinate in 3D space. Initialized in the parent class.

y (float, optional)

The y-coordinate in 3D space. Initialized in the parent class.

z (float, optional)

The z-coordinate in 3D space. Initialized in the parent class.

pan (float, optional)

Angular orientation around the vertical axis, in degrees. Defaults to None.

tilt (float, optional)

Angular orientation around the horizontal axis, in degrees. Defaults to None.

Examples:

>>> from plantimager.path import Pose
>>> p = Pose(50, 250, 80, 270, 0)
>>> print(p)
x: 50, y: 250, z: 80, pan: 270, tilt: 0

Represents a 3D point in space along with pan and tilt angles.

Parameters:

Name Type Description Default
x float

The x-coordinate (in millimeters) of the 3D point. Defaults to None.

None
y float

The y-coordinate (in millimeters) of the 3D point. Defaults to None.

None
z float

The z-coordinate (in millimeters) of the 3D point. Defaults to None.

None
pan float

The pan angle (in degrees), i.e. horizontal rotation, associated with the point. Defaults to None.

None
tilt float

The tilt angle (in degrees), i.e. vertical rotation, associated with the point. Defaults to None.

None
Source code in plantimager/path.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(self, x=None, y=None, z=None, pan=None, tilt=None):
    """Represents a 3D point in space along with pan and tilt angles.

    Parameters
    ----------
    x : float, optional
        The x-coordinate (in millimeters) of the 3D point. Defaults to ``None``.
    y : float, optional
        The y-coordinate (in millimeters) of the 3D point. Defaults to ``None``.
    z : float, optional
        The z-coordinate (in millimeters) of the 3D point. Defaults to ``None``.
    pan : float, optional
        The pan angle (in degrees), _i.e._ horizontal rotation, associated with the point.
        Defaults to ``None``.
    tilt : float, optional
        The tilt angle (in degrees), _i.e._ vertical rotation, associated with the point.
        Defaults to ``None``.
    """
    self.x = x
    self.y = y
    self.z = z
    self.pan = pan
    self.tilt = tilt

attributes Link

attributes()

Returns a list of attribute names related to the object's position and orientation.

Returns:

Type Description
list of str

A list containing the names of the attributes: "x", "y", "z", "pan", and "tilt".

Source code in plantimager/path.py
88
89
90
91
92
93
94
95
96
def attributes(self):
    """Returns a list of attribute names related to the object's position and orientation.

    Returns
    -------
    list of str
        A list containing the names of the attributes: "x", "y", "z", "pan", and "tilt".
    """
    return ["x", "y", "z", "pan", "tilt"]

circle Link

circle(center_x, center_y, radius, n_points, offset_angle=0)

Generates the 2D coordinates and angles for points evenly distributed on a circle, facing the central point.

This function computes the x and y coordinates of n_points evenly distributed on the circumference of a circle centered at (center_x, center_y) with a specified radius. Additionally, the function calculates the angle (in degrees) for each point with respect to the vertical axis, optionally offset by an offset_angle.

Parameters:

Name Type Description Default
center_x float

The x-coordinate of the circle's center.

required
center_y float

The y-coordinate of the circle's center.

required
radius float

The radius of the circle.

required
n_points int

The number of points to generate along the circle's circumference.

required
offset_angle float

The angle offset in degrees for the start of the point distribution. Defaults to 0.

0

Returns:

Type Description
list of float

A list of x-coordinates for the points on the circle.

list of float

A list of y-coordinates for the points on the circle.

list of float

A list of corresponding angles (in degrees, measured clockwise) for the points with respect to the vertical axis.

Examples:

>>> from plantimager.path import circle
>>> x, y, p = circle(0, 0, 5, 5)
>>> list(zip(x, y, p))  # to get set of 2D coordinates (x, y) and associated pan.
[(-5.0, 0.0, 270.0),
 (-1.5450849718747373, -4.755282581475767, 342.0),
 (4.045084971874736, -2.9389262614623664, 54.0),
 (4.045084971874738, 2.938926261462365, 126.0),
 (-1.5450849718747361, 4.755282581475768, 198.0)]
Source code in plantimager/path.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
def circle(center_x, center_y, radius, n_points, offset_angle=0):
    """Generates the 2D coordinates and angles for points evenly distributed on a circle, facing the central point.

    This function computes the x and y coordinates of `n_points` evenly distributed
    on the circumference of a circle centered at (`center_x`, `center_y`) with a
    specified `radius`. Additionally, the function calculates the angle (in degrees)
    for each point with respect to the vertical axis, optionally offset by an
    `offset_angle`.

    Parameters
    ----------
    center_x : float
        The x-coordinate of the circle's center.
    center_y : float
        The y-coordinate of the circle's center.
    radius : float
        The radius of the circle.
    n_points : int
        The number of points to generate along the circle's circumference.
    offset_angle : float, optional
        The angle offset in degrees for the start of the point distribution. Defaults to 0.

    Returns
    -------
    list of float
        A list of x-coordinates for the points on the circle.
    list of float
        A list of y-coordinates for the points on the circle.
    list of float
        A list of corresponding angles (in degrees, measured clockwise) for the points
        with respect to the vertical axis.

    Examples
    --------
    >>> from plantimager.path import circle
    >>> x, y, p = circle(0, 0, 5, 5)
    >>> list(zip(x, y, p))  # to get set of 2D coordinates (x, y) and associated pan.
    [(-5.0, 0.0, 270.0),
     (-1.5450849718747373, -4.755282581475767, 342.0),
     (4.045084971874736, -2.9389262614623664, 54.0),
     (4.045084971874738, 2.938926261462365, 126.0),
     (-1.5450849718747361, 4.755282581475768, 198.0)]
    """
    x, y, p = [], [], []
    # Convert starting_angle to radians for computation
    starting_angle_rad = math.radians(offset_angle)

    for i in range(n_points):
        pan = 2 * i * math.pi / n_points + starting_angle_rad
        x.append(center_x - radius * math.cos(pan))
        y.append(center_y - radius * math.sin(pan))
        pan = pan * 180 / math.pi
        p.append((pan - 90) % 360)

    return x, y, p

line_1d Link

line_1d(start, stop, n_points)

Generates a 1D linearly spaced sequence of values between start and stop, inclusive.

The returned sequence contains n_points values equally spaced between these boundary values.

Parameters:

Name Type Description Default
start float or int

The starting coordinate of the sequence.

required
stop float or int

The ending coordinate of the sequence.

required
n_points int

The number of values to generate in the sequence. Must be greater than or equal to 2.

required

Returns:

Type Description
list of float

A list containing n_points equally spaced values from start to stop, inclusive.

Examples:

>>> from plantimager.path import line_1d
>>> line_1d(0,10,n_points=5)
[0.0, 2.5, 5.0, 7.5, 10.0]
Source code in plantimager/path.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def line_1d(start, stop, n_points):
    """Generates a 1D linearly spaced sequence of values between `start` and `stop`, inclusive.

    The returned sequence contains `n_points` values equally spaced between these boundary values.

    Parameters
    ----------
    start : float or int
        The starting coordinate of the sequence.
    stop : float or int
        The ending coordinate of the sequence.
    n_points : int
        The number of values to generate in the sequence. Must be greater than or equal to 2.

    Returns
    -------
    list of float
        A list containing `n_points` equally spaced values from `start` to `stop`, inclusive.

    Examples
    --------
    >>> from plantimager.path import line_1d
    >>> line_1d(0,10,n_points=5)
    [0.0, 2.5, 5.0, 7.5, 10.0]
    """
    return [(1 - i / (n_points - 1)) * start + (i / (n_points - 1)) * stop for i in range(n_points)]

line_3d Link

line_3d(x_start, y_start, z_start, x_stop, y_stop, z_stop, n_points)

Generates coordinates of a 3D line given start and stop points and the number of intermediate points.

This function computes the coordinates of a 3D line by generating intermediate linearly spaced points between the given start and stop points along the x, y, and z dimensions.

Parameters:

Name Type Description Default
x_start float

The starting coordinate of the line on the x-axis.

required
y_start float

The starting coordinate of the line on the y-axis.

required
z_start float

The starting coordinate of the line on the z-axis.

required
x_stop float

The ending coordinate of the line on the x-axis.

required
y_stop float

The ending coordinate of the line on the y-axis.

required
z_stop float

The ending coordinate of the line on the z-axis.

required
n_points int

The number of points to generate along each line segment, including the start and stop points.

required

Returns:

Type Description
tuple of numpy.ndarray

A tuple containing the x, y, and z coordinates of the generated 3D line. Each element of the tuple is a len-3 list of size n_points with linearly spaced values between the respective start and stop coordinates.

Examples:

>>> from plantimager.path import line_3d
>>> line_3d(0, 0, 0, 10, 10, 10, n_points=5)
([0.0, 2.5, 5.0, 7.5, 10.0],
 [0.0, 2.5, 5.0, 7.5, 10.0],
 [0.0, 2.5, 5.0, 7.5, 10.0])
Source code in plantimager/path.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
def line_3d(x_start, y_start, z_start, x_stop, y_stop, z_stop, n_points):
    """Generates coordinates of a 3D line given start and stop points and the number of intermediate points.

    This function computes the coordinates of a 3D line by generating intermediate
    linearly spaced points between the given start and stop points along the x, y, and z dimensions.

    Parameters
    ----------
    x_start : float
        The starting coordinate of the line on the x-axis.
    y_start : float
        The starting coordinate of the line on the y-axis.
    z_start : float
        The starting coordinate of the line on the z-axis.
    x_stop : float
        The ending coordinate of the line on the x-axis.
    y_stop : float
        The ending coordinate of the line on the y-axis.
    z_stop : float
        The ending coordinate of the line on the z-axis.
    n_points : int
        The number of points to generate along each line segment, including the start
        and stop points.

    Returns
    -------
    tuple of numpy.ndarray
        A tuple containing the x, y, and z coordinates of the generated 3D line.
        Each element of the tuple is a len-3 list of size `n_points` with linearly
        spaced values between the respective start and stop coordinates.

    Examples
    --------
    >>> from plantimager.path import line_3d
    >>> line_3d(0, 0, 0, 10, 10, 10, n_points=5)
    ([0.0, 2.5, 5.0, 7.5, 10.0],
     [0.0, 2.5, 5.0, 7.5, 10.0],
     [0.0, 2.5, 5.0, 7.5, 10.0])
    """
    return line_1d(x_start, x_stop, n_points), line_1d(y_start, y_stop, n_points), line_1d(z_start, z_stop, n_points)