Skip to content

scan

plantimager.tasks.scan Link

CalibrationScan Link

Bases: Scan

A task for running a scan, real or virtual, with a calibration path.

Module: romiscan.tasks.scan Colmap poses for subsequent scans. (TODO: see calibration documentation) Default upstream tasks: None

Parameters:

Name Type Description Default
metadata DictParameter

metadata for the scan

required
scanner DictParameter

scanner hardware configuration (TODO: see hardware documentation)

required
path DictParameter

scanner path configuration (TODO: see hardware documentation)

required
n_points_line IntParameter

Number of shots taken on the orthogonal calibration lines. Defaults to 10.

required
offset IntParameter

Offset to axis limits, in millimeters. Defaults to 5.

required

get_path Link

get_path()

Load the ScanPath module & get the configuration from the TOML config file.

Source code in plantimager/tasks/scan.py
170
171
172
173
174
def get_path(self) -> path.Path:
    """Load the ``ScanPath`` module & get the configuration from the TOML config file."""
    path_module = importlib.import_module(ScanPath().module)
    path = getattr(path_module, ScanPath().class_name)(**ScanPath().kwargs)
    return path

load_scanner Link

load_scanner()

Load the CNC, Gimbal & Camera modules and create a Scanner instance.

Source code in plantimager/tasks/scan.py
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
def load_scanner(self) -> Scanner:
    """Load the ``CNC``, ``Gimbal`` & ``Camera`` modules and create a ``Scanner`` instance."""
    scanner_config = self.scanner

    # - Load the CNC configuration from TOML:
    cnc_module = scanner_config["cnc"]["module"]
    logger.debug(f"CNC module: {cnc_module}")
    cnc_kwargs = scanner_config["cnc"]["kwargs"]
    param_str = [f"\n  - {k}={v}" for k, v in cnc_kwargs.items()]
    logger.debug(f"CNC parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    cnc_module = importlib.import_module(cnc_module)
    cnc = getattr(cnc_module, "CNC")(**cnc_kwargs)

    # - Load the Gimbal configuration from TOML:
    gimbal_module = scanner_config["gimbal"]["module"]
    gimbal_kwargs = scanner_config["gimbal"]["kwargs"]
    logger.debug(f"Gimbal module: {gimbal_module}")
    param_str = [f"\n  - {k}={v}" for k, v in gimbal_kwargs.items()]
    logger.debug(f"Gimbal parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    gimbal_module = importlib.import_module(gimbal_module)
    gimbal = getattr(gimbal_module, "Gimbal")(**gimbal_kwargs)

    # - Load the Camera configuration from TOML:
    camera_module = scanner_config["camera"]["module"]
    camera_kwargs = scanner_config["camera"]["kwargs"]
    logger.debug(f"Camera module: {camera_module}")
    param_str = [f"\n  - {k}={v}" for k, v in camera_kwargs.items()]
    logger.debug(f"Camera parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    camera_module = importlib.import_module(camera_module)
    camera = getattr(camera_module, "Camera")(**camera_kwargs)

    return Scanner(cnc, gimbal, camera)

output Link

output()

The output fileset associated to a Scan task is an 'images' dataset.

Source code in plantimager/tasks/scan.py
166
167
168
def output(self):
    """The output fileset associated to a ``Scan`` task is an 'images' dataset."""
    return FilesetTarget(DatabaseConfig().scan, "images")

requires Link

requires()

Nothing is required.

Source code in plantimager/tasks/scan.py
162
163
164
def requires(self):
    """Nothing is required."""
    return []

run Link

run()

Run the calibration scan.

Notes

Overrides the method from Scan.

Source code in plantimager/tasks/scan.py
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
def run(self):
    """Run the calibration scan.

    Notes
    -----
    Overrides the method from ``Scan``.
    """
    path = Scan().get_path()
    # Load the Scanner instance to get axes limits:
    scanner = Scan().load_scanner()
    # Get axes limits:
    x_lims = getattr(scanner.cnc, 'x_lims', None)
    if x_lims is not None:
        logger.info(f"Got X limits from scanner: {x_lims}")
        # Avoid true limits, as you might get stuck in some cases:
        x_lims = [x_lims[0] + self.offset, x_lims[1] - self.offset]
    y_lims = getattr(scanner.cnc, 'y_lims', None)
    if y_lims is not None:
        logger.info(f"Got Y limits from scanner: {y_lims}")
        # Avoid true limits, as you might get stuck in some cases:
        y_lims = [y_lims[0] + self.offset, y_lims[1] - self.offset]
    # Get the ScanPath module:
    path_module = importlib.import_module(ScanPath().module)
    # Create a CalibrationPath instance:
    calibration_path = getattr(path_module, "CalibrationPath")(path, self.n_points_line,
                                                               x_lims=x_lims, y_lims=y_lims)
    # Run the calibration procedure:
    Scan().run(path=calibration_path, scanner=scanner)

HdriFileset Link

Bases: FilesetExists

This task check the existence of a 'hdri' Fileset.

Attributes:

Name Type Description
upstream_task None

No upstream task is required.

scan_id Parameter

The id of the scan dataset that should contain the fileset.

fileset_id str

The name of the fileset that should exist.

IntrinsicCalibrationScan Link

Bases: Scan

A task to calibrate the intrinsic parameters of the camera.

Parameters:

Name Type Description Default
n_poses IntParameter

Number of calibration pattern pictures to take. Defaults to 20.

required
offset IntParameter

Offset to axis limits, in millimeters. Defaults to 5.

required
romi_run_task
required

get_path Link

get_path()

Load the ScanPath module & get the configuration from the TOML config file.

Source code in plantimager/tasks/scan.py
170
171
172
173
174
def get_path(self) -> path.Path:
    """Load the ``ScanPath`` module & get the configuration from the TOML config file."""
    path_module = importlib.import_module(ScanPath().module)
    path = getattr(path_module, ScanPath().class_name)(**ScanPath().kwargs)
    return path

load_scanner Link

load_scanner()

Load the CNC, Gimbal & Camera modules and create a Scanner instance.

Source code in plantimager/tasks/scan.py
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
def load_scanner(self) -> Scanner:
    """Load the ``CNC``, ``Gimbal`` & ``Camera`` modules and create a ``Scanner`` instance."""
    scanner_config = self.scanner

    # - Load the CNC configuration from TOML:
    cnc_module = scanner_config["cnc"]["module"]
    logger.debug(f"CNC module: {cnc_module}")
    cnc_kwargs = scanner_config["cnc"]["kwargs"]
    param_str = [f"\n  - {k}={v}" for k, v in cnc_kwargs.items()]
    logger.debug(f"CNC parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    cnc_module = importlib.import_module(cnc_module)
    cnc = getattr(cnc_module, "CNC")(**cnc_kwargs)

    # - Load the Gimbal configuration from TOML:
    gimbal_module = scanner_config["gimbal"]["module"]
    gimbal_kwargs = scanner_config["gimbal"]["kwargs"]
    logger.debug(f"Gimbal module: {gimbal_module}")
    param_str = [f"\n  - {k}={v}" for k, v in gimbal_kwargs.items()]
    logger.debug(f"Gimbal parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    gimbal_module = importlib.import_module(gimbal_module)
    gimbal = getattr(gimbal_module, "Gimbal")(**gimbal_kwargs)

    # - Load the Camera configuration from TOML:
    camera_module = scanner_config["camera"]["module"]
    camera_kwargs = scanner_config["camera"]["kwargs"]
    logger.debug(f"Camera module: {camera_module}")
    param_str = [f"\n  - {k}={v}" for k, v in camera_kwargs.items()]
    logger.debug(f"Camera parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    camera_module = importlib.import_module(camera_module)
    camera = getattr(camera_module, "Camera")(**camera_kwargs)

    return Scanner(cnc, gimbal, camera)

output Link

output()

The output fileset associated to a Scan task is an 'images' dataset.

Source code in plantimager/tasks/scan.py
166
167
168
def output(self):
    """The output fileset associated to a ``Scan`` task is an 'images' dataset."""
    return FilesetTarget(DatabaseConfig().scan, "images")

requires Link

requires()

Nothing is required.

Source code in plantimager/tasks/scan.py
162
163
164
def requires(self):
    """Nothing is required."""
    return []

ObjFileset Link

Bases: FilesetExists

This task check the existence of a 'data' Fileset.

Attributes:

Name Type Description
upstream_task None

No upstream task is required.

scan_id Parameter

The id of the scan dataset that should contain the fileset.

fileset_id str

The name of the fileset that should exist.

PaletteFileset Link

Bases: FilesetExists

This task check the existence of a 'palette' Fileset.

Attributes:

Name Type Description
upstream_task None

No upstream task is required.

scan_id Parameter

The id of the scan dataset that should contain the fileset.

fileset_id str

The name of the fileset that should exist.

Scan Link

Bases: RomiTask

A task for running a scan, real or virtual.

Module: romiscan.tasks.scan Default upstream tasks: None

Attributes:

Name Type Description
upstream_task None

No upstream task is required by a Scan task.

scan_id (Parameter, optional)

The scan id to use to create the FilesetTarget.

metadata (DictParameter, optional)

Metadata of the scan. Defaults to an empty dictionary.

scanner (DictParameter, optional)

Scanner configuration to use for this task. (TODO: see hardware documentation) Defaults to an empty dictionary.

get_path Link

get_path()

Load the ScanPath module & get the configuration from the TOML config file.

Source code in plantimager/tasks/scan.py
170
171
172
173
174
def get_path(self) -> path.Path:
    """Load the ``ScanPath`` module & get the configuration from the TOML config file."""
    path_module = importlib.import_module(ScanPath().module)
    path = getattr(path_module, ScanPath().class_name)(**ScanPath().kwargs)
    return path

load_scanner Link

load_scanner()

Load the CNC, Gimbal & Camera modules and create a Scanner instance.

Source code in plantimager/tasks/scan.py
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
def load_scanner(self) -> Scanner:
    """Load the ``CNC``, ``Gimbal`` & ``Camera`` modules and create a ``Scanner`` instance."""
    scanner_config = self.scanner

    # - Load the CNC configuration from TOML:
    cnc_module = scanner_config["cnc"]["module"]
    logger.debug(f"CNC module: {cnc_module}")
    cnc_kwargs = scanner_config["cnc"]["kwargs"]
    param_str = [f"\n  - {k}={v}" for k, v in cnc_kwargs.items()]
    logger.debug(f"CNC parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    cnc_module = importlib.import_module(cnc_module)
    cnc = getattr(cnc_module, "CNC")(**cnc_kwargs)

    # - Load the Gimbal configuration from TOML:
    gimbal_module = scanner_config["gimbal"]["module"]
    gimbal_kwargs = scanner_config["gimbal"]["kwargs"]
    logger.debug(f"Gimbal module: {gimbal_module}")
    param_str = [f"\n  - {k}={v}" for k, v in gimbal_kwargs.items()]
    logger.debug(f"Gimbal parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    gimbal_module = importlib.import_module(gimbal_module)
    gimbal = getattr(gimbal_module, "Gimbal")(**gimbal_kwargs)

    # - Load the Camera configuration from TOML:
    camera_module = scanner_config["camera"]["module"]
    camera_kwargs = scanner_config["camera"]["kwargs"]
    logger.debug(f"Camera module: {camera_module}")
    param_str = [f"\n  - {k}={v}" for k, v in camera_kwargs.items()]
    logger.debug(f"Camera parameters: {''.join(param_str)}")
    # - Import corresponding module to python:
    camera_module = importlib.import_module(camera_module)
    camera = getattr(camera_module, "Camera")(**camera_kwargs)

    return Scanner(cnc, gimbal, camera)

output Link

output()

The output fileset associated to a Scan task is an 'images' dataset.

Source code in plantimager/tasks/scan.py
166
167
168
def output(self):
    """The output fileset associated to a ``Scan`` task is an 'images' dataset."""
    return FilesetTarget(DatabaseConfig().scan, "images")

requires Link

requires()

Nothing is required.

Source code in plantimager/tasks/scan.py
162
163
164
def requires(self):
    """Nothing is required."""
    return []

run Link

run(path=None, scanner=None, extra_md=None)

Run a scan.

Parameters:

Name Type Description Default
path Path

If None (default), load the ScanPath module & get the configuration from the TOML config file. Else should be a plantimager.path.Path instance.

None
scanner Scanner or VirtualScanner

If None (default), load the configuration from the TOML config file with self.load_scanner. Else should be a Scanner or VirtualScanner instance.

None
extra_md dict

A dictionary of extra metadata to add to the 'images' fileset.

None
Source code in plantimager/tasks/scan.py
212
213
214
215
216
217
218
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def run(self, path=None, scanner=None, extra_md=None):
    """Run a scan.

    Parameters
    ----------
    path : plantimager.path.Path, optional
        If ``None`` (default), load the ``ScanPath`` module & get the configuration from the TOML config file.
        Else should be a ``plantimager.path.Path`` instance.
    scanner : plantimager.scanner.Scanner or plantimager.scanner.VirtualScanner, optional
        If ``None`` (default), load the configuration from the TOML config file with `self.load_scanner`.
        Else should be a ``Scanner`` or ``VirtualScanner`` instance.
    extra_md : dict, optional
        A dictionary of extra metadata to add to the 'images' fileset.

    """
    if path is None:
        path = self.get_path()
    if scanner is None:
        scanner = self.load_scanner()

    metadata = json.loads(luigi.DictParameter().serialize(self.metadata))

    # Get extra metadata from the `scanner` object:
    if isinstance(scanner, Scanner):
        # Import the axes limits from the ``plantimager.scanner.Scanner`` instance & add them to the metadata:
        if "hardware" not in metadata:
            logger.warning("Metadata entry 'hardware' is missing from the configuration file!")
            metadata["hardware"] = {}
        metadata["hardware"]['x_lims'] = getattr(scanner.cnc, "x_lims", None)
        metadata["hardware"]['y_lims'] = getattr(scanner.cnc, "y_lims", None)
        metadata["hardware"]['z_lims'] = getattr(scanner.cnc, "z_lims", None)
    elif isinstance(scanner, VirtualScanner):
        # Import software info from the ``plantimager.vscan.VirtualScanner`` instance & add them to the metadata:
        metadata["software"] = scanner.request_get_dict('info')
    else:
        pass

    # Set if the poses are exact or approximate depending on the type of `scanner`:
    if isinstance(scanner, Scanner):
        # In case of a Scanner, the poses are approximate:
        for p in path:
            p.exact = False
    elif isinstance(scanner, VirtualScanner):
        # In case of a VirtualScanner, the poses are exact:
        for p in path:
            p.exact = True
    else:
        logger.warning(f"Unknown type of scanner: {type(scanner)}!")
        logger.info(f"Could not change the `exact` attribute of the PathElements!")

    # Add the extra metadata to the metadata, if any:
    if extra_md is not None:
        metadata.update(extra_md)
    # Add the acquisition time to the metadata:
    from plantimager.utils import now
    metadata["acquisition_date"] = now()

    # Get (create) the output 'images' fileset:
    output_fileset = self.output().get()
    # Scan with the plant imager:
    scanner.scan(path, output_fileset)
    if isinstance(scanner, Scanner):
        # Go back close to home position:
        scanner.cnc.moveto(10., 10., 10.)

    # Write the metadata to the JSON associated to the 'images' fileset:
    output_fileset.set_metadata(metadata)
    # TODO: Remove (duplicate) metadata from 'images' fileset in later release!
    # Write the metadata to the JSON associated to the scan dataset:
    output_fileset.scan.set_metadata(metadata)
    # Add a description of the type of scan data with a "channel" entry in the 'images' fileset metadata:
    output_fileset.set_metadata("channels", scanner.channels())
    return

ScannerToCenter Link

Bases: RomiTask

A task to move the camera at the center of the path.

Attributes:

Name Type Description
upstream_task None

No upstream task is required.

scan_id (Parameter, optional)

The scan id to use to get or create the FilesetTarget.

SceneFileset Link

Bases: FilesetExists

This task check the existence of a 'scenes' Fileset.

Attributes:

Name Type Description
upstream_task None

No upstream task is required.

scan_id Parameter

The id of the scan dataset that should contain the fileset.

fileset_id str

The name of the fileset that should exist.

VirtualScan Link

Bases: Scan

Task to create scans of virtual plants using Blender.

Attributes:

Name Type Description
upstream_task None

No upstream task is required by a VirtualScan task.

scan_id (Parameter, optional)

The scan id to use to create the FilesetTarget.

metadata (DictParameter, optional)

Metadata of the scan. Defaults to an empty dictionary.

scanner (DictParameter, optional)

VirtualScanner configuration to use for this task. Defaults to an empty dictionary.

load_scene (BoolParameter, optional)

Whether to load the scene file. Defaults to False.

scene_file_id (Parameter, optional)

The name of the scene file to load, if any. Defaults to an empty string.

use_palette (BoolParameter, optional)

Whether to use a color palette to rendre the virtual plant. Defaults to False.

use_hdri (BoolParameter, optional)

Whether to use an HDRI file for the background. Defaults to False.

obj_fileset (TaskParameter, optional)

The Fileset that contains the virtual plant OBJ file to render and capture. Defaults to VirtualPlant.

hdri_fileset (TaskParameter, optional)

The Fileset that contains the HDRI files to use as background. Defaults to HdriFileset.

scene_fileset (TaskParameter, optional)

The Fileset that contain the scenes to use. Defaults to SceneFileset.

palette_fileset (TaskParameter, optional)

The Fileset that contains the color palette to use to render the virtual plant. Defaults to PaletteFileset.

render_ground_truth (BoolParameter, optional)

If True, create the mask image for defined ground truth classes in virtual plant model. Defaults to False.

colorize (bool, optional)

Whether the virtual plant should be colorized in Blender. Defaults to True.

get_path Link

get_path()

Load the ScanPath module & get the configuration from the TOML config file.

Source code in plantimager/tasks/scan.py
170
171
172
173
174
def get_path(self) -> path.Path:
    """Load the ``ScanPath`` module & get the configuration from the TOML config file."""
    path_module = importlib.import_module(ScanPath().module)
    path = getattr(path_module, ScanPath().class_name)(**ScanPath().kwargs)
    return path

load_scanner Link

load_scanner()

Create the virtual scanner configuration and defines the files it.

Returns:

Type Description
VirtualScanner

The configured virtual scanner.

Notes

Overrides the method from Scan.

Source code in plantimager/tasks/scan.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
def load_scanner(self):
    """Create the virtual scanner configuration and defines the files it.

    Returns
    -------
    plantimager.vscan.VirtualScanner
        The configured virtual scanner.

    Notes
    -----
    Overrides the method from ``Scan``.
    """
    # - Create the virtual scanner configuration (from TOML section 'VirtualScan.scanner'):
    scanner_config = json.loads(luigi.DictParameter().serialize(self.scanner))

    # - Defines the virtual plant files:
    # Get the `Fileset` containing the OBJ & MTL files:
    obj_fileset = self.input()["object"].get()
    # Get the OBJ `File`:
    while True:
        # Randomly pick one of the files from the fileset...
        obj_file = random.choice(obj_fileset.get_files())
        if "obj" in obj_file.filename:
            # ...stop when "obj" is found in the filename
            break
    # Get the MTL `File`:
    try:
        mtl_file = obj_fileset.get_file(obj_file.id + "_mtl")
    except:
        mtl_file = None

    # - Defines the palette `File`, if requested:
    palette_file = None
    if self.use_palette:
        # Randomly choose among existing palette files:
        palette_fileset = self.input()["palette"].get()
        palette_file = random.choice(palette_fileset.get_files())

    # - Defines the hdri `File`, if requested:
    hdri_file = None
    if self.use_hdri:
        # Randomly choose among existing hdri files:
        hdri_fileset = self.input()["hdri"].get()
        hdri_file = random.choice(hdri_fileset.get_files())

    # - Defines the scene `File`, if requested:
    if self.load_scene:
        scene_fileset = self.input()["scene"].get()
        # Duplicate all files in a temporary directory: (TODO: why?!)
        self.tmpdir = io.tmpdir_from_fileset(scene_fileset)
        # Use the selected `scene_file_id` to get the `File`:
        scene_file = scene_fileset.get_file(self.scene_file_id).filename
        # Add its path to the virtual scanner config to create:
        scanner_config["scene"] = os.path.join(self.tmpdir.name, scene_file)

    # - Defines the list of classes to create ground-truth images for, if requested:
    if self.render_ground_truth:
        # Add the list of classes names to the virtual scanner config to create:
        scanner_config["classes"] = list(VirtualPlantConfig().classes.values())

    # - Instantiate the `VirtualScanner` using the config:
    vscan = VirtualScanner(**scanner_config)
    # - Load the defined OBJ, MTL and palette files to Blender:
    vscan.load_object(obj_file, mtl=mtl_file, palette=palette_file, colorize=self.colorize)
    # - Load the HDRI files to Blender, if requested:
    if self.use_hdri:
        vscan.load_background(hdri_file)
    # Get the bounding-box from Blender & add it to the output fileset metadata:
    bbox = vscan.get_bounding_box()
    self.output().get().set_metadata("bounding_box", bbox)

    return vscan

output Link

output()

The output fileset associated to a Scan task is an 'images' dataset.

Source code in plantimager/tasks/scan.py
166
167
168
def output(self):
    """The output fileset associated to a ``Scan`` task is an 'images' dataset."""
    return FilesetTarget(DatabaseConfig().scan, "images")

requires Link

requires()

Defines the VirtualScan task requirements.

Always require a Fileset that contains a virtual plant. It can be an ObjFileset or the output of the VirtualPlant task. If the use of an HDRI is required, check the HdriFileset exists. If the use of a color palette is required, check the PaletteFileset exists. If the use of a scene is required, check the SceneFileset exists.

Notes

Overrides the method from Scan.

Source code in plantimager/tasks/scan.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
def requires(self):
    """Defines the ``VirtualScan`` task requirements.

    Always require a `Fileset` that contains a virtual plant.
    It can be an ``ObjFileset`` or the output of the ``VirtualPlant`` task.
    If the use of an HDRI is required, check the ``HdriFileset`` exists.
    If the use of a color palette is required, check the ``PaletteFileset`` exists.
    If the use of a scene is required, check the ``SceneFileset`` exists.

    Notes
    -----
    Overrides the method from ``Scan``.
    """
    requires = {
        "object": self.obj_fileset()
    }
    if self.use_hdri:
        requires["hdri"] = self.hdri_fileset()
    if self.use_palette:
        requires["palette"] = self.palette_fileset()
    if self.load_scene:
        requires["scene"] = self.scene_fileset()
    return requires

run Link

run(path=None, scanner=None, extra_md=None)

Run a scan.

Parameters:

Name Type Description Default
path Path

If None (default), load the ScanPath module & get the configuration from the TOML config file. Else should be a plantimager.path.Path instance.

None
scanner Scanner or VirtualScanner

If None (default), load the configuration from the TOML config file with self.load_scanner. Else should be a Scanner or VirtualScanner instance.

None
extra_md dict

A dictionary of extra metadata to add to the 'images' fileset.

None
Source code in plantimager/tasks/scan.py
212
213
214
215
216
217
218
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def run(self, path=None, scanner=None, extra_md=None):
    """Run a scan.

    Parameters
    ----------
    path : plantimager.path.Path, optional
        If ``None`` (default), load the ``ScanPath`` module & get the configuration from the TOML config file.
        Else should be a ``plantimager.path.Path`` instance.
    scanner : plantimager.scanner.Scanner or plantimager.scanner.VirtualScanner, optional
        If ``None`` (default), load the configuration from the TOML config file with `self.load_scanner`.
        Else should be a ``Scanner`` or ``VirtualScanner`` instance.
    extra_md : dict, optional
        A dictionary of extra metadata to add to the 'images' fileset.

    """
    if path is None:
        path = self.get_path()
    if scanner is None:
        scanner = self.load_scanner()

    metadata = json.loads(luigi.DictParameter().serialize(self.metadata))

    # Get extra metadata from the `scanner` object:
    if isinstance(scanner, Scanner):
        # Import the axes limits from the ``plantimager.scanner.Scanner`` instance & add them to the metadata:
        if "hardware" not in metadata:
            logger.warning("Metadata entry 'hardware' is missing from the configuration file!")
            metadata["hardware"] = {}
        metadata["hardware"]['x_lims'] = getattr(scanner.cnc, "x_lims", None)
        metadata["hardware"]['y_lims'] = getattr(scanner.cnc, "y_lims", None)
        metadata["hardware"]['z_lims'] = getattr(scanner.cnc, "z_lims", None)
    elif isinstance(scanner, VirtualScanner):
        # Import software info from the ``plantimager.vscan.VirtualScanner`` instance & add them to the metadata:
        metadata["software"] = scanner.request_get_dict('info')
    else:
        pass

    # Set if the poses are exact or approximate depending on the type of `scanner`:
    if isinstance(scanner, Scanner):
        # In case of a Scanner, the poses are approximate:
        for p in path:
            p.exact = False
    elif isinstance(scanner, VirtualScanner):
        # In case of a VirtualScanner, the poses are exact:
        for p in path:
            p.exact = True
    else:
        logger.warning(f"Unknown type of scanner: {type(scanner)}!")
        logger.info(f"Could not change the `exact` attribute of the PathElements!")

    # Add the extra metadata to the metadata, if any:
    if extra_md is not None:
        metadata.update(extra_md)
    # Add the acquisition time to the metadata:
    from plantimager.utils import now
    metadata["acquisition_date"] = now()

    # Get (create) the output 'images' fileset:
    output_fileset = self.output().get()
    # Scan with the plant imager:
    scanner.scan(path, output_fileset)
    if isinstance(scanner, Scanner):
        # Go back close to home position:
        scanner.cnc.moveto(10., 10., 10.)

    # Write the metadata to the JSON associated to the 'images' fileset:
    output_fileset.set_metadata(metadata)
    # TODO: Remove (duplicate) metadata from 'images' fileset in later release!
    # Write the metadata to the JSON associated to the scan dataset:
    output_fileset.scan.set_metadata(metadata)
    # Add a description of the type of scan data with a "channel" entry in the 'images' fileset metadata:
    output_fileset.set_metadata("channels", scanner.channels())
    return