Skip to content

path

plantimager.controller.scanner.path Link

Path Generation for Plant Imaging Systems.

This module provides classes and functions for generating and manipulating 3D paths for plant imaging systems. It includes implementations for various path types such as circles, cylinders, and lines, as well as utilities for path manipulation.

Key Features: - Abstract representation of camera poses in 5D space (x, y, z, pan, tilt) - Path generation for common scanning patterns (circles, cylinders, lines) - Path manipulation and combination utilities - Support for calibration paths - Precise control over camera orientation at each path point

Usage Examples:

>>> from plantimager.controller.scanner.path import Circle, Pose
>>> # Create a circular path with 10 points
>>> center_x, center_y = 200, 200  # Center coordinates in mm
>>> height = 50  # Height in mm
>>> tilt = 0  # Camera tilt in degrees
>>> radius = 100  # Circle radius in mm
>>> n_points = 10  # Number of points in the circle
>>> circular_path = Circle(center_x, center_y, height, tilt, radius, n_points)
>>> # Access the first point in the path
>>> first_point = circular_path[0]
>>> print(f"First point: x={first_point.x}, y={first_point.y}, z={first_point.z}")

CalibrationPath Link

CalibrationPath(path, n_points_line)

Bases: Path

Creates a calibration path for the scanner.

This build two lines spanning the X & Y axes extent of the given path.

Notes

The calibration path is made of the path to calibrate, plus two linear paths, X & Y, in that order.

Takes the first PathElement of the input path to calibrate as lines starting points

Takes the max distance to input path origin along x & y axes to create X & Y lines ending points. Let x0, y0 & z0 be the first ElementPath xyz position, then: - line #1: (x0, y0, z0, max_dist(xi, x0), y0, z0) - line #2: (x0, y0, z0, x0, max_dist(yi, y0), z0)

See Also

romiscan.tasks.colmap.use_calibrated_poses

Examples:

>>> from plantimager.controller.scanner.path import CalibrationPath
>>> from plantimager.controller.scanner.path import Circle
>>> circular_path = Circle(200, 200, 50, 0, 200, 9)
>>> n_points_line = 5
>>> calib_path = CalibrationPath(circular_path, n_points_line)
>>> calib_path
[x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 167.86, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 135.72, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 103.58, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 71.44, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 11.70, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 23.40, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 35.09, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 46.79, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 46.79, y = 71.44, z = 50.00, pan = 310.00, tilt = 0.00,
 x = 165.27, y = 3.04, z = 50.00, pan = 350.00, tilt = 0.00,
 x = 300.00, y = 26.79, z = 50.00, pan = 30.00, tilt = 0.00,
 x = 387.94, y = 131.60, z = 50.00, pan = 70.00, tilt = 0.00,
 x = 387.94, y = 268.40, z = 50.00, pan = 110.00, tilt = 0.00,
 x = 300.00, y = 373.21, z = 50.00, pan = 150.00, tilt = 0.00,
 x = 165.27, y = 396.96, z = 50.00, pan = 190.00, tilt = 0.00,
 x = 46.79, y = 328.56, z = 50.00, pan = 230.00, tilt = 0.00]

Parameters:

Name Type Description Default
path Path

A path to calibrate.

required
n_points_line int

The number of points per line.

