Coverage for src/pytribeam/factory.py: 77%
677 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-09 18:27 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-09 18:27 +0000
1#!/usr/bin/python3
2"""Factory and validation functions for `pytribeam` settings objects.
4This module converts raw configuration dictionaries and live microscope state
5into structured `pytribeam.types` objects. It is the main construction layer used
6by the workflow to validate YAML input, read current hardware settings, enforce
7limits, and create typed settings for imaging, FIB, laser, EBSD, EDS, custom,
8stage, beam, detector, and scan operations.
10Most users should not need to call the low-level validation helpers directly.
11Workflow code typically enters this module through `general`, `step`, or one of
12the `active_*` functions.
14## Typical usage
16Create experiment-wide general settings from a parsed YAML dictionary:
18```python
19import pytribeam.factory as factory
21general_settings = factory.general(
22 general_db=general_db,
23 yml_format=yml_format,
24)
25```
27Create a typed workflow step from a YAML step dictionary:
29```python
30step = factory.step(
31 microscope=microscope,
32 step_name=step_name,
33 step_settings=step_settings,
34 general_settings=general_settings,
35 yml_format=yml_format,
36)
37```
39Read the current microscope state into `pytribeam` settings objects:
41```python
42beam = factory.active_beam_with_settings(microscope)
43image_settings = factory.active_image_settings(microscope)
44stage_position = factory.active_stage_position_settings(microscope)
45```
47## Main entry points
49- `general`: validate and create `tbt.GeneralSettings`.
50- `step`: validate and create a `tbt.Step` for the configured step type.
51- `active_image_settings`: read the current microscope imaging state.
52- `active_beam_with_settings`: read the active beam and beam settings.
53- `active_detector_settings`: read the active detector settings.
54- `active_scan_settings`: read the active scan settings.
55- `active_stage_position_settings`: read the current stage position in user
56 units.
57- `active_laser_state`: read the current laser state.
58- `stage_limits`, `beam_limits`, and `scan_limits`: read hardware limits from
59 the active microscope connection.
61## Configuration workflow
63The workflow module uses this factory layer to transform parsed YAML data into
64validated runtime objects:
66```text
67YAML file
68 -> raw dictionaries
69 -> validation helpers
70 -> typed settings objects
71 -> workflow execution
72```
74For example, image, FIB, laser, EBSD, EDS, and custom steps are all constructed
75from dictionaries using step-type-specific factory functions. These functions
76validate required keys, convert strings to enums, check numeric limits, and
77return the appropriate `tbt.*Settings` object.
79## Validation behavior
81Validation helpers check user configuration against package schema information,
82supported enum values, microscope hardware limits, and workflow constraints.
83Invalid settings generally raise `ValueError`, `TypeError`, `KeyError`, or
84`NotImplementedError` with context about the step or setting that failed.
86## Active-state helpers
88Functions beginning with `active_` read the current microscope or laser state and
89return a corresponding `pytribeam.types` object. These are useful for logging,
90GUI display, default-setting generation, and validating that requested hardware
91changes were applied successfully.
93## Units
95Factory functions preserve the package-wide unit conventions used by
96`pytribeam.types`. User-facing values generally use explicit suffixes such as
97`_mm`, `_um`, `_deg`, `_kv`, `_na`, and `_us`, while values read from or written
98to hardware APIs may be converted internally as needed.
100> **Warning**
101>
102> Many functions in this module query live microscope or laser state. Ensure that
103> the required hardware connections and external APIs are available before using
104> active-state or hardware-limit factory functions.
106<hr style="height: 12px; background-color: #333; border: none;">
107"""
109__all__ = [
110 "active_fib_applications",
111 "active_beam_with_settings",
112 "active_detector_settings",
113 "active_image_settings",
114 "active_imaging_device",
115 "active_scan_settings",
116 "active_stage_position_settings",
117 "active_laser_state",
118 "active_laser_settings",
119 "available_detector_types",
120 "available_detector_modes",
121 "beam_object_type",
122 "stage_limits",
123 "beam_limits",
124 "scan_limits",
125 "general",
126 "image",
127 "fib",
128 "laser",
129 "ebsd",
130 "eds",
131 "custom",
132 "step",
133 "string_to_res",
134 "valid_string_resolution",
135]
137## python standard libraries
138from pathlib import Path
139from typing import List, Union
140import warnings
141from functools import singledispatch
142import math
145# 3rd party libraries
146from schema import And, Or, Schema
148# Local
149import pytribeam.insertable_devices as devices
150import pytribeam.image as img
151import pytribeam.utilities as ut
152import pytribeam.stage as stage
153from pytribeam.constants import Conversions, Constants
154from pytribeam.utilities import application_files
156try:
157 import pytribeam.laser as fs_laser
158except:
159 pass
160import pytribeam.types as tbt
163def active_fib_applications(
164 microscope: tbt.Microscope,
165) -> list:
166 """
167 Retrieve a list of all active FIB (Focused Ion Beam) patterning application files from the microscope.
169 ## Parameters
171 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
173 ## Returns
175 - `list`: A list of active FIB patterning application files.
177 """
178 return application_files(microscope)
181def active_beam_with_settings(
182 microscope: tbt.Microscope,
183) -> tbt.Beam:
184 """
185 Retrieve the current active beam and its settings from the microscope to create a beam object.
187 This function grabs the current beam and its settings on the microscope to make a beam object. These settings fully depend on the currently active beam as determined by xTUI. Tolerance values for voltage and current are auto-populated as a ratio of current values predetermined in the `Constants` class.
189 ## Parameters
191 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active beam and its settings.
193 ## Returns
195 - `tbt.Beam`: The active beam object with its settings.
196 """
197 selected_beam = active_imaging_device(microscope=microscope)
198 beam = ut.beam_type(selected_beam, microscope)
200 voltage_kv = round(beam.high_voltage.value * Conversions.V_TO_KV, 6)
201 voltage_tol_kv = round(voltage_kv * Constants.voltage_tol_ratio, 6)
203 current_na = round(beam.beam_current.value * Conversions.A_TO_NA, 6)
204 current_tol_na = round(current_na * Constants.current_tol_ratio, 6)
206 hfw_mm = round(beam.horizontal_field_width.value * Conversions.M_TO_MM, 6)
208 working_dist_mm = round(beam.working_distance.value * Conversions.M_TO_MM, 6)
210 angular_correction = getattr(beam, "angular_correction", None)
211 if angular_correction is None: 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true
212 dynamic_focus = None
213 tilt_correction = None
214 else:
215 dynamic_focus = bool(angular_correction.dynamic_focus.is_on)
216 tilt_correction = bool(angular_correction.tilt_correction.is_on)
218 active_settings = tbt.BeamSettings(
219 voltage_kv=voltage_kv,
220 current_na=current_na,
221 hfw_mm=hfw_mm,
222 working_dist_mm=working_dist_mm,
223 voltage_tol_kv=voltage_tol_kv,
224 current_tol_na=current_tol_na,
225 dynamic_focus=dynamic_focus,
226 tilt_correction=tilt_correction,
227 )
229 return type(selected_beam)(settings=active_settings)
232def active_detector_settings(
233 microscope: tbt.Microscope,
234) -> tbt.Detector:
235 """
236 Retrieve the current active detector settings from the microscope to create a detector object.
238 This function grabs the current detector settings on the microscope to make a detector object. These settings fully depend on the currently active detector as determined by xTUI.
240 ## Parameters
242 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active detector settings.
244 ## Returns
246 - `tbt.Detector`: The active detector object with its settings.
247 """
249 detector_type = microscope.detector.type.value
250 detector_mode = microscope.detector.mode.value
251 brightness = microscope.detector.brightness.value
252 contrast = microscope.detector.contrast.value
253 auto_cb_settings = tbt.ScanArea(
254 left=None,
255 top=None,
256 width=None,
257 height=None,
258 )
259 custom_settings = None
261 active_detector = tbt.Detector(
262 type=detector_type,
263 mode=detector_mode,
264 brightness=brightness,
265 contrast=contrast,
266 auto_cb_settings=auto_cb_settings,
267 custom_settings=custom_settings,
268 )
270 return active_detector
273def active_image_settings(microscope: tbt.Microscope) -> tbt.ImageSettings:
274 """
275 Retrieve the current active image settings from the microscope to create an image settings object.
277 This function grabs the current beam, detector, and scan settings on the microscope to make an image settings object. The bit depth is set to the default color depth defined in the `Constants` class.
279 ## Parameters
281 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active image settings.
283 ## Returns
285 - `tbt.ImageSettings`: The active image settings object.
286 """
287 beam = active_beam_with_settings(microscope=microscope)
288 detector = active_detector_settings(microscope=microscope)
289 scan = active_scan_settings(microscope=microscope)
290 bit_depth = Constants.default_color_depth
292 active_image_settings = tbt.ImageSettings(
293 microscope=microscope,
294 beam=beam,
295 detector=detector,
296 scan=scan,
297 bit_depth=bit_depth,
298 )
300 return active_image_settings
303def active_imaging_device(microscope: tbt.Microscope) -> tbt.Beam:
304 """
305 Determine the active imaging device and return the corresponding internal beam type object with null beam settings.
307 This function identifies the currently active imaging device on the microscope and returns the appropriate beam type object (electron or ion) with null beam settings.
309 ## Parameters
311 - `microscope` (`tbt.Microscope`): The microscope object from which to determine the active imaging device.
313 ## Returns
315 - `tbt.Beam`: The active beam object with null beam settings.
317 ## Raises
319 - `ValueError`: If the currently selected device is neither an electron beam nor an ion beam.
320 """
321 curr_device = tbt.Device(microscope.imaging.get_active_device())
322 if curr_device == tbt.Device.ELECTRON_BEAM:
323 selected_beam = beam_object_type(type=tbt.BeamType.ELECTRON)(
324 settings=tbt.BeamSettings()
325 )
326 elif curr_device == tbt.Device.ION_BEAM: 326 ↛ 331line 326 didn't jump to line 331 because the condition on line 326 was always true
327 selected_beam = beam_object_type(type=tbt.BeamType.ION)(
328 settings=tbt.BeamSettings()
329 )
330 else:
331 raise ValueError(
332 f"Currently selected device {curr_device}, make sure a quadrant in xTUI with either an Electron beam or Ion beam active is selected."
333 )
334 return selected_beam
337def active_scan_settings(
338 microscope: tbt.Microscope,
339) -> tbt.Scan:
340 """
341 Retrieve the current active scan settings from the microscope to create a scan object.
343 This function grabs the current scan settings on the microscope to make a scan object. These settings fully depend on the currently active scan settings as determined by xTUI.
345 ## Parameters
347 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active scan settings.
349 ## Returns
351 - `tbt.Scan`: The active scan object with its settings.
352 """
353 selected_beam = active_imaging_device(microscope=microscope)
354 beam = ut.beam_type(selected_beam, microscope)
356 rotation_deg = beam.scanning.rotation.value * Conversions.RAD_TO_DEG
357 dwell_time_us = beam.scanning.dwell_time.value * Conversions.S_TO_US
358 current_res = beam.scanning.resolution.value
359 res = string_to_res(current_res)
360 resolution = tbt.PresetResolution(res)
362 active_scan = tbt.Scan(
363 rotation_deg=rotation_deg,
364 dwell_time_us=dwell_time_us,
365 resolution=resolution,
366 # mode=mode,
367 )
369 return active_scan
372def active_stage_position_settings(microscope: tbt.Microscope) -> tbt.StagePositionUser:
373 """
374 Retrieve the current stage position in the raw coordinate system and user units [mm, deg].
376 This function sets the stage coordinate system to RAW, retrieves the current stage position in encoder units (meters and radians), converts it to user units (millimeters and degrees), and ensures the r-axis is within the axis limit.
378 ## Parameters
380 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the current stage position.
382 ## Returns
384 - `tbt.StagePositionUser`: The current stage position in user units [mm, deg].
385 """
386 stage.coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
387 # encoder positions (pos) are in meters and radians
388 direct_encoder_pos = microscope.specimen.stage.current_position
389 x_m, y_m, z_m = direct_encoder_pos.x, direct_encoder_pos.y, direct_encoder_pos.z
390 r_rad, t_rad = direct_encoder_pos.r, direct_encoder_pos.t
391 coord_system_str = direct_encoder_pos.coordinate_system
393 encoder_pos = tbt.StagePositionEncoder(
394 x=x_m, y=y_m, z=z_m, r=r_rad, t=t_rad, coordinate_system=coord_system_str
395 )
396 user_pos = stage.encoder_to_user_position(encoder_pos)
398 # ensure r-axis is kept in axis limit
399 # ------------------------------------------------
400 # THIS IS NOT NEEDED, AUTOSCRIPT DOES THIS ALREADY
401 # ------------------------------------------------
402 # if not ut.in_interval(
403 # val=user_pos.r_deg,
404 # limit=Constants.rotation_axis_limit_deg,
405 # type=tbt.IntervalType.RIGHT_OPEN,
406 # ):
407 # # used as right-open internal: 180.0 is not valid and should be converted to -180.0
408 # while user_pos.r_deg >= Constants.rotation_axis_limit_deg.max:
409 # new_r_deg = user_pos.r_deg - 360.0
410 # while user_pos.r_deg < Constants.rotation_axis_limit_deg.min:
411 # new_r_deg = user_pos.r_deg + 360.0
412 # new_r_deg = round(new_r_deg, 6)
413 # user_pos = tbt.StagePositionUser(
414 # x_mm=user_pos.x_mm,
415 # y_mm=user_pos.y_mm,
416 # z_mm=user_pos.z_mm,
417 # r_deg=new_r_deg,
418 # t_deg=user_pos.t_deg,
419 # )
421 return user_pos
424def active_laser_state() -> tbt.LaserState:
425 """
426 Retrieve the current state of the laser, including various properties that can be quickly read.
428 This function returns a dictionary object for all properties that can be quickly read from the laser (not exhaustive). Power can be read but has its own method and is more involved. Flipper configuration can only be set, not read.
430 ## Returns
432 tbt.LaserState
433 The current state of the laser, including wavelength, frequency, pulse divider, pulse energy, objective position, beam shift, pattern, and expected pattern duration.
435 ## Raises
437 KeyError
438 If an unsupported LaserPatternType is encountered.
440 """
441 vals = fs_laser.tfs_laser.Laser_ReadValues()
442 vals["objective_position_mm"] = fs_laser.tfs_laser.LIP_GetZPosition()
443 vals["beam_shift_um_x"] = fs_laser.tfs_laser.BeamShift_Get_X()
444 vals["beam_shift_um_y"] = fs_laser.tfs_laser.BeamShift_Get_Y()
445 vals["shutter_state"] = fs_laser.tfs_laser.Shutter_GetState()
446 vals["pattern"] = fs_laser.tfs_laser.Patterning_ReadValues()
447 vals["expected_pattern_duration_s"] = (
448 fs_laser.tfs_laser.Patterning_GetExpectedDuration()
449 )
451 pattern_db = vals["pattern"]
452 pattern_type = pattern_db["patternType"].lower()
453 if not ut.valid_enum_entry(pattern_type, tbt.LaserPatternType): 453 ↛ 457line 453 didn't jump to line 457 because the condition on line 453 was always true
454 raise KeyError( 454 ↛ exit, 454 ↛ exit2 missed branches: 1) line 454 didn't jump to the function exit, 2) line 454 didn't except from function 'active_laser_state' because the raise on line 454 wasn't executed
455 f"Unsupported LaserPatternType of {pattern_type}, supported types include: {[i.value for i in tbt.LaserPatternType]}"
456 )
457 pattern_type = tbt.LaserPatternType(pattern_type)
458 mode = tbt.LaserPatternMode(pattern_db["patterningMode"].lower())
459 if mode == tbt.LaserPatternMode.COARSE: 459 ↛ 460, 459 ↛ 4622 missed branches: 1) line 459 didn't jump to line 460 because the condition on line 459 was never true, 2) line 459 didn't jump to line 462 because the condition on line 459 was always true
460 pixel_dwell_ms = pattern_db["dwellTime"]
461 pulses_per_pixel = None
462 elif mode == tbt.LaserPatternMode.FINE:
463 pixel_dwell_ms = None
464 pulses_per_pixel = pattern_db["pulsesPerPixel"]
465 rotation_deg = pattern_db["patternRotation_deg"]
467 if pattern_type == tbt.LaserPatternType.BOX:
468 geometry = tbt.LaserBoxPattern(
469 passes=pattern_db["passes"],
470 size_x_um=pattern_db["xSize_um"],
471 size_y_um=pattern_db["ySize_um"],
472 pitch_x_um=pattern_db["xPitch_um"],
473 pitch_y_um=pattern_db["yPitch_um"],
474 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()),
475 coordinate_ref=tbt.CoordinateReference(
476 pattern_db["coordReference"].lower()
477 ),
478 )
479 if pattern_type == tbt.LaserPatternType.LINE:
480 geometry = tbt.LaserLinePattern(
481 passes=pattern_db["passes"],
482 size_um=pattern_db["xSize_um"],
483 pitch_um=pattern_db["xPitch_um"],
484 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()),
485 )
487 pattern = tbt.LaserPattern(
488 mode=mode,
489 rotation_deg=rotation_deg,
490 geometry=geometry,
491 pulses_per_pixel=pulses_per_pixel,
492 pixel_dwell_ms=pixel_dwell_ms,
493 )
495 state = tbt.LaserState(
496 wavelength_nm=vals["wavelength_nm"],
497 frequency_khz=vals["frequency_kHz"],
498 pulse_divider=vals["pulse_divider"],
499 pulse_energy_uj=vals["pulse_energy_uJ"],
500 objective_position_mm=vals["objective_position_mm"],
501 beam_shift_um=tbt.Point(x=vals["beam_shift_um_x"], y=vals["beam_shift_um_y"]),
502 pattern=pattern,
503 expected_pattern_duration_s=vals["expected_pattern_duration_s"],
504 )
506 return state
509def active_laser_settings(microscope: tbt.Microscope) -> tbt.LaserSettings:
510 """
511 Retrieve the current active laser settings from the microscope to create a laser settings object.
513 This function grabs the current laser state and uses it to create a laser settings object. Some values cannot be read by Laser Control and can only be set. For example, polarization will default to "Vertical" as this value cannot be read.
515 ## Parameters
517 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active laser settings.
519 ## Returns
521 - `tbt.LaserSettings`: The active laser settings object.
522 """
523 state = active_laser_state()
525 settings = tbt.LaserSettings(
526 microscope=microscope,
527 pulse=tbt.LaserPulse(
528 wavelength_nm=state.wavelength_nm,
529 divider=state.pulse_divider,
530 energy_uj=state.pulse_energy_uj,
531 polarization=tbt.LaserPolarization.VERTICAL, # TODO, can't read this, can only set it
532 ),
533 objective_position_mm=state.objective_position_mm,
534 beam_shift_um=state.beam_shift_um,
535 pattern=state.pattern,
536 )
538 return settings
541def available_detector_types(microscope: tbt.Microscope) -> List[str]:
542 """
543 Retrieve the available detector types on the current microscope.
545 This function returns a list of available detector types on the current microscope.
547 ## Parameters
549 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the available detector types.
551 ## Returns
553 - `List[str]`: A list of available detector types.
554 """
555 detectors = microscope.detector.type.available_values
556 return detectors
559def available_detector_modes(microscope: tbt.Microscope) -> List[str]:
560 """
561 Retrieve the available detector modes on the current microscope.
563 This function returns a list of available detector modes on the current microscope.
565 ## Parameters
567 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the available detector modes.
569 ## Returns
571 - `List[str]`: A list of available detector modes.
572 """
573 modes = microscope.detector.mode.available_values
574 # available = [tbt.DetectorType(i) for i in modes]
575 return modes
578def beam_object_type(type: tbt.BeamType) -> tbt.Beam:
579 """
580 Retrieve the beam object type based on the given beam type.
582 This function returns the appropriate beam object type (electron or ion) based on the provided beam type.
584 ## Parameters
586 - `type` (`tbt.BeamType`): The type of the beam (electron or ion).
588 ## Returns
590 - `tbt.Beam`: The corresponding beam object type.
592 ## Raises
594 - `NotImplementedError`: If the provided beam type is unsupported.
595 """
596 if not isinstance(type, tbt.BeamType):
597 raise ValueError("Electron and Ion are the only allowed beam object types.")
598 if type.value == "electron":
599 return tbt.ElectronBeam
600 if type.value == "ion": 600 ↛ exitline 600 didn't return from function 'beam_object_type' because the condition on line 600 was always true
601 return tbt.IonBeam
604def stage_limits(microscope: tbt.Microscope) -> tbt.StageLimits:
605 """
606 Retrieve the stage limits from the current microscope connection.
608 This function retrieves the stage limits for the X, Y, Z, R, and T axes from the current microscope connection and returns them as a `StageLimits` object.
610 ## Parameters
612 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the stage limits.
614 ## Returns
616 - `tbt.StageLimits`: The stage limits for the X, Y, Z, R, and T axes in user units (mm and degrees).
617 """
618 stage.coordinate_system(microscope=microscope)
619 # x position
620 min_x_mm = (
621 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).min
622 * Conversions.M_TO_MM
623 )
624 max_x_mm = (
625 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).max
626 * Conversions.M_TO_MM
627 )
628 # y position
629 min_y_mm = (
630 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).min
631 * Conversions.M_TO_MM
632 )
633 max_y_mm = (
634 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).max
635 * Conversions.M_TO_MM
636 )
637 # z position
638 min_z_mm = (
639 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).min
640 * Conversions.M_TO_MM
641 )
642 max_z_mm = (
643 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).max
644 * Conversions.M_TO_MM
645 )
646 # r position
647 min_r_deg = Constants.rotation_axis_limit_deg.min
648 max_r_deg = Constants.rotation_axis_limit_deg.max
649 # t position
650 min_t_deg = (
651 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).min
652 * Conversions.RAD_TO_DEG
653 )
654 max_t_deg = (
655 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).max
656 * Conversions.RAD_TO_DEG
657 )
659 return tbt.StageLimits(
660 x_mm=tbt.Limit(min=min_x_mm, max=max_x_mm),
661 y_mm=tbt.Limit(min=min_y_mm, max=max_y_mm),
662 z_mm=tbt.Limit(min=min_z_mm, max=max_z_mm),
663 r_deg=tbt.Limit(min=min_r_deg, max=max_r_deg),
664 t_deg=tbt.Limit(min=min_t_deg, max=max_t_deg),
665 )
668def beam_limits(
669 selected_beam: property,
670 beam_type: tbt.BeamType,
671) -> tbt.BeamLimits:
672 """
673 Retrieve the beam limits for the selected beam and beam type.
675 This function retrieves the limits for voltage, current, horizontal field width (HFW), and working distance for the selected beam and beam type, and returns them as a `BeamLimits` object.
677 ## Parameters
679 - `selected_beam` (`property`): The selected beam property from which to retrieve the limits.
680 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion).
682 ## Returns
684 - `tbt.BeamLimits`: The beam limits for voltage, current, HFW, and working distance in user units (kV, nA, mm).
686 ## Raises
688 - `ValueError`: If the beam type is unsupported.
689 """
690 # voltage range
691 min_kv = selected_beam.high_voltage.limits.min * Conversions.V_TO_KV
692 max_kv = selected_beam.high_voltage.limits.max * Conversions.V_TO_KV
694 # current range
695 if beam_type == tbt.BeamType.ELECTRON:
696 min_na = selected_beam.beam_current.limits.min * Conversions.A_TO_NA
697 max_na = selected_beam.beam_current.limits.max * Conversions.A_TO_NA
698 if beam_type == tbt.BeamType.ION:
699 available_currents = selected_beam.beam_current.available_values
700 min_na = min(available_currents) * Conversions.A_TO_NA
701 max_na = max(available_currents) * Conversions.A_TO_NA
703 # hfw range
704 min_hfw_mm = selected_beam.horizontal_field_width.limits.min * Conversions.M_TO_MM
705 max_hfw_mm = selected_beam.horizontal_field_width.limits.max * Conversions.M_TO_MM
707 # working_dist range
708 min_wd_mm = selected_beam.working_distance.limits.min * Conversions.M_TO_MM
709 max_wd_mm = selected_beam.working_distance.limits.max * Conversions.M_TO_MM
711 return tbt.BeamLimits(
712 voltage_kv=tbt.Limit(min=min_kv, max=max_kv),
713 current_na=tbt.Limit(min=min_na, max=max_na),
714 hfw_mm=tbt.Limit(min=min_hfw_mm, max=max_hfw_mm),
715 working_distance_mm=tbt.Limit(min=min_wd_mm, max=max_wd_mm),
716 )
719def general(
720 general_db: dict,
721 yml_format: tbt.YMLFormatVersion,
722) -> tbt.GeneralSettings:
723 """
724 Convert a general settings dictionary to a built-in type and perform schema checking.
726 This function converts a general settings dictionary from a .yml file to a `GeneralSettings` object. It performs schema checking to ensure valid inputs are requested.
728 ## Parameters
730 - `general_db` (`dict`): The general settings dictionary from the .yml file.
731 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
733 ## Returns
735 - `tbt.GeneralSettings`: The general settings object.
737 ## Raises
739 - `NotImplementedError`: If the provided yml format is unsupported.
740 """
742 if not yml_format in tbt.YMLFormatVersion:
743 raise NotImplementedError(
744 """Due to the complexity and number of variables,
745 image objects should only be constructed using a yml file."""
746 )
747 if not isinstance(yml_format, tbt.YMLFormatVersion):
748 raise NotImplementedError(f"Unsupported yml format of {yml_format}.")
750 validate_general_settings(
751 settings=general_db,
752 yml_format=yml_format,
753 )
755 if yml_format.version >= 1.0: 755 ↛ 789line 755 didn't jump to line 789 because the condition on line 755 was always true
756 # slice thickness
757 slice_thickness_um = general_db["slice_thickness_um"]
758 # max slice number
759 max_slice_number = general_db["max_slice_num"]
760 # pre tilt
761 pre_tilt_deg = general_db["pre_tilt_deg"]
762 # sectioning axis
763 sectioning_axis = tbt.SectioningAxis(general_db["sectioning_axis"])
764 # stage tolerance
765 stage_tolerance = tbt.StageTolerance(
766 translational_um=general_db["stage_translational_tol_um"],
767 angular_deg=general_db["stage_angular_tol_deg"],
768 )
769 # connection
770 connection = tbt.MicroscopeConnection(
771 general_db["connection_host"], general_db["connection_port"]
772 )
773 # EBSD OEM
774 ebsd_oem = tbt.ExternalDeviceOEM(general_db["EBSD_OEM"])
775 # EDS OEM
776 eds_oem = tbt.ExternalDeviceOEM(general_db["EDS_OEM"])
777 # exp dir
778 exp_dir = general_db["exp_dir"]
779 # h5 log name
780 h5_log_name = general_db["h5_log_name"]
781 # remove log file extension if the user provided it
782 log_extension = Constants.logfile_extension
783 if h5_log_name.endswith(log_extension): 783 ↛ 784line 783 didn't jump to line 784 because the condition on line 783 was never true
784 h5_log_name = h5_log_name[: -len(log_extension)]
785 # step count
786 step_count = general_db["step_count"]
787 yml_version = 1.0
789 general_settings = tbt.GeneralSettings(
790 yml_version=yml_version,
791 slice_thickness_um=slice_thickness_um,
792 max_slice_number=max_slice_number,
793 pre_tilt_deg=pre_tilt_deg,
794 sectioning_axis=sectioning_axis,
795 stage_tolerance=stage_tolerance,
796 connection=connection,
797 EBSD_OEM=ebsd_oem,
798 EDS_OEM=eds_oem,
799 exp_dir=Path(exp_dir),
800 h5_log_name=h5_log_name,
801 step_count=step_count,
802 )
804 return general_settings
807def laser_box_pattern(settings: dict) -> tbt.LaserBoxPattern:
808 """
809 Convert a dictionary of laser box pattern settings to a `LaserBoxPattern` object.
811 This function takes a dictionary of laser box pattern settings and converts it to a `LaserBoxPattern` object.
813 ## Parameters
815 - `settings` (`dict`): The dictionary containing laser box pattern settings.
817 ## Returns
819 - `tbt.LaserBoxPattern`: The laser box pattern object.
820 """
821 return tbt.LaserBoxPattern(
822 passes=settings["passes"],
823 size_x_um=settings["size_x_um"],
824 size_y_um=settings["size_y_um"],
825 pitch_x_um=settings["pitch_x_um"],
826 pitch_y_um=settings["pitch_y_um"],
827 scan_type=tbt.LaserScanType(settings["scan_type"]),
828 coordinate_ref=tbt.CoordinateReference(settings["coordinate_ref"]),
829 )
832def laser_line_pattern(settings: dict) -> tbt.LaserBoxPattern:
833 """
834 Convert a dictionary of laser line pattern settings to a `LaserLinePattern` object.
836 This function takes a dictionary of laser line pattern settings and converts it to a `LaserLinePattern` object.
838 ## Parameters
840 - `settings` (`dict`): The dictionary containing laser line pattern settings.
842 ## Returns
844 - `tbt.LaserLinePattern`: The laser line pattern object.
845 """
846 return tbt.LaserLinePattern(
847 passes=settings["passes"],
848 size_um=settings["size_um"],
849 pitch_um=settings["pitch_um"],
850 scan_type=tbt.LaserScanType(settings["scan_type"]),
851 )
854def laser(
855 microscope: tbt.Microscope,
856 step_settings: dict,
857 step_name: str,
858 yml_format: tbt.YMLFormatVersion,
859) -> tbt.LaserSettings:
860 """
861 Convert a laser step from a .yml file to microscope settings for performing laser milling.
863 This function converts a laser step from a .yml file to `LaserSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
865 ## Parameters
867 - `microscope` (`tbt.Microscope`): The microscope object for which to set the laser settings.
868 - `step_settings` (`dict`): The dictionary containing the laser step settings from the .yml file.
869 - `step_name` (`str`): The name of the step in the .yml file.
870 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
872 ## Returns
874 - `tbt.LaserSettings`: The laser settings object.
876 ## Raises
878 - `KeyError`: If required settings are missing from the .yml file.
879 """
880 if yml_format.version >= 1.0: 880 ↛ 902line 880 didn't jump to line 902 because the condition on line 880 was always true
881 # pulse settings
882 pulse_set_db = step_settings.get("pulse")
883 if pulse_set_db is None: 883 ↛ 884line 883 didn't jump to line 884 because the condition on line 883 was never true
884 raise KeyError(
885 f"Invalid .yml file, no 'pulse' settings found in 'laser' step_type for step '{step_name}'."
886 )
887 # laser optics settings
888 optics_set_db = {
889 "objective_position_mm": step_settings.get("objective_position_mm"),
890 "beam_shift_um_x": step_settings.get("beam_shift").get("x_um"),
891 "beam_shift_um_y": step_settings.get("beam_shift").get("y_um"),
892 }
893 # pattern settings
894 pattern_set_db = step_settings.get("pattern")
895 if pattern_set_db is None: 895 ↛ 896line 895 didn't jump to line 896 because the condition on line 895 was never true
896 raise KeyError(
897 f"Invalid .yml file, no 'pattern' settings found in 'laser' step_type for step '{step_name}'."
898 )
899 line_pattern_db = pattern_set_db.get("type").get("line")
900 box_pattern_db = pattern_set_db.get("type").get("box")
902 validate_pulse_settings(
903 settings=pulse_set_db,
904 yml_format=yml_format,
905 step_name=step_name,
906 )
907 pulse = tbt.LaserPulse(
908 wavelength_nm=tbt.LaserWavelength(pulse_set_db["wavelength_nm"]),
909 divider=pulse_set_db["divider"],
910 energy_uj=pulse_set_db["energy_uj"],
911 polarization=tbt.LaserPolarization(pulse_set_db["polarization"]),
912 )
914 validate_laser_optics_settings(
915 settings=optics_set_db,
916 yml_format=yml_format,
917 step_name=step_name,
918 )
920 pattern_type = validate_laser_pattern_settings(
921 settings=pattern_set_db,
922 yml_format=yml_format,
923 step_name=step_name,
924 )
925 if pattern_type == tbt.LaserPatternType.BOX:
926 geometry = laser_box_pattern(box_pattern_db)
927 if pattern_type == tbt.LaserPatternType.LINE:
928 geometry = laser_line_pattern(line_pattern_db)
929 pattern = tbt.LaserPattern(
930 mode=tbt.LaserPatternMode(pattern_set_db["mode"]),
931 rotation_deg=pattern_set_db["rotation_deg"],
932 pulses_per_pixel=pattern_set_db["pulses_per_pixel"],
933 pixel_dwell_ms=pattern_set_db["pixel_dwell_ms"],
934 geometry=geometry,
935 )
937 laser_settings = tbt.LaserSettings(
938 microscope=microscope,
939 pulse=pulse,
940 objective_position_mm=optics_set_db["objective_position_mm"],
941 beam_shift_um=tbt.Point(
942 x=optics_set_db["beam_shift_um_x"],
943 y=optics_set_db["beam_shift_um_y"],
944 ),
945 pattern=pattern,
946 )
948 return laser_settings
951def image(
952 microscope: tbt.Microscope,
953 step_settings: dict,
954 step_name: str,
955 yml_format: tbt.YMLFormatVersion,
956) -> tbt.ImageSettings:
957 """
958 Convert an image step from a .yml file to microscope settings for capturing an image.
960 This function converts an image step from a .yml file to `ImageSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
962 ## Parameters
964 - `microscope` (`tbt.Microscope`): The microscope object for which to set the image settings.
965 - `step_settings` (`dict`): The dictionary containing the image step settings from the .yml file.
966 - `step_name` (`str`): The name of the step in the .yml file.
967 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
969 ## Returns
971 - `tbt.ImageSettings`: The image settings object.
973 ## Raises
975 - `KeyError`: If required settings are missing from the .yml file.
976 - `NotImplementedError`: If the provided beam type is unsupported.
977 - `ValueError`: If invalid scan rotation is requested with dynamic focus or tilt correction, or if the bit depth is unsupported.
978 """
979 if yml_format.version >= 1.0: 979 ↛ 1023line 979 didn't jump to line 1023 because the condition on line 979 was always true
980 step_general = step_settings.get(yml_format.step_general_key)
981 if step_general is None: 981 ↛ 982line 981 didn't jump to line 982 because the condition on line 981 was never true
982 raise KeyError(
983 f"Invalid .yml file, no 'step_general' settings found in step '{step_name}'."
984 )
985 step_type = step_general.get(yml_format.step_type_key)
986 if step_type is None: 986 ↛ 987line 986 didn't jump to line 987 because the condition on line 986 was never true
987 raise KeyError(
988 f"Invalid .yml file, no 'step_type' settings found in step '{step_name}'."
989 )
990 # beam settings
991 beam_set_db = step_settings.get("beam")
992 if beam_set_db is None: 992 ↛ 993line 992 didn't jump to line 993 because the condition on line 992 was never true
993 raise KeyError(
994 f"Invalid .yml file, no 'beam' settings found in '{step_type}' step_type for step '{step_name}'."
995 )
996 beam_type_value = beam_set_db.get("type")
997 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
998 raise NotImplementedError(
999 f"Unsupported beam type of '{beam_type_value}', supported beam types are: {[i.value for i in tbt.BeamType]}."
1000 )
1001 beam_type = tbt.BeamType(beam_set_db.get("type"))
1003 # detector settings
1004 detector_set_db = step_settings.get("detector")
1005 if detector_set_db is None: 1005 ↛ 1006line 1005 didn't jump to line 1006 because the condition on line 1005 was never true
1006 raise KeyError(
1007 f"Invalid .yml file, no 'detector' settings found in '{step_type}' step_type for step '{step_name}'."
1008 )
1009 auto_cb_set_db = detector_set_db.get("auto_cb")
1011 # scan settings
1012 scan_set_db = step_settings.get("scan")
1013 if scan_set_db is None: 1013 ↛ 1014line 1013 didn't jump to line 1014 because the condition on line 1013 was never true
1014 raise KeyError(
1015 f"Invalid .yml file, no 'scan' settings found in '{step_type}' step_type for step '{step_name}'."
1016 )
1017 scan_res = string_to_res(scan_set_db.get("resolution"))
1019 # misc settings
1020 bit_depth = step_settings.get("bit_depth")
1022 # TODO incorporate tile settings
1023 if yml_format.version >= 1.1: 1023 ↛ 1025line 1023 didn't jump to line 1025 because the condition on line 1023 was never true
1024 # tile settings
1025 tile_set_db = step_settings.get("tile_settings")
1027 validate_beam_settings(
1028 microscope=microscope,
1029 beam_type=beam_type,
1030 settings=beam_set_db,
1031 yml_format=yml_format,
1032 step_name=step_name,
1033 )
1034 beam_settings = tbt.BeamSettings(
1035 voltage_kv=beam_set_db["voltage_kv"],
1036 voltage_tol_kv=beam_set_db["voltage_tol_kv"],
1037 current_na=beam_set_db["current_na"],
1038 current_tol_na=beam_set_db["current_tol_na"],
1039 hfw_mm=beam_set_db["hfw_mm"],
1040 working_dist_mm=beam_set_db["working_dist_mm"],
1041 dynamic_focus=beam_set_db["dynamic_focus"],
1042 tilt_correction=beam_set_db["tilt_correction"],
1043 )
1045 validate_auto_cb_settings(
1046 settings=auto_cb_set_db,
1047 yml_format=yml_format,
1048 step_name=step_name,
1049 )
1050 auto_cb_settings = tbt.ScanArea(
1051 left=auto_cb_set_db["left"],
1052 top=auto_cb_set_db["top"],
1053 width=auto_cb_set_db["width"],
1054 height=auto_cb_set_db["height"],
1055 )
1057 validate_detector_settings(
1058 microscope=microscope,
1059 beam_type=beam_type,
1060 settings=detector_set_db,
1061 yml_format=yml_format,
1062 step_name=step_name,
1063 )
1064 detector_settings = tbt.Detector(
1065 type=tbt.DetectorType(detector_set_db["type"]),
1066 mode=tbt.DetectorMode(detector_set_db["mode"]),
1067 brightness=detector_set_db["brightness"],
1068 contrast=detector_set_db["contrast"],
1069 auto_cb_settings=auto_cb_settings,
1070 # custom_settings=None,
1071 )
1073 validate_scan_settings(
1074 microscope=microscope,
1075 beam_type=beam_type,
1076 settings=scan_set_db,
1077 yml_format=yml_format,
1078 step_name=step_name,
1079 )
1081 # cast resolution to preset if applicable
1082 if ut.valid_enum_entry(obj=scan_res, check_type=tbt.PresetResolution):
1083 scan_res = tbt.PresetResolution(scan_res)
1085 scan_settings = tbt.Scan(
1086 rotation_deg=scan_set_db["rotation_deg"],
1087 dwell_time_us=scan_set_db["dwell_time_us"],
1088 resolution=scan_res,
1089 # mode=tbt.ScanMode(scan_set_db["mode"]),
1090 )
1092 # make sure Scan rotation is 0 if dynamic focus or tilt correction is on (using auto mode only for angular correction)
1093 if beam_settings.dynamic_focus or beam_settings.tilt_correction:
1094 if not math.isclose(a=scan_settings.rotation_deg, b=0.0):
1095 raise ValueError(
1096 f"Invalid .yml for step '{step_name}'. Scan rotation of '{scan_settings.rotation_deg}' degrees requested with tilt_correction and/or dynamic focus set to 'True'. Cannot use dynamic focus or tilt correction for non-zero scan rotation."
1097 )
1099 # validate bit_depth
1100 if not ut.valid_enum_entry(bit_depth, tbt.ColorDepth):
1101 valid_bit_depths = [i.value for i in tbt.ColorDepth] 1101 ↛ exitline 1101 didn't run the list comprehension on line 1101
1102 raise ValueError(
1103 f"Unsupported bit depth of {bit_depth}, available depths are {valid_bit_depths}"
1104 )
1106 image_settings = tbt.ImageSettings(
1107 microscope=microscope,
1108 beam=beam_object_type(beam_type)(settings=beam_settings),
1109 detector=detector_settings,
1110 scan=scan_settings,
1111 bit_depth=bit_depth,
1112 )
1114 return image_settings
1117def fib(
1118 microscope: tbt.Microscope,
1119 step_settings: dict,
1120 step_name: str,
1121 yml_format: tbt.YMLFormatVersion,
1122) -> tbt.FIBSettings:
1123 """
1124 Convert a FIB step from a .yml file to microscope settings for performing a FIB operation.
1126 This function converts a FIB step from a .yml file to `FIBSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
1128 ## Parameters
1130 - `microscope` (`tbt.Microscope`): The microscope object for which to set the FIB settings.
1131 - `step_settings` (`dict`): The dictionary containing the FIB step settings from the .yml file.
1132 - `step_name` (`str`): The name of the step in the .yml file.
1133 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1135 ## Returns
1137 - `tbt.FIBSettings`: The FIB settings object.
1139 ## Raises
1141 - `KeyError`: If required settings are missing from the .yml file.
1142 - `ValueError`: If invalid beam type is requested.
1143 """
1144 ## create image_step_settings from this
1145 if yml_format.version >= 1.0: 1145 ↛ 1154line 1145 didn't jump to line 1154 because the condition on line 1145 was always true
1146 image_step_settings = step_settings.get("image")
1147 image_step_settings["step_general"] = step_settings.get("step_general")
1149 mill_step_settings = step_settings.get("mill")
1150 mill_beam_db = mill_step_settings.get("beam")
1151 mill_pattern_db = mill_step_settings.get("pattern")
1153 # ensure image is with an ion beam
1154 enforce_beam_type(
1155 tbt.IonBeam(settings=None),
1156 step_settings=image_step_settings,
1157 step_name=step_name,
1158 yml_format=yml_format,
1159 )
1160 image_settings = image(
1161 microscope=microscope,
1162 step_settings=image_step_settings,
1163 step_name=step_name,
1164 yml_format=yml_format,
1165 )
1167 ## fib mill settings
1168 # ensure milling is with an ion beam
1169 enforce_beam_type(
1170 tbt.IonBeam(settings=None),
1171 step_settings=mill_step_settings,
1172 step_name=step_name,
1173 yml_format=yml_format,
1174 )
1175 beam_type = tbt.BeamType(mill_beam_db.get("type"))
1176 # mill beam
1177 validate_beam_settings(
1178 microscope=microscope,
1179 beam_type=beam_type,
1180 settings=mill_beam_db,
1181 yml_format=yml_format,
1182 step_name=step_name,
1183 )
1184 mill_beam = tbt.IonBeam(
1185 settings=tbt.BeamSettings(
1186 voltage_kv=mill_beam_db["voltage_kv"],
1187 voltage_tol_kv=mill_beam_db["voltage_tol_kv"],
1188 current_na=mill_beam_db["current_na"],
1189 current_tol_na=mill_beam_db["current_tol_na"],
1190 hfw_mm=mill_beam_db["hfw_mm"],
1191 working_dist_mm=mill_beam_db["working_dist_mm"],
1192 dynamic_focus=mill_beam_db["dynamic_focus"],
1193 tilt_correction=mill_beam_db["tilt_correction"],
1194 )
1195 )
1197 # fib pattern settings
1198 pattern = validate_fib_pattern_settings(
1199 microscope=microscope,
1200 settings=mill_pattern_db,
1201 yml_format=yml_format,
1202 step_name=step_name,
1203 )
1205 fib_settings = tbt.FIBSettings(
1206 microscope=microscope,
1207 image=image_settings,
1208 mill_beam=mill_beam,
1209 pattern=pattern,
1210 )
1211 return fib_settings
1214@singledispatch
1215def enforce_beam_type(
1216 beam_type,
1217 step_settings: dict,
1218 step_name: str,
1219 yml_format: tbt.YMLFormatVersion,
1220) -> bool:
1221 """
1222 Enforce a specific beam type is used for an operation based on a dictionary.
1224 This function ensures that the specified beam type is used for an operation based on the provided settings dictionary. The dictionary must contain a sub-dictionary with the key 'beam'.
1226 ## Parameters
1228 - `beam_type` (`Any`): The beam type to enforce.
1229 - `step_settings` (`dict`): The dictionary containing the step settings.
1230 - `step_name` (`str`): The name of the step in the .yml file.
1231 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1233 ## Returns
1235 - `bool`: True if the beam type is enforced successfully.
1237 ## Raises
1239 - `NotImplementedError`: If no handler is available for the provided type.
1240 """
1241 _ = beam_type
1242 __ = step_settings
1243 ___ = step_name
1244 ____ = yml_format
1245 raise NotImplementedError(f"No handler for type {type(step_settings)}")
1248@enforce_beam_type.register
1249def _(
1250 beam_type: tbt.ElectronBeam,
1251 step_settings: dict,
1252 step_name: str,
1253 yml_format: tbt.YMLFormatVersion,
1254) -> bool:
1255 """
1256 Enforce that an electron beam is used for an operation based on a dictionary.
1258 This function ensures that an electron beam is used for an operation based on the provided settings dictionary.
1260 ## Parameters
1262 - `beam_type` (`tbt.ElectronBeam`): The electron beam type to enforce.
1263 - `step_settings` (`dict`): The dictionary containing the step settings.
1264 - `step_name` (`str`): The name of the step in the .yml file.
1265 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1267 ## Returns
1269 - `bool`: True if the electron beam type is enforced successfully.
1271 ## Raises
1273 - `KeyError`: If the 'beam' settings are missing from the .yml file.
1274 - `NotImplementedError`: If the beam type is unsupported or not an electron beam.
1275 """
1276 # beam must be electron
1277 if yml_format.version >= 1.0: 1277 ↛ exitline 1277 didn't return from function '_' because the condition on line 1277 was always true
1278 beam_set_db = step_settings.get("beam")
1279 if beam_set_db is None: 1279 ↛ 1280line 1279 didn't jump to line 1280 because the condition on line 1279 was never true
1280 raise KeyError(
1281 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'."
1282 )
1283 beam_type_value = beam_set_db.get("type")
1285 electron_beam_error_message = f"Unsupported beam type of '{beam_type_value}' in step '{step_name}'. '{tbt.BeamType.ELECTRON.value}' beam type must be used."
1286 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
1287 raise NotImplementedError(electron_beam_error_message)
1288 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ELECTRON:
1289 raise NotImplementedError(electron_beam_error_message)
1292@enforce_beam_type.register
1293def _(
1294 beam_type: tbt.IonBeam,
1295 step_settings: dict,
1296 step_name: str,
1297 yml_format: tbt.YMLFormatVersion,
1298) -> bool:
1299 """
1300 Enforce that an ion beam is used for an operation based on a dictionary.
1302 This function ensures that an ion beam is used for an operation based on the provided settings dictionary.
1304 ## Parameters
1306 - `beam_type` (`tbt.IonBeam`): The ion beam type to enforce.
1307 - `step_settings` (`dict`): The dictionary containing the step settings.
1308 - `step_name` (`str`): The name of the step in the .yml file.
1309 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1311 ## Returns
1313 - `bool`: True if the ion beam type is enforced successfully.
1315 ## Raises
1317 - `KeyError`: If the 'beam' settings are missing from the .yml file.
1318 - `NotImplementedError`: If the beam type is unsupported or not an ion beam.
1319 """
1320 # beam must be ion
1321 if yml_format.version >= 1.0: 1321 ↛ exitline 1321 didn't return from function '_' because the condition on line 1321 was always true
1322 beam_set_db = step_settings.get("beam")
1323 if beam_set_db is None: 1323 ↛ 1324line 1323 didn't jump to line 1324 because the condition on line 1323 was never true
1324 raise KeyError(
1325 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'."
1326 )
1327 beam_type_value = beam_set_db.get("type")
1329 ion_beam_error_message = f"Unsupported beam type of '{beam_type_value}' for step '{step_name}'. '{tbt.BeamType.ION.value}' beam type must be used."
1330 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
1331 raise NotImplementedError(ion_beam_error_message)
1332 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ION:
1333 raise NotImplementedError(ion_beam_error_message)
1336def ebsd(
1337 microscope: tbt.Microscope,
1338 step_settings: dict,
1339 step_name: str,
1340 yml_format: tbt.YMLFormatVersion,
1341) -> tbt.EBSDSettings:
1342 """
1343 Convert an EBSD step from a .yml file to microscope settings for performing an EBSD operation.
1345 This function converts an EBSD step from a .yml file to `EBSDSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
1347 ## Parameters
1349 - `microscope` (`tbt.Microscope`): The microscope object for which to set the EBSD settings.
1350 - `step_settings` (`dict`): The dictionary containing the EBSD step settings from the .yml file.
1351 - `step_name` (`str`): The name of the step in the .yml file.
1352 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1354 ## Returns
1356 - `tbt.EBSDSettings`: The EBSD settings object.
1358 ## Raises
1360 - `KeyError`: If required settings are missing from the .yml file or if the 'concurrent_EDS' key is invalid.
1361 """
1362 enforce_beam_type(
1363 tbt.ElectronBeam(settings=None),
1364 step_settings=step_settings,
1365 step_name=step_name,
1366 yml_format=yml_format,
1367 )
1368 image_settings = image(
1369 microscope=microscope,
1370 step_settings=step_settings,
1371 step_name=step_name,
1372 yml_format=yml_format,
1373 )
1374 concurrent_EDS = step_settings.get("concurrent_EDS")
1375 if (concurrent_EDS is None) or (not concurrent_EDS):
1376 enable_eds = False
1377 elif concurrent_EDS == True: 1377 ↛ 1380line 1377 didn't jump to line 1380 because the condition on line 1377 was always true
1378 enable_eds = True
1379 else:
1380 raise KeyError(
1381 f"Invalid .yml file, for step '{step_name}', an EBSD type step. 'concurrent_EDS' key is '{concurrent_EDS}' of type {type(concurrent_EDS)} but must be boolean (True/False) or of NoneType (null in .yml)."
1382 )
1384 ebsd_settings = tbt.EBSDSettings(
1385 image=image_settings,
1386 enable_eds=enable_eds,
1387 enable_ebsd=True,
1388 )
1389 return ebsd_settings
1392def eds(
1393 microscope: tbt.Microscope,
1394 step_settings: dict,
1395 step_name: str,
1396 yml_format: tbt.YMLFormatVersion,
1397) -> tbt.EDSSettings:
1398 """
1399 Convert an EDS step from a .yml file to microscope settings for performing an EDS operation.
1401 This function converts an EDS step from a .yml file to `EDSSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
1403 ## Parameters
1405 - `microscope` (`tbt.Microscope`): The microscope object for which to set the EDS settings.
1406 - `step_settings` (`dict`): The dictionary containing the EDS step settings from the .yml file.
1407 - `step_name` (`str`): The name of the step in the .yml file.
1408 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1410 ## Returns
1412 - `tbt.EDSSettings`: The EDS settings object.
1413 """
1414 enforce_beam_type(
1415 tbt.ElectronBeam(settings=None),
1416 step_settings=step_settings,
1417 step_name=step_name,
1418 yml_format=yml_format,
1419 )
1420 image_settings = image(
1421 microscope=microscope,
1422 step_settings=step_settings,
1423 step_name=step_name,
1424 yml_format=yml_format,
1425 )
1426 eds_settings = tbt.EDSSettings(
1427 image=image_settings,
1428 enable_eds=True,
1429 )
1430 return eds_settings
1433def custom(
1434 microscope: tbt.Microscope,
1435 step_settings: dict,
1436 step_name: str,
1437 yml_format: tbt.YMLFormatVersion,
1438) -> tbt.CustomSettings:
1439 """
1440 Convert a custom step from a .yml file to custom settings for the microscope.
1442 This function converts a custom step from a .yml file to `CustomSettings` for the microscope. It performs schema checking to ensure valid inputs are requested.
1444 ## Parameters
1446 - `microscope` (`tbt.Microscope`): The microscope object for which to set the custom settings.
1447 - `step_settings` (`dict`): The dictionary containing the custom step settings from the .yml file.
1448 - `step_name` (`str`): The name of the step in the .yml file.
1449 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1451 ## Returns
1453 - `tbt.CustomSettings`: The custom settings object.
1455 ## Raises
1457 - `KeyError`: If required settings are missing from the .yml file.
1458 - `ValueError`: If the specified script or executable path does not exist.
1459 """
1460 if yml_format.version >= 1.0: 1460 ↛ 1481line 1460 didn't jump to line 1481 because the condition on line 1460 was always true
1461 script_path = step_settings.get("script_path")
1462 if script_path is None: 1462 ↛ 1463line 1462 didn't jump to line 1463 because the condition on line 1462 was never true
1463 raise KeyError(
1464 f"Invalid .yml file, no 'script_path' found in custom step_type for step '{step_name}'."
1465 )
1466 if not Path(script_path).is_file(): 1466 ↛ 1467line 1466 didn't jump to line 1467 because the condition on line 1466 was never true
1467 raise ValueError(
1468 f"Invalid location for script at location {script_path}. File does not exist."
1469 )
1471 executable_path = step_settings.get("executable_path")
1472 if executable_path is None: 1472 ↛ 1473line 1472 didn't jump to line 1473 because the condition on line 1472 was never true
1473 raise KeyError(
1474 f"Invalid .yml file, no 'executable_path' found in custom step_type for step '{step_name}'."
1475 )
1476 if not Path(executable_path).is_file():
1477 raise ValueError(
1478 f"Invalid location for executable at location {executable_path}. File does not exist."
1479 )
1481 custom_settings = tbt.CustomSettings(
1482 script_path=Path(script_path),
1483 executable_path=Path(executable_path),
1484 )
1485 return custom_settings
1488# def fib_pattern_type(
1489# settings: tbt.FIBSettings,
1490# ) -> Union[tbt.FIBBoxPattern, tbt.FIBStreamPattern]:
1491# """Returns specific pattern type settings for a properly formatted FIBSettings object"""
1494def scan_limits(
1495 selected_beam: property,
1496) -> tbt.ScanLimits:
1497 """
1498 Retrieve the scan settings limits for the selected beam.
1500 This function retrieves the limits for rotation and dwell time for the selected beam and returns them as a `ScanLimits` object.
1502 ## Parameters
1504 - `selected_beam` (`property`): The selected beam property from which to retrieve the scan limits.
1506 ## Returns
1508 - `tbt.ScanLimits`: The scan limits for rotation (degrees) and dwell time (microseconds).
1509 """
1510 # rotation
1511 min_deg = selected_beam.scanning.rotation.limits.min * Conversions.RAD_TO_DEG
1512 max_deg = selected_beam.scanning.rotation.limits.max * Conversions.RAD_TO_DEG
1513 # dwell_time
1514 min_dwell_us = selected_beam.scanning.dwell_time.limits.min * Conversions.S_TO_US
1515 max_dwell_us = selected_beam.scanning.dwell_time.limits.max * Conversions.S_TO_US
1517 return tbt.ScanLimits(
1518 rotation_deg=tbt.Limit(min=min_deg, max=max_deg),
1519 dwell_us=tbt.Limit(min=min_dwell_us, max=max_dwell_us),
1520 )
1523def string_to_res(input: str) -> tbt.Resolution:
1524 """
1525 Convert a string in the format "{{width}}x{{height}}" to a resolution object.
1527 This function takes a string representing the resolution in the format "WIDTHxHEIGHT" and converts it to a `Resolution` object.
1529 ## Parameters
1531 - `input` (`str`): The string representing the resolution in the format "WIDTHxHEIGHT".
1533 ## Returns
1535 - `tbt.Resolution`: The resolution object.
1537 ## Raises
1539 - `ValueError`: If the input string is not in the expected format.
1540 """
1541 try:
1542 split_res = (input.lower()).split("x")
1543 width, height = int(split_res[0]), int(split_res[1])
1544 except:
1545 raise ValueError(
1546 f"""Invalid string format,
1547 expected string format of "WIDTHxHEIGHT",
1548 but received the following: "{input}"."""
1549 )
1551 return tbt.Resolution(width=width, height=height)
1554def valid_string_resolution(string_resolution: str) -> bool:
1555 """
1556 Validate a string resolution.
1558 This function validates a string resolution by converting it to a `Resolution` object and checking if the width and height are within the specified limits.
1560 ## Parameters
1562 - `string_resolution` (`str`): The string representing the resolution in the format "WIDTHxHEIGHT".
1564 ## Returns
1566 - `bool`: True if the resolution is valid, False otherwise.
1567 """
1568 res = string_to_res(string_resolution)
1569 width, height = res.width, res.height
1570 return (
1571 ut.in_interval(
1572 width,
1573 limit=Constants.scan_resolution_limit,
1574 type=tbt.IntervalType.CLOSED,
1575 )
1576 ) and (
1577 ut.in_interval(
1578 height,
1579 limit=Constants.scan_resolution_limit,
1580 type=tbt.IntervalType.CLOSED,
1581 )
1582 )
1585def validate_auto_cb_settings(
1586 settings: dict,
1587 yml_format: tbt.YMLFormatVersion,
1588 step_name: str,
1589) -> bool:
1590 """
1591 Perform schema checking for auto contrast/brightness setting dictionary.
1593 This function validates the auto contrast/brightness settings dictionary based on the specified yml format.
1595 ## Parameters
1597 - `settings` (`dict`): The dictionary containing the auto contrast/brightness settings.
1598 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1599 - `step_name` (`str`): The name of the step in the .yml file.
1601 ## Returns
1603 - `bool`: True if the settings are valid, False otherwise.
1605 ## Raises
1607 - `KeyError`: If required keys are missing from the settings dictionary.
1608 - `ValueError`: If the settings do not satisfy the specified schema.
1609 """
1611 if ut.none_value_dictionary(settings): 1611 ↛ 1614line 1611 didn't jump to line 1614 because the condition on line 1611 was always true
1612 return True
1614 if settings.get("left") is None or settings.get("top") is None: 1614 ↛ 1623line 1614 didn't jump to line 1623 because the condition on line 1614 was always true
1615 if settings.get("left") is None: 1615 ↛ 1616, 1615 ↛ 16182 missed branches: 1) line 1615 didn't jump to line 1616 because the condition on line 1615 was never true, 2) line 1615 didn't jump to line 1618 because the condition on line 1615 was always true
1616 missing_key = "left"
1617 else:
1618 missing_key = "top"
1619 raise KeyError(
1620 f"Missing or no value for '{missing_key}' key in 'auto_cb' sub-dictionary in step '{step_name}' All 'auto_cb' sub-dictionary values must be declared by the user to implement this capability."
1621 )
1623 origin_limit = tbt.Limit(min=0.0, max=1.0) # reduced area limit for scan window
1624 width_limit = tbt.Limit(min=0.0, max=1.0 - settings["left"])
1625 height_limit = tbt.Limit(min=0.0, max=1.0 - settings["top"])
1627 if yml_format.version >= 1.0:
1628 schema = Schema( 1628 ↛ exit, 1628 ↛ 16702 missed branches: 1) line 1628 didn't jump to the function exit, 2) line 1628 didn't jump to line 1670 because
1629 {
1630 "left": And(
1631 float,
1632 lambda x: ut.in_interval(
1633 x,
1634 limit=origin_limit,
1635 type=tbt.IntervalType.RIGHT_OPEN,
1636 ),
1637 error=f"In step '{step_name}', requested auto contrast/brightness window setting 'left' of '{settings['left']}' must satisfy '0 <= left < {origin_limit.max}'. Origin is in top-left corner of the field of view.",
1638 ),
1639 "top": And(
1640 float,
1641 lambda x: ut.in_interval(
1642 x,
1643 limit=origin_limit,
1644 type=tbt.IntervalType.RIGHT_OPEN,
1645 ),
1646 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'top' of '{settings['top']}' must satisfy '0.0 <= top < {origin_limit.max}'. Origin is in top-left corner of the field of view.",
1647 ),
1648 "width": And(
1649 float,
1650 lambda x: ut.in_interval(
1651 x,
1652 limit=width_limit,
1653 type=tbt.IntervalType.LEFT_OPEN,
1654 ),
1655 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'width' of {settings['width']} must satisfy '0.0 < width <= {width_limit.max}' with 'left' setting of {settings['left']} as total width ('left' + 'width') cannot exceed 1.0",
1656 ),
1657 "height": And(
1658 float,
1659 lambda x: ut.in_interval(
1660 x,
1661 limit=height_limit,
1662 type=tbt.IntervalType.LEFT_OPEN,
1663 ),
1664 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'height' of {settings['height']} must satisfy '0.0 < height <= {height_limit.max}' with 'top' setting of {settings['top']} as total height ('top' + 'height') cannot exceed 1.0",
1665 ),
1666 },
1667 ignore_extra_keys=True,
1668 )
1670 try:
1671 schema.validate(settings)
1672 except UnboundLocalError:
1673 raise ValueError(
1674 f"Error. Unsupported yml version {yml_format.version} provided."
1675 )
1676 return True
1679def validate_stage_position(
1680 microscope: tbt.Microscope,
1681 step_name: str,
1682 settings: dict,
1683 yml_format: tbt.YMLFormatVersion,
1684) -> bool:
1685 """
1686 Perform schema checking for stage position dictionary.
1688 This function validates the stage position settings dictionary based on the specified yml format and the stage limits of the microscope.
1690 ## Parameters
1692 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the stage position settings.
1693 - `step_name` (`str`): The name of the step in the .yml file.
1694 - `settings` (`dict`): The dictionary containing the stage position settings.
1695 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1697 ## Returns
1699 - `bool`: True if the settings are valid, False otherwise.
1701 ## Raises
1703 - `ValueError`: If the yml version is unsupported.
1704 """
1705 limits = stage_limits(microscope=microscope)
1707 if yml_format.version >= 1.0: 1707 ↛ 1759line 1707 didn't jump to line 1759 because the condition on line 1707 was always true
1708 schema = Schema(
1709 {
1710 "x_mm": And(
1711 float,
1712 lambda x: ut.in_interval(
1713 x,
1714 limit=limits.x_mm,
1715 type=tbt.IntervalType.CLOSED,
1716 ),
1717 error=f"Requested x-axis position of {settings['x_mm']} mm for step '{step_name}' must satisfy '{limits.x_mm.min} <= x_mm <= {limits.x_mm.max}'",
1718 ),
1719 "y_mm": And(
1720 float,
1721 lambda x: ut.in_interval(
1722 x,
1723 limit=limits.y_mm,
1724 type=tbt.IntervalType.CLOSED,
1725 ),
1726 error=f"Requested y-axis position of {settings['y_mm']} mm for step '{step_name}' must satisfy '{limits.y_mm.min} <= y_mm <= {limits.y_mm.max}'",
1727 ),
1728 "z_mm": And(
1729 float,
1730 lambda x: ut.in_interval(
1731 x,
1732 limit=limits.z_mm,
1733 type=tbt.IntervalType.CLOSED,
1734 ),
1735 error=f"Requested z-axis position of {settings['z_mm']} mm for step '{step_name}' must satisfy '{limits.z_mm.min} <= z_mm <= {limits.z_mm.max}'",
1736 ),
1737 "r_deg": And(
1738 float,
1739 lambda x: ut.in_interval(
1740 x,
1741 limit=limits.r_deg,
1742 type=tbt.IntervalType.RIGHT_OPEN,
1743 ),
1744 error=f"Requested r-axis position of {settings['r_deg']} degree for step '{step_name}' must satisfy '{limits.r_deg.min} <= r_deg < {limits.r_deg.max}'",
1745 ),
1746 "t_deg": And(
1747 float,
1748 lambda x: ut.in_interval(
1749 x,
1750 limit=limits.t_deg,
1751 type=tbt.IntervalType.CLOSED,
1752 ),
1753 error=f"Requested r-axis position of {settings['t_deg']} degree for step '{step_name}' must satisfy '{limits.t_deg.min} <= t_deg <= {limits.t_deg.max}'",
1754 ),
1755 },
1756 ignore_extra_keys=True,
1757 )
1759 try:
1760 schema.validate(settings)
1761 except UnboundLocalError:
1762 raise ValueError(
1763 f"Error. Unsupported yml version {yml_format.version} provided."
1764 )
1767def validate_beam_settings(
1768 microscope: tbt.Microscope,
1769 beam_type: tbt.BeamType,
1770 settings: dict,
1771 yml_format: tbt.YMLFormatVersion,
1772 step_name: str,
1773) -> bool:
1774 """
1775 Perform schema checking for beam setting dictionary.
1777 This function validates the beam settings dictionary based on the specified yml format and the beam limits of the microscope.
1779 ## Parameters
1781 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the beam settings.
1782 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion).
1783 - `settings` (`dict`): The dictionary containing the beam settings.
1784 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1785 - `step_name` (`str`): The name of the step in the .yml file.
1787 ## Returns
1789 - `bool`: True if the settings are valid, False otherwise.
1791 ## Raises
1793 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
1794 """
1795 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
1796 selected_beam = ut.beam_type(specified_beam, microscope)
1798 limits = beam_limits(selected_beam, beam_type)
1800 if yml_format.version >= 1.0: 1800 ↛ 1884line 1800 didn't jump to line 1884 because the condition on line 1800 was always true
1801 schema = Schema(
1802 {
1803 "voltage_kv": And(
1804 float,
1805 lambda x: ut.in_interval(
1806 x,
1807 limit=limits.voltage_kv,
1808 type=tbt.IntervalType.CLOSED,
1809 ),
1810 error=f"In step '{step_name}', requested voltage of '{settings['voltage_kv']}' kV not within limits of {limits.voltage_kv.min} kV and {limits.voltage_kv.max} kV.",
1811 ),
1812 "voltage_tol_kv": And(
1813 float,
1814 lambda x: x > 0,
1815 error=f"In step '{step_name}', requested voltage tolerance of '{settings['voltage_tol_kv']}' kV must be a positive float (greater than 0).",
1816 ),
1817 "current_na": And(
1818 float,
1819 lambda x: ut.in_interval(
1820 x,
1821 limit=limits.current_na,
1822 type=tbt.IntervalType.CLOSED,
1823 ),
1824 error=f"In step '{step_name}', requested voltage of '{settings['current_na']}' nA not within limits of {limits.current_na.min} nA and {limits.current_na.max} nA",
1825 ),
1826 "current_tol_na": And(
1827 float,
1828 lambda x: x > 0,
1829 error=f"In step '{step_name}', 'current_tol_na' must be a positive float (greater than 0)",
1830 ),
1831 "hfw_mm": And(
1832 float,
1833 lambda x: ut.in_interval(
1834 x,
1835 limits.hfw_mm,
1836 type=tbt.IntervalType.CLOSED,
1837 ),
1838 error=f"In step '{step_name}', requested horizontal field width of '{settings['hfw_mm']}' mm not within limits of {limits.hfw_mm.min} mm and {limits.hfw_mm.max} mm",
1839 ),
1840 "working_dist_mm": And(
1841 float,
1842 lambda x: ut.in_interval(
1843 x,
1844 limit=limits.working_distance_mm,
1845 type=tbt.IntervalType.CLOSED,
1846 ),
1847 error=f"In step '{step_name}', requested working distance of '{settings['working_dist_mm']}' mm not within limits of {limits.working_distance_mm.min} mm and {limits.working_distance_mm.max} mm",
1848 ),
1849 },
1850 ignore_extra_keys=True,
1851 )
1853 e_beam_schema = Schema(
1854 {
1855 "dynamic_focus": Or(
1856 None,
1857 bool,
1858 error=f"In step '{step_name}' with 'electron' beam imaging, 'dynamic_focus' must be a boolean value but '{settings['dynamic_focus']}' of type {type(settings['dynamic_focus'])} was requested.",
1859 ),
1860 "tilt_correction": Or(
1861 None,
1862 bool,
1863 error=f"In step '{step_name}' with 'electron' beam imaging, 'tilt_correction' must be a boolean value but '{settings['tilt_correction']}' of type {type(settings['tilt_correction'])} was requested.",
1864 ),
1865 },
1866 ignore_extra_keys=True,
1867 )
1868 i_beam_schema = Schema(
1869 {
1870 "dynamic_focus": Or(
1871 None,
1872 False,
1873 error=f"In step '{step_name}' with 'ion' beam imaging, 'dynamic_focus' must be 'False' or 'None' but '{settings['dynamic_focus']}' of type {type(settings['dynamic_focus'])} was requested.",
1874 ),
1875 "tilt_correction": Or(
1876 None,
1877 False,
1878 error=f"In step '{step_name}' with 'ion' beam imaging, 'tilt_correction' must be 'False' or 'None' but '{settings['tilt_correction']}' of type {type(settings['tilt_correction'])} was requested.",
1879 ),
1880 },
1881 ignore_extra_keys=True,
1882 )
1884 try:
1885 schema.validate(settings)
1886 if specified_beam.type == tbt.BeamType.ELECTRON:
1887 e_beam_schema.validate(settings)
1888 if specified_beam.type == tbt.BeamType.ION:
1889 i_beam_schema.validate(settings)
1890 except UnboundLocalError:
1891 raise ValueError(
1892 f"Error. Unsupported yml version {yml_format.version} provided."
1893 )
1896def validate_detector_settings(
1897 microscope: tbt.Microscope,
1898 beam_type: tbt.BeamType,
1899 settings: dict,
1900 yml_format: tbt.YMLFormatVersion,
1901 step_name: str,
1902) -> bool:
1903 """
1904 Perform schema checking for detector setting dictionary.
1906 This function validates the detector settings dictionary based on the specified yml format and the detector capabilities of the microscope.
1908 ## Parameters
1910 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the detector settings.
1911 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion).
1912 - `settings` (`dict`): The dictionary containing the detector settings.
1913 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
1914 - `step_name` (`str`): The name of the step in the .yml file.
1916 ## Returns
1918 - `bool`: True if the settings are valid, False otherwise.
1920 ## Raises
1922 - `KeyError`: If auto contrast/brightness settings conflict with fixed brightness/contrast values.
1923 - `ValueError`: If the yml version is unsupported, or if the settings do not satisfy the specified schema, or if the detector type or mode is unsupported.
1924 """
1926 # switch to top left quad, enable e-beam
1927 devices.device_access(microscope=microscope)
1929 # set specified beam to active imaging device
1930 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
1931 img.set_beam_device(
1932 microscope=microscope,
1933 device=specified_beam.device,
1934 )
1936 auto_cb_db = settings.get("auto_cb")
1937 use_auto_cb = not ut.none_value_dictionary(settings.get("auto_cb"))
1938 if use_auto_cb and (
1939 settings.get("brightness") is not None or settings.get("contrast") is not None
1940 ):
1941 raise KeyError(
1942 f"Auto contrast/brightness settings of {auto_cb_db} provided while settings of fixed brightness of '{settings.get('brightness')}' and fixed contrast of '{settings.get('contrast')}' were also provided. Users may use either fixed brightness/contrast values or auto contrast/brightness settings, but not both."
1943 )
1945 if yml_format.version >= 1.0: 1945 ↛ 1980line 1945 didn't jump to line 1980 because the condition on line 1945 was always true
1946 detector = settings.get("type")
1947 mode = settings.get("mode")
1948 cb_limit = tbt.Limit(min=0.0, max=1.0)
1949 schema = Schema(
1950 {
1951 "brightness": Or(
1952 None,
1953 And(
1954 float,
1955 lambda x: ut.in_interval(
1956 x,
1957 limit=cb_limit,
1958 type=tbt.IntervalType.LEFT_OPEN,
1959 ),
1960 ),
1961 error=f"In step '{step_name}', requested fixed brightness of '{settings['brightness']}'. This is not within limits of '>{cb_limit.min}' and '<={cb_limit.max}'.",
1962 ),
1963 "contrast": Or(
1964 None,
1965 And(
1966 float,
1967 lambda x: ut.in_interval(
1968 x,
1969 limit=cb_limit,
1970 type=tbt.IntervalType.LEFT_OPEN,
1971 ),
1972 ),
1973 error=f"In step '{step_name}', requested fixed contrast of '{settings['contrast']}'. This is either not within limits of '>{cb_limit.min}' and '<={cb_limit.max}'.",
1974 ),
1975 },
1976 ignore_extra_keys=True,
1977 )
1979 # check detector type
1980 if not ut.valid_enum_entry(detector, tbt.DetectorType):
1981 raise ValueError(
1982 f"Unsupported detector type of '{detector}' on step '{step_name}'."
1983 )
1984 detector_type = tbt.DetectorType(detector)
1985 microscope_detector_types = available_detector_types(microscope=microscope)
1986 if detector_type.value not in microscope_detector_types: 1986 ↛ 1987line 1986 didn't jump to line 1987 because the condition on line 1986 was never true
1987 raise ValueError(
1988 f"Requested detector of {detector_type.value} is unavailable on this tool. Available detectors are: {microscope_detector_types}"
1989 )
1990 # make sure to set this detector as the active one to access modes
1991 img.detector_type(
1992 microscope=microscope,
1993 detector=detector_type,
1994 )
1996 # check detector mode
1997 if not ut.valid_enum_entry(mode, tbt.DetectorMode):
1998 raise ValueError(
1999 f'Unsupported detector mode of "{mode}" for "{detector}" detector'
2000 )
2001 detector_mode = tbt.DetectorMode(mode)
2002 microscope_detector_modes = available_detector_modes(microscope=microscope)
2003 if detector_mode.value not in microscope_detector_modes: 2003 ↛ 2004line 2003 didn't jump to line 2004 because the condition on line 2003 was never true
2004 raise ValueError(
2005 f"Requested mode of {detector_mode.value} for {detector_type.value} detector is invalid. Valid mode types are: {microscope_detector_modes}"
2006 )
2008 # check detector settings
2009 try:
2010 schema.validate(settings)
2011 except UnboundLocalError:
2012 raise ValueError(
2013 f"Error. Unsupported yml version {yml_format.version} provided."
2014 )
2017def validate_EBSD_EDS_settings(
2018 ebsd_oem: str,
2019 eds_oem: str,
2020) -> bool:
2021 """
2022 Check EBSD and EDS OEM and connection for supported OEMs.
2024 This function ensures that the specified EBSD and EDS OEMs are supported and that the connection settings are valid.
2026 ## Parameters
2028 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2029 - `connection_host` (`str`): The host for the microscope connection.
2030 - `connection_port` (`str`): The port for the microscope connection.
2031 - `ebsd_oem` (`str`): The OEM for the EBSD device.
2032 - `eds_oem` (`str`): The OEM for the EDS device.
2034 ## Returns
2036 - `bool`: True if the settings are valid, False otherwise.
2038 ## Raises
2040 - `NotImplementedError`: If differing EBSD and EDS OEMs are requested.
2041 - `ValueError`: If the EBSD or EDS OEM is unsupported.
2042 - `SystemError`: If the Laser API is not accessible.
2043 """
2044 # ensure same manufacturer for both EBSD and EDS
2045 if (ebsd_oem is not None) and (eds_oem is not None) and (ebsd_oem != eds_oem):
2046 raise NotImplementedError(
2047 f"Differing EBSD and EDS OEMs are not supported. Requested EBSD OEM of '{ebsd_oem}' and EDS OEM of '{eds_oem}'."
2048 )
2050 # Check EBSD OEM
2051 if not ut.valid_enum_entry(ebsd_oem, tbt.ExternalDeviceOEM): 2051 ↛ 2052line 2051 didn't jump to line 2052 because the condition on line 2051 was never true
2052 raise ValueError( 2052 ↛ exit, 2052 ↛ exit2 missed branches: 1) line 2052 didn't jump to the function exit, 2) line 2052 didn't except from function 'validate_EBSD_EDS_settings' because the raise on line 2052 wasn't executed
2053 f"Unsupported EBSD OEM of '{ebsd_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}"
2054 )
2055 ebsd_device = tbt.ExternalDeviceOEM(ebsd_oem)
2056 # Check EDS OEM
2057 if not ut.valid_enum_entry(eds_oem, tbt.ExternalDeviceOEM): 2057 ↛ 2058line 2057 didn't jump to line 2058 because the condition on line 2057 was never true
2058 raise ValueError( 2058 ↛ exit, 2058 ↛ exit2 missed branches: 1) line 2058 didn't jump to the function exit, 2) line 2058 didn't except from function 'validate_EBSD_EDS_settings' because the raise on line 2058 wasn't executed
2059 f"Unsupported EDS OEM of '{eds_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}"
2060 )
2061 eds_device = tbt.ExternalDeviceOEM(eds_oem)
2063 # exit if both devices are none, no need for laser control
2064 if ebsd_device == eds_device == tbt.ExternalDeviceOEM.NONE: 2064 ↛ 2068line 2064 didn't jump to line 2068 because the condition on line 2064 was always true
2065 return True
2067 # check EBSD and EDS connection
2068 try:
2069 fs_laser.tfs_laser
2070 except:
2071 raise SystemError(
2072 "EBSD and/or EDS control requested, but Laser API not accessible, so cannot use EBSD and EDS control. Please restart Laser API, or if not installed, change OEM to 'null', or leave blank in settings file."
2073 )
2075 ###################### PROPOSED CHANGE ######################
2076 # Retracting devices here is unnecessary
2077 # This causes devices to retract during validation
2078 # Device retraction is needed only when startin/ending experiments
2079 # Both of which are outside the scope of validation and already handled elsewhere
2080 ###################### PROPOSED CHANGE ######################
2081 # microscope = tbt.Microscope()
2082 # ut.connect_microscope(
2083 # microscope=microscope,
2084 # quiet_output=True,
2085 # connection_host=connection_host,
2086 # connection_port=connection_port,
2087 # )
2088 # if tbt.ExternalDeviceOEM(eds_oem) != tbt.ExternalDeviceOEM.NONE:
2089 # devices.retract_EDS(microscope=microscope)
2090 # if tbt.ExternalDeviceOEM(ebsd_oem) != tbt.ExternalDeviceOEM.NONE:
2091 # devices.retract_EBSD(microscope=microscope)
2092 # ut.disconnect_microscope(
2093 # microscope=microscope,
2094 # quiet_output=True,
2095 # )
2097 return True
2100def validate_general_settings(
2101 settings: dict,
2102 yml_format: tbt.YMLFormatVersion,
2103) -> bool:
2104 """
2105 Perform schema checking for general setting dictionary.
2107 This function validates the general settings dictionary based on the specified yml format. It checks the microscope connection and EBSD/EDS connection if valid OEMs are specified.
2109 ## Parameters
2111 - `settings` (`dict`): The dictionary containing the general settings.
2112 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2114 ## Returns
2116 - `bool`: True if the settings are valid, False otherwise.
2118 ## Raises
2120 - `ValueError`: If the general settings dictionary is empty, or if the settings do not satisfy the specified schema, or if the connection is invalid.
2121 - `NotImplementedError`: If the sectioning axis is unsupported.
2122 """
2124 if settings == {}: 2124 ↛ 2125line 2124 didn't jump to line 2125 because the condition on line 2124 was never true
2125 raise ValueError("General settings dictionary is empty.")
2127 slice_thickness_limit_um = Constants.slice_thickness_limit_um
2128 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_generic
2130 if yml_format.version >= 1.0: 2130 ↛ 2141line 2130 didn't jump to line 2141 because the condition on line 2130 was always true
2131 sectioning_axis = settings.get("sectioning_axis")
2132 connection_host = settings.get("connection_host")
2133 connection_port = settings.get("connection_port")
2134 ebsd_oem = settings.get("EBSD_OEM")
2135 eds_oem = settings.get("EDS_OEM")
2136 exp_dir = settings.get("exp_dir")
2137 h5_log_name = settings.get("h5_log_name")
2139 # Validate the non-numeric values
2140 # Check sectioning axis
2141 if not ut.valid_enum_entry(sectioning_axis, tbt.SectioningAxis): 2141 ↛ 2142line 2141 didn't jump to line 2142 because the condition on line 2141 was never true
2142 raise ValueError(f"Unsupported sectioning axis of {sectioning_axis}.")
2143 # TODO
2144 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z:
2145 raise NotImplementedError("Currently only Z-axis sectioning is supported.")
2146 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z:
2147 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_non_Z_sectioning # overwrite
2148 warnings.warn(
2149 "Pre-tilt value must be zero (0.0) degrees when using a sectioning axis other than 'Z'"
2150 )
2151 # Check connection host and port
2152 if not ut.valid_microscope_connection( 2152 ↛ 2156line 2152 didn't jump to line 2156 because the condition on line 2152 was never true
2153 host=connection_host,
2154 port=connection_port,
2155 ):
2156 raise ValueError(
2157 f"Unsupported connection with host of {connection_host} and port of {connection_port}."
2158 )
2160 # check EBSD and EDS
2161 validate_EBSD_EDS_settings(
2162 ebsd_oem=ebsd_oem,
2163 eds_oem=eds_oem,
2164 )
2166 # Check exp dir
2167 try:
2168 Path(exp_dir).mkdir(
2169 parents=True,
2170 exist_ok=True,
2171 )
2172 except TypeError:
2173 raise ValueError(
2174 f'Requested experimental directory of "{exp_dir}", which is not a valid path.'
2175 )
2176 # Check h5 log name
2177 if not isinstance(h5_log_name, str): 2177 ↛ 2178line 2177 didn't jump to line 2178 because the condition on line 2177 was never true
2178 raise ValueError(f'Unsupported h5 log name of "{h5_log_name}"')
2180 schema = Schema(
2181 {
2182 "slice_thickness_um": And(
2183 float,
2184 lambda x: ut.in_interval(
2185 x,
2186 limit=slice_thickness_limit_um,
2187 type=tbt.IntervalType.CLOSED,
2188 ),
2189 error=f"Requested slice thickness of {settings['slice_thickness_um']} um must satisfy '{slice_thickness_limit_um.min} <= slice_thickness_um <= {slice_thickness_limit_um.max}'",
2190 ),
2191 "max_slice_num": And(
2192 int,
2193 lambda x: x > 0,
2194 error=f"Requested max slice number of {settings['max_slice_num']} must satisfy '0 < max_slice_number'",
2195 ),
2196 "pre_tilt_deg": And(
2197 float,
2198 lambda x: ut.in_interval(
2199 x,
2200 limit=pre_tilt_limit_deg,
2201 type=tbt.IntervalType.CLOSED,
2202 ),
2203 error=f"Requested pre tilt of {settings['pre_tilt_deg']} degrees must satisfy '{pre_tilt_limit_deg.min} <= pre_tilt_deg <= {pre_tilt_limit_deg.max}'",
2204 ),
2205 "stage_translational_tol_um": And(
2206 float,
2207 lambda x: x > 0,
2208 error=f"Requested stage translational tolerance of {settings['stage_translational_tol_um']} um must be a positive float (greater than 0)",
2209 ),
2210 "stage_angular_tol_deg": And(
2211 float,
2212 lambda x: x > 0,
2213 error=f"Requested stage angular tolerance of {settings['stage_angular_tol_deg']} degrees must be a positive float (greater than 0)",
2214 ),
2215 "step_count": And(
2216 int,
2217 lambda x: x > 0,
2218 error=f"Requested step count of {settings['step_count']} must be a positive int (greater than 0)",
2219 ),
2220 },
2221 ignore_extra_keys=True,
2222 )
2224 try:
2225 schema.validate(settings)
2226 except UnboundLocalError:
2227 raise ValueError(
2228 f"Error. Unsupported yml version {yml_format.version} provided."
2229 )
2232def validate_scan_settings(
2233 microscope: tbt.Microscope,
2234 beam_type: tbt.BeamType,
2235 settings: dict,
2236 yml_format: tbt.YMLFormatVersion,
2237 step_name: str,
2238) -> bool:
2239 """
2240 Perform schema checking for scan setting dictionary.
2242 This function validates the scan settings dictionary based on the specified yml format and the scan limits of the microscope.
2244 ## Parameters
2246 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the scan settings.
2247 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion).
2248 - `settings` (`dict`): The dictionary containing the scan settings.
2249 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2250 - `step_name` (`str`): The name of the step in the .yml file.
2252 ## Returns
2254 - `bool`: True if the settings are valid, False otherwise.
2256 ## Raises
2258 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2259 """
2260 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
2261 selected_beam = ut.beam_type(specified_beam, microscope)
2263 """Schema checking for scan setting dictionary, format specified by yml_format"""
2264 if yml_format.version >= 1.0: 2264 ↛ 2271line 2264 didn't jump to line 2271 because the condition on line 2264 was always true
2265 limits = scan_limits(selected_beam)
2266 resolution = settings["resolution"]
2267 rotation_limit = limits.rotation_deg
2268 dwell_limit = limits.dwell_us
2270 # check resolution
2271 res = string_to_res(resolution)
2273 schema = Schema(
2274 {
2275 "resolution": And(
2276 str,
2277 lambda x: valid_string_resolution(x),
2278 error=f"In '{step_name}', resolution provided was {settings['resolution']}, but must be an integer width and height in format '[width]x[height]' with each dimension satisfying '{Constants.scan_resolution_limit.min} <= [value] <= {Constants.scan_resolution_limit.max}",
2279 ),
2280 "rotation_deg": And(
2281 float,
2282 lambda x: ut.in_interval(
2283 x,
2284 limit=rotation_limit,
2285 type=tbt.IntervalType.CLOSED,
2286 ),
2287 error=f"In '{step_name}', requested fixed rotation of '{settings['rotation_deg']}' degrees. This is not a float or not within limits of '>={rotation_limit.min}' and '<={rotation_limit.max}' degrees. Setting is of type {type(settings['rotation_deg'])}.",
2288 ),
2289 "dwell_time_us": And(
2290 float,
2291 lambda x: ut.in_interval(
2292 x,
2293 limit=dwell_limit,
2294 type=tbt.IntervalType.CLOSED,
2295 ),
2296 error=f"In '{step_name}', requested fixed dwell_time of '{settings['dwell_time_us']}' microseconds. This is a float or not within limits of '>={dwell_limit.min}' and '<={dwell_limit.max}' microseconds. Settting is of type {type(settings['dwell_time_us'])}.",
2297 ),
2298 },
2299 ignore_extra_keys=True,
2300 )
2302 try:
2303 schema.validate(settings)
2304 except UnboundLocalError:
2305 raise ValueError(
2306 f"Error. Unsupported yml version {yml_format.version} provided."
2307 )
2310def stage_position_settings(
2311 microscope: tbt.Microscope,
2312 step_name: str,
2313 general_settings: tbt.GeneralSettings,
2314 step_stage_settings: dict,
2315 yml_format: tbt.YMLFormatVersion,
2316) -> tbt.StageSettings:
2317 """
2318 Create a StagePositionUser object from settings, including validation.
2320 This function creates a `StagePositionUser` object from the provided settings and performs validation.
2322 ## Parameters
2324 - `microscope` (`tbt.Microscope`): The microscope object for which to set the stage position.
2325 - `step_name` (`str`): The name of the step in the .yml file.
2326 - `general_settings` (`tbt.GeneralSettings`): The general settings object.
2327 - `step_stage_settings` (`dict`): The dictionary containing the stage position settings for the step.
2328 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2330 ## Returns
2332 - `tbt.StageSettings`: The stage settings object.
2334 ## Raises
2336 - `NotImplementedError`: If the rotation side value is unsupported.
2337 - `ValueError`: If the stage position settings do not satisfy the specified schema.
2338 """
2340 if yml_format.version >= 1.0: 2340 ↛ 2349line 2340 didn't jump to line 2349 because the condition on line 2340 was always true
2341 pos_db = step_stage_settings.get("initial_position")
2343 rotation_side = step_stage_settings["rotation_side"]
2344 if not ut.valid_enum_entry(rotation_side, tbt.RotationSide):
2345 raise NotImplementedError(
2346 f"Unsupported rotation_side value of '{rotation_side}' of type '{type(rotation_side)}' in step '{step_name}', supported rotation_side values are: {[i.value for i in tbt.RotationSide]}."
2347 )
2349 validate_stage_position(
2350 microscope=microscope,
2351 step_name=step_name,
2352 settings=pos_db,
2353 yml_format=yml_format,
2354 )
2356 initial_position = tbt.StagePositionUser(
2357 x_mm=pos_db["x_mm"],
2358 y_mm=pos_db["y_mm"],
2359 z_mm=pos_db["z_mm"],
2360 r_deg=pos_db["r_deg"],
2361 t_deg=pos_db["t_deg"],
2362 )
2364 pretilt_angle_deg = general_settings.pre_tilt_deg
2365 sectioning_axis = general_settings.sectioning_axis
2367 stage_settings = tbt.StageSettings(
2368 microscope=microscope,
2369 initial_position=initial_position,
2370 pretilt_angle_deg=pretilt_angle_deg,
2371 sectioning_axis=sectioning_axis,
2372 rotation_side=tbt.RotationSide(rotation_side),
2373 # movement_mode=tbt.StageMovementMode.OUT_OF_PLANE,
2374 )
2376 return stage_settings
2379def validate_pulse_settings(
2380 settings: dict,
2381 yml_format: tbt.YMLFormatVersion,
2382 step_name: str,
2383) -> bool:
2384 """
2385 Perform schema checking for pulse setting dictionary.
2387 This function validates the pulse settings dictionary based on the specified yml format.
2389 ## Parameters
2391 - `settings` (`dict`): The dictionary containing the pulse settings.
2392 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2393 - `step_name` (`str`): The name of the step in the .yml file.
2395 ## Returns
2397 - `bool`: True if the settings are valid, False otherwise.
2399 ## Raises
2401 - `NotImplementedError`: If the wavelength or polarization value is unsupported.
2402 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2403 """
2404 if yml_format.version >= 1.0: 2404 ↛ 2431line 2404 didn't jump to line 2431 because the condition on line 2404 was always true
2405 wavelength_nm = settings.get("wavelength_nm")
2406 if not ut.valid_enum_entry(wavelength_nm, tbt.LaserWavelength):
2407 raise NotImplementedError(
2408 f"In 'laser' step_type for step '{step_name}', unsupported wavelength of '{wavelength_nm}' nm (data type {type(wavelength_nm)}), supported wavelengths are: {[i.value for i in tbt.LaserWavelength]}."
2409 )
2410 polarization = settings.get("polarization")
2411 if not ut.valid_enum_entry(polarization, tbt.LaserPolarization):
2412 raise NotImplementedError(
2413 f"In 'laser' step_type for step '{step_name}', unsupported laser polarization of '{polarization}', supported values are: {[i.value for i in tbt.LaserPolarization]}."
2414 )
2416 schema = Schema(
2417 {
2418 "divider": And(
2419 int,
2420 lambda x: x > 0,
2421 error=f"In 'laser' step_type for step '{step_name}', 'divider' parameter must be a positive integer greater than 0 but '{settings['divider']}' was requested.",
2422 ),
2423 "energy_uj": And(
2424 float,
2425 lambda x: x > 0,
2426 error=f"In 'laser' step_type for step '{step_name}', 'energy_uj' parameter must be a positive float greater than 0 but '{settings['energy_uj']}' was requested.",
2427 ),
2428 },
2429 ignore_extra_keys=True,
2430 )
2431 try:
2432 schema.validate(settings)
2433 except UnboundLocalError:
2434 raise ValueError(
2435 f"Error. Unsupported yml version {yml_format.version} provided."
2436 )
2439def validate_laser_optics_settings(
2440 settings: dict,
2441 yml_format: tbt.YMLFormatVersion,
2442 step_name: str,
2443) -> bool:
2444 """
2445 Perform schema checking for laser optics setting dictionary.
2447 This function validates the laser optics settings dictionary based on the specified yml format.
2449 ## Parameters
2451 - `settings` (`dict`): The dictionary containing the laser optics settings.
2452 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2453 - `step_name` (`str`): The name of the step in the .yml file.
2455 ## Returns
2457 - `bool`: True if the settings are valid, False otherwise.
2459 ## Raises
2461 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2462 """
2463 if yml_format.version >= 1.0: 2463 ↛ 2486line 2463 didn't jump to line 2486 because the condition on line 2463 was always true
2464 schema = Schema(
2465 {
2466 "objective_position_mm": And(
2467 float,
2468 lambda x: ut.in_interval(
2469 x,
2470 limit=Constants.laser_objective_limit_mm,
2471 type=tbt.IntervalType.CLOSED,
2472 ),
2473 error=f"In 'laser' step_type for step '{step_name}', 'objective_position_mm' parameter must be a float satisfying the following: {Constants.laser_objective_limit_mm.min} mm <= value <= {Constants.laser_objective_limit_mm.max} mm. '{settings['objective_position_mm']}' mm (of type {type(settings['objective_position_mm'])}) was requested.",
2474 ),
2475 "beam_shift_um_x": And(
2476 float,
2477 error=f"In 'laser' step_type for step '{step_name}', 'x' parameter for 'beam_shift_um' sub-dictioanry must be a float but '{settings['beam_shift_um_x']}' (of type {type(settings['beam_shift_um_x'])}) was requested.",
2478 ),
2479 "beam_shift_um_y": And(
2480 float,
2481 error=f"In 'laser' step_type for step '{step_name}', 'y' parameter for 'beam_shift_um' sub-dictioanry must be a float but '{settings['beam_shift_um_y']}' (of type {type(settings['beam_shift_um_y'])}) was requested.",
2482 ),
2483 },
2484 ignore_extra_keys=True,
2485 )
2486 try:
2487 schema.validate(settings)
2488 except UnboundLocalError:
2489 raise ValueError(
2490 f"Error. Unsupported yml version {yml_format.version} provided."
2491 )
2494def validate_laser_box_settings(
2495 settings: dict,
2496 yml_format: tbt.YMLFormatVersion,
2497 step_name: str,
2498) -> bool:
2499 """
2500 Perform schema checking for laser box pattern setting dictionary.
2502 This function validates the laser box pattern settings dictionary based on the specified yml format.
2504 ## Parameters
2506 - `settings` (`dict`): The dictionary containing the laser box pattern settings.
2507 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2508 - `step_name` (`str`): The name of the step in the .yml file.
2510 ## Returns
2512 - `bool`: True if the settings are valid, False otherwise.
2514 ## Raises
2516 - `NotImplementedError`: If the scan type or coordinate reference value is unsupported.
2517 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2518 """
2519 if yml_format.version >= 1.0: 2519 ↛ 2575line 2519 didn't jump to line 2575 because the condition on line 2519 was always true
2520 # scan type
2521 scan_type = settings.get("scan_type")
2522 scan_types = [
2523 tbt.LaserScanType.RASTER,
2524 tbt.LaserScanType.SERPENTINE,
2525 ]
2526 scan_type_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported scan type of '{scan_type}' for box pattern, supported scan types are: {[i.value for i in scan_types]}."
2527 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType):
2528 raise NotImplementedError(scan_type_error_msg)
2529 if tbt.LaserScanType(scan_type) not in scan_types:
2530 raise NotImplementedError(scan_type_error_msg)
2532 # coordinate reference
2533 coordinate_ref = settings.get("coordinate_ref")
2534 coord_refs = [
2535 tbt.CoordinateReference.CENTER,
2536 tbt.CoordinateReference.UPPER_CENTER,
2537 tbt.CoordinateReference.UPPER_LEFT,
2538 ]
2539 coord_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported coordinate reference of '{coordinate_ref}' for box pattern, supported coordinate references are: {[i.value for i in coord_refs]}."
2540 if not ut.valid_enum_entry(coordinate_ref, tbt.CoordinateReference):
2541 raise NotImplementedError(coord_error_msg)
2542 if tbt.CoordinateReference(coordinate_ref) not in coord_refs:
2543 raise NotImplementedError(coord_error_msg)
2545 schema = Schema(
2546 {
2547 "passes": And(
2548 int,
2549 lambda x: x > 0,
2550 error=f"In 'laser' step_type for step '{step_name}', 'passes' parameter in 'box' type pattern must be a positive integer. '{settings['passes']}' (of type {type(settings['passes'])}) was requested.",
2551 ),
2552 "size_x_um": And(
2553 float,
2554 lambda x: x > 0,
2555 error=f"In 'laser' step_type for step '{step_name}', 'size_x_um' parameter in 'box' type pattern must be a positive float. '{settings['size_x_um']}' mm (of type {type(settings['size_x_um'])}) was requested.",
2556 ),
2557 "size_y_um": And(
2558 float,
2559 lambda x: x > 0,
2560 error=f"In 'laser' step_type for step '{step_name}', 'size_y_um' parameter in 'box' type pattern must be a positive float. '{settings['size_y_um']}' mm (of type {type(settings['size_y_um'])}) was requested.",
2561 ),
2562 "pitch_x_um": And(
2563 float,
2564 lambda x: x > 0,
2565 error=f"In 'laser' step_type for step '{step_name}', 'pitch_x_um' parameter in 'box' type pattern must be a positive float. '{settings['pitch_x_um']}' mm (of type {type(settings['pitch_x_um'])}) was requested.",
2566 ),
2567 "pitch_y_um": And(
2568 float,
2569 lambda x: x > 0,
2570 error=f"In 'laser' step_type for step '{step_name}', 'pitch_y_um' parameter in 'box' type pattern must be a positive float. '{settings['pitch_y_um']}' mm (of type {type(settings['pitch_y_um'])}) was requested.",
2571 ),
2572 },
2573 ignore_extra_keys=True,
2574 )
2575 try:
2576 schema.validate(settings)
2577 except UnboundLocalError:
2578 raise ValueError(
2579 f"Error. Unsupported yml version {yml_format.version} provided."
2580 )
2583def validate_laser_line_settings(
2584 settings: dict,
2585 yml_format: tbt.YMLFormatVersion,
2586 step_name: str,
2587) -> bool:
2588 """
2589 Perform schema checking for laser line pattern setting dictionary.
2591 This function validates the laser line pattern settings dictionary based on the specified yml format.
2593 ## Parameters
2595 - `settings` (`dict`): The dictionary containing the laser line pattern settings.
2596 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2597 - `step_name` (`str`): The name of the step in the .yml file.
2599 ## Returns
2601 - `bool`: True if the settings are valid, False otherwise.
2603 ## Raises
2605 - `NotImplementedError`: If the scan type value is unsupported.
2606 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2607 """
2608 if yml_format.version >= 1.0:
2609 # scan type
2610 scan_type = settings.get("scan_type")
2611 scan_types = [
2612 tbt.LaserScanType.SINGLE,
2613 tbt.LaserScanType.LAP,
2614 ]
2615 scan_type_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported scan type of '{scan_type}' for line pattern, supported scan types are: {[i.value for i in scan_types]}."
2616 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType):
2617 raise NotImplementedError(scan_type_error_msg)
2618 if tbt.LaserScanType(scan_type) not in scan_types:
2619 raise NotImplementedError(scan_type_error_msg)
2621 schema = Schema(
2622 {
2623 "passes": And(
2624 int,
2625 lambda x: x > 0,
2626 error=f"In 'laser' step_type for step '{step_name}', 'passes' parameter in 'line' type pattern must be a positive integer. '{settings['passes']}' (of type {type(settings['passes'])}) was requested.",
2627 ),
2628 "size_um": And(
2629 float,
2630 lambda x: x > 0,
2631 error=f"In 'laser' step_type for step '{step_name}', 'size_um' parameter in 'line' type pattern must be a positive float. '{settings['size_um']}' mm (of type {type(settings['size_um'])}) was requested.",
2632 ),
2633 "pitch_um": And(
2634 float,
2635 lambda x: x > 0,
2636 error=f"In 'laser' step_type for step '{step_name}', 'pitch_um' parameter in 'line' type pattern must be a positive float. '{settings['pitch_um']}' mm (of type {type(settings['pitch_um'])}) was requested.",
2637 ),
2638 },
2639 ignore_extra_keys=True,
2640 )
2641 try:
2642 schema.validate(settings)
2643 except UnboundLocalError:
2644 raise ValueError(
2645 f"Error. Unsupported yml version {yml_format.version} provided."
2646 )
2649def validate_laser_mode_settings(
2650 settings: dict,
2651 yml_format: tbt.YMLFormatVersion,
2652 step_name: str,
2653) -> bool:
2654 """
2655 Perform schema checking for laser mode setting dictionary.
2657 This function validates the laser mode settings dictionary based on the specified yml format.
2659 ## Parameters
2661 - `settings` (`dict`): The dictionary containing the laser mode settings.
2662 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2663 - `step_name` (`str`): The name of the step in the .yml file.
2665 ## Returns
2667 - `bool`: True if the settings are valid, False otherwise.
2669 ## Raises
2671 - `NotImplementedError`: If the laser pattern mode value is unsupported.
2672 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
2673 """
2674 if yml_format.version >= 1.0: 2674 ↛ 2722line 2674 didn't jump to line 2722 because the condition on line 2674 was always true
2675 mode = settings.get("mode")
2676 if not ut.valid_enum_entry(mode, tbt.LaserPatternMode):
2677 raise NotImplementedError(
2678 f"In 'laser' step_type for step '{step_name}', unsupported laser pattern mode of '{mode}', supported values are: {[i.value for i in tbt.LaserPatternMode]}."
2679 )
2681 schema = Schema(
2682 {
2683 "rotation_deg": And(
2684 float,
2685 error=f"In 'laser' step_type for step '{step_name}', 'rotation_deg' parameter must be a float. '{settings['rotation_deg']}' degrees (of type {type(settings['rotation_deg'])}) was requested.",
2686 ),
2687 },
2688 ignore_extra_keys=True,
2689 )
2690 schema_fine = Schema(
2691 {
2692 "pixel_dwell_ms": Or(
2693 None,
2694 "null",
2695 "None",
2696 error=f"In 'laser' step_type for step '{step_name}', 'pixel_dwell_ms' parameter does not apply to the selected 'fine' milling mode. Set 'pixel_dwell_ms' to 'null' 'None' or leave the entry blank to continue.",
2697 ),
2698 "pulses_per_pixel": And(
2699 int,
2700 lambda x: x > 0,
2701 error=f"In 'laser' step_type for step '{step_name}', 'pulses_per_pixel' parameter is required for pattern type of 'fine'. 'pulses_per_pixel' must be a positive integer greater than 0. '{settings['pulses_per_pixel']}' (of type {type(settings['pulses_per_pixel'])}) was requested.",
2702 ),
2703 },
2704 ignore_extra_keys=True,
2705 )
2706 schema_coarse = Schema( 2706 ↛ exitline 2706 didn't jump to the function exit
2707 {
2708 "pixel_dwell_ms": And(
2709 float,
2710 lambda x: x > 0.0,
2711 error=f"In 'laser' step_type for step '{step_name}', 'pixel_dwell_ms' parameter is required for pattern type of 'coarse'. 'pixel_dwell_ms' must be a positive float greater than 0. '{settings['pixel_dwell_ms']}' (of type {type(settings['pixel_dwell_ms'])}) was requested.",
2712 ),
2713 "pulses_per_pixel": Or(
2714 None,
2715 "null",
2716 "None",
2717 error=f"In 'laser' step_type for step '{step_name}', 'pulses_per_pixel' parameter does not apply to the selected 'coarse' milling mode. Set 'pulses_per_pixel' to 'null' 'None' or leave the entry blank to continue.",
2718 ),
2719 },
2720 ignore_extra_keys=True,
2721 )
2722 try:
2723 schema.validate(settings)
2724 if mode == "fine": 2724 ↛ 2726line 2724 didn't jump to line 2726 because the condition on line 2724 was always true
2725 schema_fine.validate(settings)
2726 if mode == "coarse": 2726 ↛ 2727line 2726 didn't jump to line 2727 because the condition on line 2726 was never true
2727 schema_coarse.validate(settings)
2728 except UnboundLocalError:
2729 raise ValueError(
2730 f"Error. Unsupported yml version {yml_format.version} provided."
2731 )
2734def validate_laser_pattern_settings(
2735 settings: dict,
2736 yml_format: tbt.YMLFormatVersion,
2737 step_name: str,
2738) -> tbt.LaserPatternType:
2739 """
2740 Perform schema checking for laser pattern setting dictionary.
2742 This function validates the laser pattern settings dictionary based on the specified yml format and determines the type of pattern (box or line).
2744 ## Parameters
2746 - `settings` (`dict`): The dictionary containing the laser pattern settings.
2747 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2748 - `step_name` (`str`): The name of the step in the .yml file.
2750 ## Returns
2752 - `tbt.LaserPatternType`: The type of the laser pattern (box or line).
2754 ## Raises
2756 - `KeyError`: If required settings are missing from the .yml file or if multiple pattern types are specified.
2757 - `ValueError`: If the laser pattern settings are invalid.
2758 """
2759 if yml_format.version >= 1.0: 2759 ↛ exitline 2759 didn't return from function 'validate_laser_pattern_settings' because the condition on line 2759 was always true
2760 # determine type of pattern (only one allowed)
2761 type_set_db = settings.get("type")
2762 if type_set_db is None: 2762 ↛ 2763line 2762 didn't jump to line 2763 because the condition on line 2762 was never true
2763 raise KeyError(
2764 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'laser' step_type for step '{step_name}'."
2765 )
2767 box_settings = type_set_db.get("box")
2768 line_settings = type_set_db.get("line")
2769 if box_settings is None: 2769 ↛ 2770line 2769 didn't jump to line 2770 because the condition on line 2769 was never true
2770 box_pattern = False
2771 else:
2772 box_pattern = not ut.none_value_dictionary(box_settings)
2773 if line_settings is None: 2773 ↛ 2774line 2773 didn't jump to line 2774 because the condition on line 2773 was never true
2774 line_pattern = False
2775 else:
2776 line_pattern = not ut.none_value_dictionary(line_settings)
2777 if box_pattern == line_pattern == False: 2777 ↛ 2778line 2777 didn't jump to line 2778 because the condition on line 2777 was never true
2778 raise KeyError(
2779 f"Invalid .yml file in 'laser' step_type for step '{step_name}'. No pattern type settings found."
2780 )
2781 if box_pattern == line_pattern == True: 2781 ↛ 2782line 2781 didn't jump to line 2782 because the condition on line 2781 was never true
2782 raise KeyError(
2783 f"Invalid .yml file in 'laser' step_type for step '{step_name}'. Pattern settings for one and only one type are allowed. Type settings found for both 'box' and 'line' type. Please leave one set of type settings completely blank, enter 'null' for each parameter, or remove the unused subdictionary completely from the .yml file."
2784 )
2785 validate_laser_mode_settings(
2786 settings=settings,
2787 yml_format=yml_format,
2788 step_name=step_name,
2789 )
2791 if box_pattern: 2791 ↛ 2798line 2791 didn't jump to line 2798 because the condition on line 2791 was always true
2792 validate_laser_box_settings(
2793 settings=box_settings,
2794 yml_format=yml_format,
2795 step_name=step_name,
2796 )
2797 return tbt.LaserPatternType.BOX
2798 elif line_pattern:
2799 validate_laser_line_settings(
2800 settings=line_settings,
2801 yml_format=yml_format,
2802 step_name=step_name,
2803 )
2804 return tbt.LaserPatternType.LINE
2805 else:
2806 raise ValueError(
2807 f"Invalid laser pattern settings for step {step_name}. Supported types are {[i.value for i in tbt.LaserPatternType]}"
2808 )
2811def validate_fib_pattern_settings(
2812 microscope: tbt.Microscope,
2813 settings: dict,
2814 yml_format: tbt.YMLFormatVersion,
2815 step_name: str,
2816) -> Union[
2817 tbt.FIBRectanglePattern,
2818 tbt.FIBRegularCrossSection,
2819 tbt.FIBCleaningCrossSection,
2820 tbt.FIBStreamPattern,
2821]:
2822 """
2823 Perform schema checking for FIB pattern setting dictionary.
2825 This function validates the FIB pattern settings dictionary based on the specified yml format and determines the type of pattern (rectangle, regular cross section, cleaning cross section, or selected area).
2827 ## Parameters
2829 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the FIB pattern settings.
2830 - `settings` (`dict`): The dictionary containing the FIB pattern settings.
2831 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
2832 - `step_name` (`str`): The name of the step in the .yml file.
2834 ## Returns
2836 - `Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern]`: The validated FIB pattern object.
2838 ## Raises
2840 - `KeyError`: If required settings are missing from the .yml file or if multiple pattern types are specified.
2841 - `ValueError`: If the application file is unsupported or invalid for the specified pattern type.
2842 """
2844 if yml_format.version >= 1.0: 2844 ↛ 2904line 2844 didn't jump to line 2904 because the condition on line 2844 was always true
2845 application_file = settings.get("application_file")
2846 # determine type of pattern (only one allowed)
2847 type_set_db = settings.get("type")
2848 if type_set_db is None: 2848 ↛ 2849line 2848 didn't jump to line 2849 because the condition on line 2848 was never true
2849 raise KeyError(
2850 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'fib' step_type for step '{step_name}'."
2851 )
2852 rectangle_settings = type_set_db.get("rectangle")
2853 regular_cross_section_settings = type_set_db.get("regular_cross_section")
2854 cleaning_cross_section_settings = type_set_db.get("cleaning_cross_section")
2855 selected_area_settings = type_set_db.get("selected_area")
2857 pattern_settings = [
2858 rectangle_settings,
2859 regular_cross_section_settings,
2860 cleaning_cross_section_settings,
2861 selected_area_settings,
2862 ]
2863 (
2864 rectangle_pattern,
2865 regular_cross_section_pattern,
2866 cleaning_cross_section_pattern,
2867 selected_area_pattern,
2868 ) = (None, None, None, None)
2869 # pattern_names = [val.value for val in tbt.FIBPatternType] # remembers order
2870 # assert pattern_names == [
2871 # "rectangle",
2872 # "regular_cross_section",
2873 # "cleaning_cross_section",
2874 # "selected_area",
2875 # ]
2876 pattern_names = [
2877 "rectangle",
2878 "regular_cross_section",
2879 "cleaning_cross_section",
2880 "selected_area",
2881 ]
2882 pattern_types = [
2883 rectangle_pattern,
2884 regular_cross_section_pattern,
2885 cleaning_cross_section_pattern,
2886 selected_area_pattern,
2887 ]
2889 for type in range(len(pattern_settings)):
2890 db = pattern_settings[type]
2891 if db is None: 2891 ↛ 2892line 2891 didn't jump to line 2892 because the condition on line 2891 was never true
2892 pattern_types[type] = False
2893 else:
2894 pattern_types[type] = not ut.none_value_dictionary(db)
2896 # one and only one type of pattern can have settings
2897 if sum(pattern_types) != 1: 2897 ↛ 2898line 2897 didn't jump to line 2898 because the condition on line 2897 was never true
2898 raise KeyError( 2898 ↛ exit, 2898 ↛ exit2 missed branches: 1) line 2898 didn't jump to the function exit, 2) line 2898 didn't except from function 'validate_fib_pattern_settings' because the raise on line 2898 wasn't executed
2899 f"Invalid .yml file in 'fib' step_type for step '{step_name}'. Pattern settings for one and only one type are allowed. Please provide settings for only one of the supported pattern types: {[name for name in pattern_names]}. For unused pattern types, leave the type settings completely blank, enter 'null' for each parameter, or remove the unused subdictionary completely from the .yml file."
2900 )
2901 pattern_type = tbt.FIBPatternType(pattern_names[pattern_types.index(True)])
2903 # check application file
2904 valid_applications = application_files(microscope=microscope)
2905 # TODO make this print out pretty
2906 # display_applications = ",".join(valid_applications)
2907 # display_applications = ut.tabular_list(data=valid_applications)
2908 if application_file not in valid_applications: 2908 ↛ 2909line 2908 didn't jump to line 2909 because the condition on line 2908 was never true
2909 raise ValueError(
2910 f"Unsupported FIB application file of '{application_file}' on step '{step_name}. Supported applications on this microscope are: \n{valid_applications}'"
2911 )
2913 if pattern_type == tbt.FIBPatternType.RECTANGLE: 2913 ↛ 2956line 2913 didn't jump to line 2956 because the condition on line 2913 was always true
2914 validate_fib_box_settings(
2915 settings=rectangle_settings,
2916 yml_format=yml_format,
2917 step_name=step_name,
2918 pattern_type=pattern_type,
2919 )
2921 pattern = tbt.FIBPattern(
2922 application=application_file,
2923 type=pattern_type,
2924 geometry=tbt.FIBRectanglePattern(
2925 center_um=tbt.Point(
2926 x=rectangle_settings.get("center").get("x_um"),
2927 y=rectangle_settings.get("center").get("y_um"),
2928 ),
2929 width_um=rectangle_settings.get("width_um"),
2930 height_um=rectangle_settings.get("height_um"),
2931 depth_um=rectangle_settings.get("depth_um"),
2932 scan_direction=tbt.FIBPatternScanDirection(
2933 rectangle_settings.get("scan_direction")
2934 ),
2935 scan_type=tbt.FIBPatternScanType(rectangle_settings.get("scan_type")),
2936 ),
2937 )
2938 # make sure application file is valid for this pattern type:
2939 try:
2940 geometry = pattern.geometry
2941 microscope.patterning.set_default_application_file(pattern.application)
2942 microscope.patterning.create_rectangle(
2943 center_x=geometry.center_um.x * Conversions.UM_TO_M,
2944 center_y=geometry.center_um.y * Conversions.UM_TO_M,
2945 width=geometry.width_um * Conversions.UM_TO_M,
2946 height=geometry.height_um * Conversions.UM_TO_M,
2947 depth=geometry.depth_um * Conversions.UM_TO_M,
2948 )
2949 microscope.patterning.clear_patterns()
2950 except:
2951 raise ValueError(
2952 f'Invalid application file of "{pattern.application}" for Rectangle pattern type. Please select or create an appropriate application file.'
2953 )
2955 return pattern
2956 elif pattern_type == tbt.FIBPatternType.REGULAR_CROSS_SECTION:
2957 validate_fib_box_settings(
2958 settings=regular_cross_section_settings,
2959 yml_format=yml_format,
2960 step_name=step_name,
2961 pattern_type=pattern_type,
2962 )
2963 pattern = tbt.FIBPattern(
2964 application=application_file,
2965 type=pattern_type,
2966 geometry=tbt.FIBRegularCrossSection(
2967 center_um=tbt.Point(
2968 x=regular_cross_section_settings.get("center").get("x_um"),
2969 y=regular_cross_section_settings.get("center").get("y_um"),
2970 ),
2971 width_um=regular_cross_section_settings.get("width_um"),
2972 height_um=regular_cross_section_settings.get("height_um"),
2973 depth_um=regular_cross_section_settings.get("depth_um"),
2974 scan_direction=tbt.FIBPatternScanDirection(
2975 regular_cross_section_settings.get("scan_direction")
2976 ),
2977 scan_type=tbt.FIBPatternScanType(
2978 regular_cross_section_settings.get("scan_type")
2979 ),
2980 ),
2981 )
2982 # make sure application file is valid for this pattern type:
2983 try:
2984 geometry = pattern.geometry
2985 microscope.patterning.set_default_application_file(pattern.application)
2986 microscope.patterning.create_regular_cross_section(
2987 center_x=geometry.center_um.x * Conversions.UM_TO_M,
2988 center_y=geometry.center_um.y * Conversions.UM_TO_M,
2989 width=geometry.width_um * Conversions.UM_TO_M,
2990 height=geometry.height_um * Conversions.UM_TO_M,
2991 depth=geometry.depth_um * Conversions.UM_TO_M,
2992 )
2993 microscope.patterning.clear_patterns()
2994 except:
2995 raise ValueError(
2996 f'Invalid application file of "{pattern.application}" for Regular Cross Section pattern type. Please select or create an appropriate application file.'
2997 )
2998 return pattern
2999 elif pattern_type == tbt.FIBPatternType.CLEANING_CROSS_SECTION:
3000 validate_fib_box_settings(
3001 settings=cleaning_cross_section_settings,
3002 yml_format=yml_format,
3003 step_name=step_name,
3004 pattern_type=pattern_type,
3005 )
3006 pattern = tbt.FIBPattern(
3007 application=application_file,
3008 type=pattern_type,
3009 geometry=tbt.FIBCleaningCrossSection(
3010 center_um=tbt.Point(
3011 x=cleaning_cross_section_settings.get("center").get("x_um"),
3012 y=cleaning_cross_section_settings.get("center").get("y_um"),
3013 ),
3014 width_um=cleaning_cross_section_settings.get("width_um"),
3015 height_um=cleaning_cross_section_settings.get("height_um"),
3016 depth_um=cleaning_cross_section_settings.get("depth_um"),
3017 scan_direction=tbt.FIBPatternScanDirection(
3018 cleaning_cross_section_settings.get("scan_direction")
3019 ),
3020 scan_type=tbt.FIBPatternScanType(
3021 cleaning_cross_section_settings.get("scan_type")
3022 ),
3023 ),
3024 )
3025 # make sure application file is valid for this pattern type:
3026 try:
3027 geometry = pattern.geometry
3028 microscope.patterning.set_default_application_file(pattern.application)
3029 microscope.patterning.create_cleaning_cross_section(
3030 center_x=geometry.center_um.x * Conversions.UM_TO_M,
3031 center_y=geometry.center_um.y * Conversions.UM_TO_M,
3032 width=geometry.width_um * Conversions.UM_TO_M,
3033 height=geometry.height_um * Conversions.UM_TO_M,
3034 depth=geometry.depth_um * Conversions.UM_TO_M,
3035 )
3036 microscope.patterning.clear_patterns()
3037 except:
3038 raise ValueError(
3039 f'Invalid application file of "{pattern.application}" for Cleaning Cross Section pattern type. Please select or create an appropriate application file.'
3040 )
3041 return pattern
3042 elif pattern_type == tbt.FIBPatternType.SELECTED_AREA:
3043 validate_fib_selected_area_settings(
3044 settings=selected_area_settings,
3045 yml_format=yml_format,
3046 step_name=step_name,
3047 pattern_type=pattern_type,
3048 )
3049 pattern = tbt.FIBPattern(
3050 application=application_file,
3051 type=pattern_type,
3052 geometry=tbt.FIBStreamPattern(
3053 dwell_us=selected_area_settings.get("dwell_us"),
3054 repeats=selected_area_settings.get("repeats"),
3055 recipe=Path(selected_area_settings.get("recipe_file")),
3056 mask=Path(selected_area_settings.get("mask_file")),
3057 ),
3058 )
3060 # make sure application file is valid for this pattern type:
3061 try:
3062 microscope.patterning.set_default_application_file(pattern.application)
3063 microscope.patterning.create_rectangle(
3064 center_x=0.0,
3065 center_y=0.0,
3066 width=10.0e-6,
3067 height=10.0e-6,
3068 depth=1.0e-6,
3069 )
3070 microscope.patterning.clear_patterns()
3071 except:
3072 raise ValueError(
3073 f'Invalid application file of "{pattern.application}" for Selected Area pattern. Please use an application file for Rectangle milling.'
3074 )
3076 return pattern
3077 else:
3078 raise KeyError(
3079 f"Invalid pattern type of {pattern_type}. Supported pattern types are: {[i.value for i in tbt.FIBPatternType]}"
3080 )
3083def validate_fib_box_settings(
3084 settings: dict,
3085 yml_format: tbt.YMLFormatVersion,
3086 step_name: str,
3087 pattern_type: tbt.FIBPatternType,
3088) -> bool:
3089 """
3090 Perform schema checking for FIB box pattern setting dictionary.
3092 This function validates the FIB box pattern settings dictionary based on the specified yml format.
3094 ## Parameters
3096 - `settings` (`dict`): The dictionary containing the FIB box pattern settings.
3097 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
3098 - `step_name` (`str`): The name of the step in the .yml file.
3099 - `pattern_type` (`tbt.FIBPatternType`): The type of the FIB pattern.
3101 ## Returns
3103 - `bool`: True if the settings are valid, False otherwise.
3105 ## Raises
3107 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
3108 """
3109 if yml_format.version >= 1.0: 3109 ↛ 3150line 3109 didn't jump to line 3150 because the condition on line 3109 was always true
3110 # flattens nested dictionary, adding "_" separator
3111 flat_settings = ut._flatten(settings)
3112 schema = Schema(
3113 {
3114 "center_x_um": And(
3115 float,
3116 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'x_um' parameter for pattern 'center' sub-dictionary must be a float. '{flat_settings['center_x_um']}' of type '{type(flat_settings['center_x_um'])}' was requested.",
3117 ),
3118 "center_y_um": And(
3119 float,
3120 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'y_um' parameter for pattern 'center' sub-dictionary must be a float. '{flat_settings['center_y_um']}' of type '{type(flat_settings['center_y_um'])}' was requested.",
3121 ),
3122 "width_um": And(
3123 float,
3124 lambda x: x > 0,
3125 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'width_um' parameter must be a float. '{flat_settings['width_um']}' of type '{type(flat_settings['width_um'])}' was requested.",
3126 ),
3127 "height_um": And(
3128 float,
3129 lambda x: x > 0,
3130 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'height_um' parameter must be a float. '{flat_settings['height_um']}' of type '{type(flat_settings['height_um'])}' was requested.",
3131 ),
3132 "depth_um": And(
3133 float,
3134 lambda x: x > 0,
3135 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'depth_um' parameter must be a float. '{flat_settings['depth_um']}' of type '{type(flat_settings['depth_um'])}' was requested.",
3136 ),
3137 "scan_direction": And(
3138 str,
3139 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanDirection),
3140 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'scan_direction' parameter must be a valid scan direction type. '{flat_settings['scan_direction']}' was requested but supported values are: {[i.value for i in tbt.FIBPatternScanDirection]}",
3141 ),
3142 "scan_type": And(
3143 str,
3144 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanType),
3145 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'scan_type' parameter must be a valid scan type type. '{flat_settings['scan_type']}' was requested but supported values are: {[i.value for i in tbt.FIBPatternScanType]}",
3146 ),
3147 },
3148 ignore_extra_keys=True,
3149 )
3150 try:
3151 schema.validate(flat_settings)
3152 except UnboundLocalError:
3153 raise ValueError(
3154 f"Error. Unsupported yml version {yml_format.version} provided."
3155 )
3156 return True
3159def validate_fib_selected_area_settings(
3160 settings: dict,
3161 yml_format: tbt.YMLFormatVersion,
3162 step_name: str,
3163 pattern_type: tbt.FIBPatternType,
3164) -> bool:
3165 """
3166 Perform schema checking for FIB selected area pattern setting dictionary.
3168 This function validates the FIB selected area pattern settings dictionary based on the specified yml format.
3170 ## Parameters
3172 - `settings` (`dict`): The dictionary containing the FIB selected area pattern settings.
3173 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
3174 - `step_name` (`str`): The name of the step in the .yml file.
3175 - `pattern_type` (`tbt.FIBPatternType`): The type of the FIB pattern.
3177 ## Returns
3179 - `bool`: True if the settings are valid, False otherwise.
3181 ## Raises
3183 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema.
3184 """
3185 if yml_format.version >= 1.0:
3186 schema = Schema(
3187 {
3188 "dwell_us": And(
3189 float,
3190 lambda x: x > 0,
3191 # check all float error versions of modulus
3192 Or(
3193 lambda x: math.isclose(
3194 x % Constants.stream_pattern_base_dwell_us,
3195 0,
3196 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3197 ),
3198 lambda x: math.isclose(
3199 x % Constants.stream_pattern_base_dwell_us,
3200 Constants.stream_pattern_base_dwell_us,
3201 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3202 ),
3203 lambda x: math.isclose(
3204 x % Constants.stream_pattern_base_dwell_us,
3205 -Constants.stream_pattern_base_dwell_us,
3206 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3207 ),
3208 ),
3209 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'dwell_us' parameter must be a positive float and an integer multiple of the base dwell time, {Constants.stream_pattern_base_dwell_us * Conversions.US_TO_NS} ns. '{settings['dwell_us']}' us of type '{type(settings['dwell_us'])}' was requested.",
3210 ),
3211 "repeats": And(
3212 int,
3213 lambda x: x > 0,
3214 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'repeats' parameter must be a positive integer. '{settings['repeats']}' of type '{type(settings['repeats'])}' was requested.",
3215 ),
3216 "recipe_file": And(
3217 str,
3218 lambda x: Path(x).is_file(),
3219 lambda x: Path(x).suffix == ".py",
3220 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'recipe_file' parameter must be a valid file path string with the extension '.py' and must already exist. The recipe file '{settings['recipe_file']}' was requested.",
3221 ),
3222 "mask_file": And(
3223 str,
3224 lambda x: Path(x).suffix == ".tif",
3225 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'mask' parameter must be a file path string with a file extension of '.tif'. '{settings['mask_file']}' of type '{type(settings['mask_file'])}' was requested.",
3226 ),
3227 },
3228 ignore_extra_keys=True,
3229 )
3230 try:
3231 schema.validate(settings)
3232 except UnboundLocalError:
3233 raise ValueError(
3234 f"Error. Unsupported yml version {yml_format.version} provided."
3235 )
3236 return True
3239def step(
3240 microscope: tbt.Microscope,
3241 # slice_number: str,
3242 step_name: str,
3243 step_settings: dict,
3244 general_settings: tbt.GeneralSettings,
3245 yml_format: tbt.YMLFormatVersion,
3246) -> tbt.Step:
3247 """
3248 Create a step object for different step types, including validation.
3250 This function creates a `Step` object for the specified step type and performs validation.
3252 ## Parameters
3254 - `microscope` (`tbt.Microscope`): The microscope object for which to create the step.
3255 - `step_name` (`str`): The name of the step in the .yml file.
3256 - `step_settings` (`dict`): The dictionary containing the step settings.
3257 - `general_settings` (`tbt.GeneralSettings`): The general settings object.
3258 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file.
3260 ## Returns
3262 - `tbt.Step`: The step object.
3264 ## Raises
3266 - `NotImplementedError`: If the step type is unsupported.
3267 - `KeyError`: If required settings are missing or invalid.
3268 """
3270 # parsing settings
3271 step_type_value = step_settings[yml_format.step_general_key][
3272 yml_format.step_type_key
3273 ]
3274 if not ut.valid_enum_entry(step_type_value, tbt.StepType):
3275 raise NotImplementedError(
3276 f"Unsupported step type of '{step_type_value}', for step name '{step_name}' supported types are: {[i.value for i in tbt.StepType]}."
3277 )
3278 step_type = tbt.StepType(step_type_value)
3280 step_number = step_settings[yml_format.step_general_key][yml_format.step_number_key]
3281 if not isinstance(step_number, int) or (step_number < 1): 3281 ↛ 3282line 3281 didn't jump to line 3282 because the condition on line 3281 was never true
3282 raise KeyError(
3283 f"Invalid step number of '{step_number}', for step name '{step_name}'. Must be a positive integer greater than 0."
3284 )
3285 step_frequency = step_settings[yml_format.step_general_key][
3286 yml_format.step_frequency_key
3287 ]
3288 if not isinstance(step_frequency, int) or (step_frequency < 1): 3288 ↛ 3289line 3288 didn't jump to line 3289 because the condition on line 3288 was never true
3289 raise KeyError(
3290 f"Invalid step frequency of '{step_frequency}', for step name '{step_name}'. Must be a positive integer greater than 0."
3291 )
3292 stage_db = step_settings[yml_format.step_general_key][
3293 yml_format.step_stage_settings_key
3294 ]
3296 # check and validate stage
3297 stage_settings = stage_position_settings(
3298 microscope=microscope,
3299 step_name=step_name,
3300 general_settings=general_settings,
3301 step_stage_settings=stage_db,
3302 yml_format=yml_format,
3303 )
3305 # TODO
3306 # operation_settings, could use match statement in python >= 3.10
3307 if step_type == tbt.StepType.EBSD:
3308 operation_settings = ebsd(
3309 microscope=microscope,
3310 step_settings=step_settings,
3311 step_name=step_name,
3312 yml_format=yml_format,
3313 )
3314 if step_type == tbt.StepType.EDS:
3315 operation_settings = eds(
3316 microscope=microscope,
3317 step_settings=step_settings,
3318 step_name=step_name,
3319 yml_format=yml_format,
3320 )
3321 if step_type == tbt.StepType.IMAGE:
3322 operation_settings = image(
3323 microscope=microscope,
3324 step_settings=step_settings,
3325 step_name=step_name,
3326 yml_format=yml_format,
3327 )
3328 if step_type == tbt.StepType.LASER:
3329 operation_settings = laser(
3330 microscope=microscope,
3331 step_settings=step_settings,
3332 step_name=step_name,
3333 yml_format=yml_format,
3334 )
3335 if step_type == tbt.StepType.CUSTOM:
3336 operation_settings = custom(
3337 microscope=microscope,
3338 step_settings=step_settings,
3339 step_name=step_name,
3340 yml_format=yml_format,
3341 )
3342 if step_type == tbt.StepType.FIB:
3343 operation_settings = fib(
3344 microscope=microscope,
3345 step_settings=step_settings,
3346 step_name=step_name,
3347 yml_format=yml_format,
3348 )
3350 step_object = tbt.Step(
3351 type=step_type,
3352 name=step_name,
3353 number=step_number,
3354 frequency=step_frequency,
3355 stage=stage_settings,
3356 operation_settings=operation_settings,
3357 )
3359 return step_object