Skip to content

scanner

plantimager.scanner Link

Scanner Link

Scanner(cnc, gimbal, camera, waiting_time=1.0)

Bases: AbstractScanner

The Scanner class to combine control of CNC, Gimbal & Camera.

Attributes:

Name Type Description
cnc AbstractCNC

A class dedicated to CNC control.

gimbal AbstractGimbal

A class dedicated to Gimbal control.

camera AbstractCamera

A class dedicated to Camera control.

waiting_time (float, optional)

The time, in seconds, to wait for stabilization after setting position, default is 1.

See Also

plantimager.hal.AbstractScanner

Examples:

>>> from plantimager.scanner import Scanner
>>> # Example #1 - A dummy scanner
>>> from plantimager.dummy import CNC
>>> from plantimager.dummy import Gimbal
>>> from plantimager.dummy import Camera
>>> cnc = CNC()
>>> gimbal = Gimbal()
>>> camera = Camera()
>>> dummy_scanner = Scanner(cnc, gimbal, camera)
>>> # Example #2 - A scanner with CNC & Gimbal connected via USB and an URL Camera
>>> from plantimager.grbl import CNC
>>> from plantimager.blgimbal import Gimbal
>>> from plantimager.urlcam import Camera
>>> cnc = CNC("/dev/ttyACM0", x_lims=[0, 800], y_lims=[0, 800], z_lims=[0, 100])
>>> gimbal = Gimbal("/dev/ttyACM1", has_tilt=False, invert_rotation=True)
>>> camera = Camera("http://192.168.0.1:8080")
>>> scanner = Scanner(cnc, gimbal, camera)

Scanner constructor.

Parameters:

Name Type Description Default
cnc AbstractCNC

A class dedicated to CNC control.

required
gimbal

A class dedicated to Gimbal control.

required
camera AbstractCamera

A class dedicated to Camera control.

required
waiting_time

The time, in seconds, to wait for stabilization after setting position, default is 1.

1.0
Source code in plantimager/scanner.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def __init__(self, cnc, gimbal, camera, waiting_time=1.):
    """Scanner constructor.

    Parameters
    ----------
    cnc : plantimager.hal.AbstractCNC
        A class dedicated to CNC control.
    gimbal: plantimager.hal.AbstractGimbal
        A class dedicated to Gimbal control.
    camera : plantimager.hal.AbstractCamera
        A class dedicated to Camera control.
    waiting_time: float, optional
        The time, in seconds, to wait for stabilization after setting position, default is 1.
    """
    super().__init__()
    self.cnc = cnc
    self.gimbal = gimbal
    self.camera = camera
    self.waiting_time = waiting_time  # time to wait for stabilization after setting position

channels Link

channels()

Channel names associated to grabbed data with the grab method.

Returns:

Type Description
List of str

The list of channel names.

See Also

plantimager.hal.AbstractCamera

Source code in plantimager/scanner.py
136
137
138
139
140
141
142
143
144
145
146
147
148
def channels(self):
    """Channel names associated to grabbed data with the grab method.

    Returns
    -------
    List of str
        The list of channel names.

    See Also
    --------
    plantimager.hal.AbstractCamera
    """
    return self.camera.channels()

get_position Link

get_position()

Get the current position of the scanner as a 5D Pose.

Source code in plantimager/scanner.py
92
93
94
95
96
def get_position(self) -> Pose:
    """Get the current position of the scanner as a 5D Pose."""
    x, y, z = self.cnc.get_position()
    pan, tilt = self.gimbal.get_position()
    return Pose(x, y, z, pan, tilt)

get_target_pose Link

get_target_pose(elt)

Get the target pose from a given path element (singleton).

Parameters:

Name Type Description Default
elt PathElement

The path element to reach.

required

Returns:

Type Description
Pose

The target pose to reach.

Notes

If a Pose attribute is missing from the given path element, we use the value from the previous pose.

Source code in plantimager/hal.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def get_target_pose(self, elt):
    """Get the target pose from a given path element (singleton).

    Parameters
    ----------
    elt : plantimager.path.PathElement
        The path element to reach.

    Returns
    -------
    plantimager.path.Pose
        The target pose to reach.

    Notes
    -----
    If a ``Pose`` attribute is missing from the given path element, we use the value from the previous pose.
    """
    pos = self.get_position()
    target_pose = Pose()
    for attr in pos.attributes():
        if getattr(elt, attr) is None:
            setattr(target_pose, attr, getattr(pos, attr))
        else:
            setattr(target_pose, attr, getattr(elt, attr))
    return target_pose

grab Link

grab(idx, metadata=None)

Grab data with an id and metadata.