required
Source code in plantimager/controller/scanner/path.py
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
def __init__(self, path, n_points_line):
    """
    Parameters
    ----------
    path : Path
        A path to calibrate.
    n_points_line : int
        The number of points per line.
    """
    super().__init__()
    el0 = path[0]
    # # TODO: find a better "algo" than this to select y-limit for line #1 and x-limit for line #2
    # el1 = path[len(path) // 4 - 1]
    # self.extend(Line(el0.x, el0.y, el0.z, el0.x, el1.y, el0.z, el0.pan, el0.tilt, n_points_line))
    # self.extend(Line(el0.x, el0.y, el0.z, el1.x, el0.y, el0.z, el0.pan, el0.tilt, n_points_line))
    # - Start with the path to calibrate
    self.extend(path)
    # x-axis line, from the first `ElementPath` xyz position to the most distant point from the origin along this x-axis
    x_max = path[np.argmax([pelt.x - el0.x for pelt in path])].x
    # y-axis line, from the first `ElementPath` xyz position to the most distant point from the origin along this y-axis
    y_max = path[np.argmax([pelt.y - el0.y for pelt in path])].y
    self.extend(Line(el0.x, el0.y, el0.z, x_max // 2, el0.y, el0.z, el0.pan, el0.tilt, n_points_line))
    self.extend(Line(el0.x, el0.y, el0.z, el0.x, y_max, el0.z, el0.pan, el0.tilt, n_points_line))

Circle Link

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

Bases: Path

Creates a circular path for the scanner.

Compute the x, y & pan PathElement values to create that circle.

Notes

The pan is computed to always face the center of the circle. If an iterable is given for tilt, performs more than one camera acquisition at same xyz position.

Examples:

>>> from plantimager.controller.scanner.path import Circle
>>> circular_path = Circle(200, 200, 50, 0, 200, 9)
>>> circular_path
[x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 46.79, y = 71.44, z = 50.00, pan = 310.00, tilt = 0.00,
 x = 165.27, y = 3.04, z = 50.00, pan = 350.00, tilt = 0.00,
 x = 300.00, y = 26.79, z = 50.00, pan = 30.00, tilt = 0.00,
 x = 387.94, y = 131.60, z = 50.00, pan = 70.00, tilt = 0.00,
 x = 387.94, y = 268.40, z = 50.00, pan = 110.00, tilt = 0.00,
 x = 300.00, y = 373.21, z = 50.00, pan = 150.00, tilt = 0.00,
 x = 165.27, y = 396.96, z = 50.00, pan = 190.00, tilt = 0.00,
 x = 46.79, y = 328.56, z = 50.00, pan = 230.00, tilt = 0.00]
>>> circular_path = Circle(200, 200, 50, (0, 10), 200, 9)
>>> circular_path[:4]  # print the first 4 position to show tilt change at given xyzp position
[x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 10.00,
 x = 46.79, y = 71.44, z = 50.00, pan = 310.00, tilt = 0.00,
 x = 46.79, y = 71.44, z = 50.00, pan = 310.00, tilt = 10.00]

Parameters:

Name Type Description Default
center_x length_mm

X-axis position, in millimeters, of the circle's center, relative to the origin.

required
center_y length_mm

Y-axis position, in millimeters, of the circle's center, relative to the origin.

required
z length_mm

Height at which to make the circle.

required
tilt deg or list(deg)

Camera tilt(s), in degrees, to use for this circle. If an iterable is given, performs more than one camera acquisition at same xyz position.

required
radius length_mm

Radius, in millimeters, of the circular path to create.

required
n_points int

Number of points (PathElement) used to generate the circular path.

required
Source code in plantimager/controller/scanner/path.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def __init__(self, center_x, center_y, z, tilt, radius, n_points):
    """
    Parameters
    ----------
    center_x : length_mm
        X-axis position, in millimeters, of the circle's center, relative to the origin.
    center_y : length_mm
        Y-axis position, in millimeters, of the circle's center, relative to the origin.
    z : length_mm
        Height at which to make the circle.
    tilt : deg or list(deg)
        Camera tilt(s), in degrees, to use for this circle.
        If an iterable is given, performs more than one camera acquisition at same xyz position.
    radius : length_mm
        Radius, in millimeters, of the circular path to create.
    n_points : int
        Number of points (``PathElement``) used to generate the circular path.
    """
    super().__init__()
    x, y, pan = circle(center_x, center_y, radius, n_points)

    self.center_x = center_x
    self.center_y = center_y
    self.radius = radius
    self.n_points = n_points

    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, exact_pose=False))

Cylinder Link

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

Bases: Path

Creates a z-axis aligned cylinder path for the scanner.

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

Notes

The pan is computed to always face the center of the circle. If an iterable is given for tilt, performs more than one camera acquisition at same xyz position.

Examples:

>>> from plantimager.controller.scanner.path import Cylinder
>>> n_points = 9
>>> cylinder_path = Cylinder(200, 200, (0, 50), 0, 200, n_points, 2)
>>> cylinder_path[:2]
[x = 0.00, y = 200.00, z = 0.00, pan = 270.00, tilt = 0.00,
 x = 46.79, y = 71.44, z = 0.00, pan = 310.00, tilt = 0.00]
