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
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:
        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