Parameters:

Name Type Description Default
idx int

Id of the data DataItem to create.

required
metadata dict

Dictionary of metadata associated to the camera data.

None

Returns:

Type Description
DataItem

The image data.

See Also

plantimager.hal.AbstractCamera plantimager.hal.AbstractScanner

Source code in plantimager/scanner.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def grab(self, idx, metadata=None):
    """Grab data with an id and metadata.

    Parameters
    ----------
    idx : int
        Id of the data `DataItem` to create.
    metadata : dict, optional
        Dictionary of metadata associated to the camera data.

    Returns
    -------
    plantimager.hal.DataItem
        The image data.

    See Also
    --------
    plantimager.hal.AbstractCamera
    plantimager.hal.AbstractScanner
    """
    return self.camera.grab(idx, metadata)

inc_count Link

inc_count()

Incremental counter used to return a picture index for the grab method.

Source code in plantimager/hal.py
213
214
215
216
217
def inc_count(self) -> int:
    """Incremental counter used to return a picture index for the ``grab`` method."""
    x = self.scan_count
    self.scan_count += 1
    return x

scan Link

scan(path, fileset)

Performs a scan, that is a series of movements and image acquisitions.

Parameters:

Name Type Description Default
path Path

The path to follows to acquire image.

required
fileset Fileset

The output fileset used to save the image.

required
Source code in plantimager/hal.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def scan(self, path, fileset):
    """Performs a scan, that is a series of movements and image acquisitions.

    Parameters
    ----------
    path : plantimager.path.Path
        The path to follows to acquire image.
    fileset : plantdb.FSDB.Fileset
        The output fileset used to save the image.
    """
    for x in tqdm(path, unit='pose'):
        pose = self.get_target_pose(x)
        data_item = self.scan_at(pose, self.exact_pose)
        for c in self.channels():
            f = fileset.create_file(data_item.channels[c].format_id())
            data = data_item.channels[c].data
            if "float" in data.dtype.name:
                data = np.array(data * 255).astype("uint8")
            io.write_image(f, data, ext=self.ext)
            if data_item.metadata is not None:
                f.set_metadata(data_item.metadata)
            f.set_metadata("shot_id", "%06i" % data_item.idx)
            f.set_metadata("channel", c)
    return

scan_at Link

scan_at(pose, exact_pose=True, metadata=None)

Move to a given position and take a picture.

Parameters:

Name Type Description Default
pose Pose

The position of the camera to take the picture.

required
exact_pose bool

If True (default), save the given pose under a "pose" entry in metadata. Else, save it as an "approximate_pose" entry in metadata.

True
metadata dict

The dictionary of metadata to associate to this picture.

None

Returns:

Type Description
DataItem

The picture data & metadata.

Source code in plantimager/hal.py
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 scan_at(self, pose, exact_pose=True, metadata=None):
    """Move to a given position and take a picture.

    Parameters
    ----------
    pose : plantimager.path.Pose
        The position of the camera to take the picture.
    exact_pose : bool, optional
        If ``True`` (default), save the given `pose` under a "pose" entry in metadata.
        Else, save it as an "approximate_pose" entry in metadata.
    metadata : dict, optional
        The dictionary of metadata to associate to this picture.

    Returns
    -------
    plantimager.hal.DataItem
        The picture data & metadata.
    """
    logger.debug(f"scanning at: {pose}")
    if metadata is None:
        metadata = {}
    if exact_pose:
        metadata = {**metadata, "pose": [pose.x, pose.y, pose.z, pose.pan, pose.tilt]}
    else:
        # PlantImager move downward with positive z values
        metadata = {**metadata, "approximate_pose": [pose.x, pose.y, -pose.z, pose.pan, pose.tilt]}
    logger.debug(f"with metadata: {metadata}")
    self.set_position(pose)
    return self.grab(self.inc_count(), metadata=metadata)

set_position Link

set_position(pose)

Set the position of the scanner from a 5D Pose.

The stabilization waiting time is done at the end of this step.

Source code in plantimager/scanner.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def set_position(self, pose: Pose) -> None:
    """Set the position of the scanner from a 5D Pose.

    The _stabilization waiting time_ is done at the end of this step.
    """
    if self.cnc.async_enabled():
        self.cnc.moveto_async(pose.x, pose.y, pose.z)
        self.gimbal.moveto_async(pose.pan, pose.tilt)
        self.cnc.wait()
        self.gimbal.wait()
    else:
        self.cnc.moveto(pose.x, pose.y, pose.z)
        self.gimbal.moveto(pose.pan, pose.tilt)
    time.sleep(self.waiting_time)
    return