>>> cylinder_path[n_points:2+n_points]
[x = 0.00, y = 200.00, z = 50.00, pan = 270.00, tilt = 0.00,
 x = 46.79, y = 71.44, z = 50.00, pan = 310.00, tilt = 0.00]

Parameters:

Name Type Description Default
center_x length_mm

X-axis position, in millimeters, of the circle's center, relative to the origin.

required
center_y length_mm

Y-axis position, in millimeters, of the circle's center, relative to the origin.

required
z_range (length_mm, length_mm)

Height range, in millimeters, at which to make the cylinder.

required
tilt deg or list of deg

Camera tilt(s), in degrees, to use for this circle. If an iterable is given, performs more than one camera acquisition at same xyz position.

required
radius length_mm

Radius of the circular path to create.

required
n_points int

Number of points (PathElement) used to generate the circular path.

required
n_circles int

Number of circular path to make within the cylinder, minimum value is 2.

2
Source code in plantimager/controller/scanner/path.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
def __init__(self, center_x, center_y, z_range, tilt, radius, n_points, n_circles=2):
    """
    Parameters
    ----------
    center_x : length_mm
        X-axis position, in millimeters, of the circle's center, relative to the origin.
    center_y : length_mm
        Y-axis position, in millimeters, of the circle's center, relative to the origin.
    z_range : (length_mm, length_mm)
        Height range, in millimeters, at which to make the cylinder.
    tilt : deg or list of deg
        Camera tilt(s), in degrees, to use for this circle.
        If an iterable is given, performs more than one camera acquisition at same xyz position.
    radius : length_mm
        Radius of the circular path to create.
    n_points : int
        Number of points (``PathElement``) used to generate the circular path.
    n_circles : int, optional
        Number of circular path to make within the cylinder, minimum value is 2.
    """
    super().__init__()
    try:
        assert n_circles >= 2
    except AssertionError:
        raise ValueError("You need a minimum of two circles to make a cylinder!")
    min_z, max_z = z_range
    for z_circle in range(min_z, max_z + 1, int((max_z - min_z) / (n_circles - 1))):
        self.extend(Circle(center_x, center_y, z_circle, tilt, radius, n_points))

Line Link

Line(x_0, y_0, z_0, x_1, y_1, z_1, pan, tilt, n_points)

Bases: Path

Creates a linear path for the scanner.

Examples:

>>> from plantimager.controller.scanner.path import Line
>>> n_points = 2
>>> linear_path = Line(0, 0, 0, 10, 10, 0, 180, 0, n_points)
>>> linear_path
[x = 0.00, y = 0.00, z = 0.00, pan = 180.00, tilt = 0.00,
 x = 10.00, y = 10.00, z = 0.00, pan = 180.00, tilt = 0.00]

Parameters:

Name Type Description Default
x_0 length_mm

Line starting position, in millimeters for the x-axis.

required
y_0 length_mm

Line starting position, in millimeters for the y-axis.

required
z_0 length_mm

Line starting position, in millimeters for the z-axis.

required
x_1 length_mm

Line ending position, in millimeters for the x-axis.

required
y_1 length_mm

Line ending position, in millimeters for the y-axis.

required
z_1 length_mm

Line ending position, in millimeters for the z-axis.

required
pan deg

Camera pan value, in degrees, to use for the linear path.

required
tilt deg or list(deg)

Camera tilt(s), in degrees, to use for this circle. If an iterable is given, performs more than one camera acquisition at same xyz position.

required
n_points int

Number of points used to create the linear path.

required
Source code in plantimager/controller/scanner/path.py
391
392
393
394
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
421
422
423
424
425
426
427
def __init__(self, x_0, y_0, z_0, x_1, y_1, z_1, pan, tilt, n_points):
    """
    Parameters
    ----------
    x_0 : length_mm
        Line starting position, in millimeters for the x-axis.
    y_0 : length_mm
        Line starting position, in millimeters for the y-axis.
    z_0 : length_mm
        Line starting position, in millimeters for the z-axis.
    x_1 : length_mm
        Line ending position, in millimeters for the x-axis.
    y_1 : length_mm
        Line ending position, in millimeters for the y-axis.
    z_1 : length_mm
        Line ending position, in millimeters for the z-axis.
    pan : deg
        Camera pan value, in degrees, to use for the linear path.
    tilt : deg or list(deg)
        Camera tilt(s), in degrees, to use for this circle.
        If an iterable is given, performs more than one camera acquisition at same xyz position.
    n_points : int
        Number of points used to create the linear path.
    """
    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 = line3d(x_0, y_0, z_0, x_1, y_1, z_1, 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=True))

Path Link

Path()

Bases: list

A path is a list of PathElement instances.

Examples:

>>> from plantimager.controller.scanner.path import PathElement
>>> elt = PathElement(0, 0, 0, 0, 0, True)
>>> from plantimager.controller.scanner.path import Path
>>> p = Path()
>>> type(p)
>>> p.append(elt)
>>> p2 = Path()
>>> p2.append(elt)
>>> p == p2
>>> p != p2
Source code in plantimager/controller/scanner/path.py
139
140
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.

Examples:

>>> from plantimager.controller.scanner.path import PathElement
>>> elt = PathElement(50, 250, 80, 270, 0, True)
>>> print(elt)

Parameters:

Name Type Description Default
x length_mm

Relative distance, in millimeters, to the origin along the x-axis.

None
y length_mm

Relative distance, in millimeters, to the origin along the y-axis.

None
z length_mm

Relative distance, in millimeters, to the origin along the z-axis.

None
pan deg

Relative rotation, in degrees, to the origin along the xy-plane.

None
tilt deg

Relative rotation, in degrees, to the origin along the xy-plane.

None
exact_pose bool

If True, the above parameter values are exact, else they are approximations.

True
Source code in plantimager/controller/scanner/path.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(self, x=None, y=None, z=None, pan=None, tilt=None, exact_pose=True):
    """
    Parameters
    ----------
    x : length_mm, optional
        Relative distance, in millimeters, to the origin along the x-axis.
    y : length_mm, optional
        Relative distance, in millimeters, to the origin along the y-axis.
    z : length_mm, optional
        Relative distance, in millimeters, to the origin along the z-axis.
    pan : deg, optional
        Relative rotation, in degrees, to the origin along the xy-plane.
    tilt : deg, optional
        Relative rotation, in degrees, to the origin along the xy-plane.
    exact_pose : bool, optional
        If ``True``, the above parameter values are exact, else they are approximations.

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

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.

Examples:

>>> from plantimager.controller.scanner.path import Pose
>>> p = Pose(50, 250, 80, 270, 0)
>>> print(p)

Pose constructor.

Parameters:

Name Type Description Default
x length_mm

Relative distance to the origin along the x-axis.

None
y length_mm

Relative distance to the origin along the y-axis.

None
z length_mm

Relative distance to the origin along the z-axis.

None
pan deg

Relative rotation to the origin along the xy-plane.

None
tilt deg

Relative rotation to the origin orthogonal to the xy-plane.

None
Source code in plantimager/controller/scanner/path.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(self, x=None, y=None, z=None, pan=None, tilt=None):
    """Pose constructor.

    Parameters
    ----------
    x : length_mm, optional
        Relative distance to the origin along the x-axis.
    y : length_mm, optional
        Relative distance to the origin along the y-axis.
    z : length_mm, optional
        Relative distance to the origin along the z-axis.
    pan : deg, optional
        Relative rotation to the origin along the xy-plane.
    tilt : deg, optional
        Relative rotation to the origin orthogonal to the xy-plane.
    """
    self.x = x
    self.y = y
    self.z = z
    self.pan = pan
    self.tilt = tilt

circle Link

circle(center_x, center_y, radius, n_points)

Create a 2D circle of N points with given center and radius.

Pan orientations are also computed to always face the center of the circle.

Parameters:

Name Type Description Default
center_x length_mm

Relative position of the circle center along the X-axis.

required
center_y length_mm

Relative position of the circle center along the Y-axis.

required
radius length_mm

Radius of the circle to create.

required
n_points int

Number of points used to create the circle.

required

Returns:

Type Description
list of length_mm

Sequence of x positions.

list of length_mm

Sequence of y positions.

list of deg

Sequence of pan orientations.

Examples:

>>> from plantimager.controller.scanner.path import circle
>>> circle(10, 10, 5, 3)
([5.0, 12.5, 12.500000000000002],
 [10.0, 5.669872981077806, 14.330127018922191],
 [270.0, 29.999999999999986, 149.99999999999997])
Source code in plantimager/controller/scanner/path.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def circle(center_x, center_y, radius, n_points):
    """Create a 2D circle of N points with given center and radius.

    Pan orientations are also computed to always face the center of the circle.

    Parameters
    ----------
    center_x : length_mm
        Relative position of the circle center along the X-axis.
    center_y : length_mm
        Relative position of the circle center along the Y-axis.
    radius : length_mm
        Radius of the circle to create.
    n_points : int
        Number of points used to create the circle.

    Returns
    -------
    list of length_mm
        Sequence of x positions.
    list of length_mm
        Sequence of y positions.
    list of deg
        Sequence of pan orientations.

    Examples
    --------
    >>> from plantimager.controller.scanner.path import circle
    >>> circle(10, 10, 5, 3)
    ([5.0, 12.5, 12.500000000000002],
     [10.0, 5.669872981077806, 14.330127018922191],
     [270.0, 29.999999999999986, 149.99999999999997])

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

    return x, y, p

line1d Link

line1d(start, stop, n_points)

Create a 1D line of N points between start and stop position (included).

Parameters:

Name Type Description Default
start length_mm

Line starting position, in millimeters.

required
stop length_mm

Line ending position, in millimeters.

required
n_points int

Number of points used to create the line of points.

required

Returns:

Type Description
list of length_mm

Sequence of 1D positions.

Examples:

>>> from plantimager.controller.scanner.path import line1d
>>> line1d(0,10,5)
[0.0, 2.5, 5.0, 7.5, 10.0]
Source code in plantimager/controller/scanner/path.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def line1d(start, stop, n_points):
    """Create a 1D line of N points between start and stop position (included).

    Parameters
    ----------
    start : length_mm
        Line starting position, in millimeters.
    stop : length_mm
        Line ending position, in millimeters.
    n_points : int
        Number of points used to create the line of points.

    Returns
    -------
    list of length_mm
        Sequence of 1D positions.

    Examples
    --------
    >>> from plantimager.controller.scanner.path import line1d
    >>> line1d(0,10,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)]

line3d Link

line3d(x_0, y_0, z_0, x_1, y_1, z_1, n_points)

Create a 3D line of N points between start and stop position (included).

Parameters:

Name Type Description Default
x_0 length_mm

Line starting position, in millimeters, for the x-axis.

required
y_0 length_mm

Line starting position, in millimeters, for the y-axis.

required
z_0 length_mm

Line starting position, in millimeters, for the z-axis.

required
x_1 length_mm

Line ending position, in millimeters, for the x-axis.

required
y_1 length_mm

Line ending position, in millimeters, for the y-axis.

required
z_1 length_mm

Line ending position, in millimeters, for the z-axis.

required
n_points int

Number of points used to create the linear path.

required

Returns:

Type Description
list of length_mm

Sequence of x positions.

list of length_mm

Sequence of y positions.

list of length_mm

Sequence of z positions.

Examples:

>>> from plantimager.controller.scanner.path import line3d
>>> line3d(0, 0, 0, 10, 10, 10, 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/controller/scanner/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
def line3d(x_0, y_0, z_0, x_1, y_1, z_1, n_points):
    """Create a 3D line of N points between start and stop position (included).

    Parameters
    ----------
    x_0 : length_mm
        Line starting position, in millimeters, for the x-axis.
    y_0 : length_mm
        Line starting position, in millimeters, for the y-axis.
    z_0 : length_mm
        Line starting position, in millimeters, for the z-axis.
    x_1 : length_mm
        Line ending position, in millimeters, for the x-axis.
    y_1 : length_mm
        Line ending position, in millimeters, for the y-axis.
    z_1 : length_mm
        Line ending position, in millimeters, for the z-axis.
    n_points : int
        Number of points used to create the linear path.

    Returns
    -------
    list of length_mm
        Sequence of x positions.
    list of length_mm
        Sequence of y positions.
    list of length_mm
        Sequence of z positions.

    Examples
    --------
    >>> from plantimager.controller.scanner.path import line3d
    >>> line3d(0, 0, 0, 10, 10, 10, 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 line1d(x_0, x_1, n_points), line1d(y_0, y_1, n_points), line1d(z_0, z_1, n_points)