Coverage for src/pytribeam/factory.py: 0%
670 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
1#!/usr/bin/python3
2"""
3Factory Module
4==============
6This module contains functions for creating and validating various settings and objects used in the microscope operations. The functions are organized to handle different step types, including EBSD, EDS, IMAGE, LASER, CUSTOM, and FIB.
8Functions
9---------
10active_fib_applications(microscope: tbt.Microscope) -> list
11 Retrieve a list of all active FIB (Focused Ion Beam) application files from the microscope.
13active_beam_with_settings(microscope: tbt.Microscope) -> tbt.Beam
14 Retrieve the current active beam and its settings from the microscope to create a beam object.
16active_detector_settings(microscope: tbt.Microscope) -> tbt.Detector
17 Retrieve the current active detector settings from the microscope to create a detector object.
19active_image_settings(microscope: tbt.Microscope) -> tbt.ImageSettings
20 Retrieve the current active image settings from the microscope to create an image settings object.
22active_imaging_device(microscope: tbt.Microscope) -> tbt.Beam
23 Determine the active imaging device and return the corresponding internal beam type object with null beam settings.
25active_scan_settings(microscope: tbt.Microscope) -> tbt.Scan
26 Retrieve the current active scan settings from the microscope to create a scan object.
28active_stage_position_settings(microscope: tbt.Microscope) -> tbt.StagePositionUser
29 Retrieve the current stage position in the raw coordinate system and user units [mm, deg].
31active_laser_state() -> tbt.LaserState
32 Retrieve the current state of the laser, including various properties that can be quickly read.
34active_laser_settings(microscope: tbt.Microscope) -> tbt.LaserSettings
35 Retrieve the current active laser settings from the microscope to create a laser settings object.
37available_detector_types(microscope: tbt.Microscope) -> List[str]
38 Retrieve the available detector types on the current microscope.
40available_detector_modes(microscope: tbt.Microscope) -> List[str]
41 Retrieve the available detector modes on the current microscope.
43beam_object_type(type: tbt.BeamType) -> tbt.Beam
44 Retrieve the beam object type based on the given beam type.
46stage_limits(microscope: tbt.Microscope) -> tbt.StageLimits
47 Retrieve the stage limits from the current microscope connection.
49beam_limits(selected_beam: property, beam_type: tbt.BeamType) -> tbt.BeamLimits
50 Retrieve the beam limits for the selected beam and beam type.
52general(general_db: dict, yml_format: tbt.YMLFormatVersion) -> tbt.GeneralSettings
53 Convert a general settings dictionary to a built-in type and perform schema checking.
55laser_box_pattern(settings: dict) -> tbt.LaserBoxPattern
56 Convert a dictionary of laser box pattern settings to a `LaserBoxPattern` object.
58laser_line_pattern(settings: dict) -> tbt.LaserLinePattern
59 Convert a dictionary of laser line pattern settings to a `LaserLinePattern` object.
61laser(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.LaserSettings
62 Convert a laser step from a .yml file to microscope settings for performing laser milling.
64image(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.ImageSettings
65 Convert an image step from a .yml file to microscope settings for capturing an image.
67fib(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.FIBSettings
68 Convert a FIB step from a .yml file to microscope settings for performing a FIB operation.
70enforce_beam_type(beam_type, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> bool
71 Enforce a specific beam type is used for an operation based on a dictionary.
73ebsd(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.EBSDSettings
74 Convert an EBSD step from a .yml file to microscope settings for performing an EBSD operation.
76eds(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.EDSSettings
77 Convert an EDS step from a .yml file to microscope settings for performing an EDS operation.
79custom(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.CustomSettings
80 Convert a custom step from a .yml file to custom settings for the microscope.
82scan_limits(selected_beam: property) -> tbt.ScanLimits
83 Retrieve the scan settings limits for the selected beam.
85string_to_res(input: str) -> tbt.Resolution
86 Convert a string in the format "{{width}}x{{height}}" to a resolution object.
88valid_string_resolution(string_resolution: str) -> bool
89 Validate a string resolution.
91validate_auto_cb_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
92 Perform schema checking for auto contrast/brightness setting dictionary.
94validate_stage_position(microscope: tbt.Microscope, step_name: str, settings: dict, yml_format: tbt.YMLFormatVersion) -> bool
95 Perform schema checking for stage position dictionary.
97validate_beam_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
98 Perform schema checking for beam setting dictionary.
100validate_detector_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
101 Perform schema checking for detector setting dictionary.
103validate_EBSD_EDS_settings(ebsd_oem: str, eds_oem: str) -> bool
104 Check EBSD and EDS OEM and connection for supported OEMs.
106validate_general_settings(settings: dict, yml_format: tbt.YMLFormatVersion) -> bool
107 Perform schema checking for general setting dictionary.
109validate_scan_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
110 Perform schema checking for scan setting dictionary.
112stage_position_settings(microscope: tbt.Microscope, step_name: str, general_settings: tbt.GeneralSettings, step_stage_settings: dict, yml_format: tbt.YMLFormatVersion) -> tbt.StageSettings
113 Create a StagePositionUser object from settings, including validation.
115validate_pulse_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
116 Perform schema checking for pulse setting dictionary.
118validate_laser_optics_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
119 Perform schema checking for laser optics setting dictionary.
121validate_laser_box_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
122 Perform schema checking for laser box pattern setting dictionary.
124validate_laser_line_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
125 Perform schema checking for laser line pattern setting dictionary.
127validate_laser_mode_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool
128 Perform schema checking for laser mode setting dictionary.
130validate_laser_pattern_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> tbt.LaserPatternType
131 Perform schema checking for laser pattern setting dictionary.
133validate_fib_pattern_settings(microscope: tbt.Microscope, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern]
134 Perform schema checking for FIB pattern setting dictionary.
136validate_fib_box_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str, pattern_type: tbt.FIBPatternType) -> bool
137 Perform schema checking for FIB box pattern setting dictionary.
139validate_fib_selected_area_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str, pattern_type: tbt.FIBPatternType) -> bool
140 Perform schema checking for FIB selected area pattern setting dictionary.
142step(microscope: tbt.Microscope, step_name: str, step_settings: dict, general_settings: tbt.GeneralSettings, yml_format: tbt.YMLFormatVersion) -> tbt.Step
143 Create a step object for different step types, including validation.
144"""
146## python standard libraries
147from pathlib import Path
148from typing import List, Union
149import warnings
150from functools import singledispatch
151import math
154# 3rd party libraries
155from schema import And, Or, Schema
157# Local
158import pytribeam.insertable_devices as devices
159import pytribeam.image as img
160import pytribeam.utilities as ut
161import pytribeam.stage as stage
162from pytribeam.constants import Conversions, Constants
163from pytribeam.fib import application_files
165try:
166 import pytribeam.laser as fs_laser
167except:
168 pass
169import pytribeam.types as tbt
172def active_fib_applications(
173 microscope: tbt.Microscope,
174) -> list:
175 """
176 Retrieve a list of all active FIB (Focused Ion Beam) patterning application files from the microscope.
178 Parameters
179 ----------
180 microscope : tbt.Microscope
181 The microscope object from which to retrieve the application files.
183 Returns
184 -------
185 list
186 A list of active FIB patterning application files.
187 """
188 return microscope.patterning.list_all_application_files()
191def active_beam_with_settings(
192 microscope: tbt.Microscope,
193) -> tbt.Beam:
194 """
195 Retrieve the current active beam and its settings from the microscope to create a beam object.
197 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.
199 Parameters
200 ----------
201 microscope : tbt.Microscope
202 The microscope object from which to retrieve the active beam and its settings.
204 Returns
205 -------
206 tbt.Beam
207 The active beam object with its settings.
208 """
209 selected_beam = active_imaging_device(microscope=microscope)
210 beam = ut.beam_type(selected_beam, microscope)
212 voltage_kv = round(beam.high_voltage.value * Conversions.V_TO_KV, 6)
213 voltage_tol_kv = round(voltage_kv * Constants.voltage_tol_ratio, 6)
215 current_na = round(beam.beam_current.value * Conversions.A_TO_NA, 6)
216 current_tol_na = round(current_na * Constants.current_tol_ratio, 6)
218 hfw_mm = round(beam.horizontal_field_width.value * Conversions.M_TO_MM, 6)
220 working_dist_mm = round(beam.working_distance.value * Conversions.M_TO_MM, 6)
222 active_settings = tbt.BeamSettings(
223 voltage_kv=voltage_kv,
224 current_na=current_na,
225 hfw_mm=hfw_mm,
226 working_dist_mm=working_dist_mm,
227 voltage_tol_kv=voltage_tol_kv,
228 current_tol_na=current_tol_na,
229 )
231 return type(selected_beam)(settings=active_settings)
234def active_detector_settings(
235 microscope: tbt.Microscope,
236) -> tbt.Detector:
237 """
238 Retrieve the current active detector settings from the microscope to create a detector object.
240 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.
242 Parameters
243 ----------
244 microscope : tbt.Microscope
245 The microscope object from which to retrieve the active detector settings.
247 Returns
248 -------
249 tbt.Detector
250 The active detector object with its settings.
251 """
253 detector_type = microscope.detector.type.value
254 detector_mode = microscope.detector.mode.value
255 brightness = microscope.detector.brightness.value
256 contrast = microscope.detector.contrast.value
257 auto_cb_settings = tbt.ScanArea(
258 left=None,
259 top=None,
260 width=None,
261 height=None,
262 )
263 custom_settings = None
265 active_detector = tbt.Detector(
266 type=detector_type,
267 mode=detector_mode,
268 brightness=brightness,
269 contrast=contrast,
270 auto_cb_settings=auto_cb_settings,
271 custom_settings=custom_settings,
272 )
274 return active_detector
277def active_image_settings(microscope: tbt.Microscope) -> tbt.ImageSettings:
278 """
279 Retrieve the current active image settings from the microscope to create an image settings object.
281 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.
283 Parameters
284 ----------
285 microscope : tbt.Microscope
286 The microscope object from which to retrieve the active image settings.
288 Returns
289 -------
290 tbt.ImageSettings
291 The active image settings object.
292 """
293 beam = active_beam_with_settings(microscope=microscope)
294 detector = active_detector_settings(microscope=microscope)
295 scan = active_scan_settings(microscope=microscope)
296 bit_depth = Constants.default_color_depth
298 active_image_settings = tbt.ImageSettings(
299 microscope=microscope,
300 beam=beam,
301 detector=detector,
302 scan=scan,
303 bit_depth=bit_depth,
304 )
306 return active_image_settings
309def active_imaging_device(microscope: tbt.Microscope) -> tbt.Beam:
310 """
311 Determine the active imaging device and return the corresponding internal beam type object with null beam settings.
313 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.
315 Parameters
316 ----------
317 microscope : tbt.Microscope
318 The microscope object from which to determine the active imaging device.
320 Returns
321 -------
322 tbt.Beam
323 The active beam object with null beam settings.
325 Raises
326 ------
327 ValueError
328 If the currently selected device is neither an electron beam nor an ion beam.
329 """
330 curr_device = tbt.Device(microscope.imaging.get_active_device())
331 if curr_device == tbt.Device.ELECTRON_BEAM:
332 selected_beam = beam_object_type(type=tbt.BeamType.ELECTRON)(
333 settings=tbt.BeamSettings()
334 )
335 elif curr_device == tbt.Device.ION_BEAM:
336 selected_beam = beam_object_type(type=tbt.BeamType.ION)(
337 settings=tbt.BeamSettings()
338 )
339 else:
340 raise ValueError(
341 f"Currently selected device {curr_device}, make sure a quadrant in xTUI with either an Electron beam or Ion beam active is selected."
342 )
343 return selected_beam
346def active_scan_settings(
347 microscope: tbt.Microscope,
348) -> tbt.Scan:
349 """
350 Retrieve the current active scan settings from the microscope to create a scan object.
352 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.
354 Parameters
355 ----------
356 microscope : tbt.Microscope
357 The microscope object from which to retrieve the active scan settings.
359 Returns
360 -------
361 tbt.Scan
362 The active scan object with its settings.
363 """
364 selected_beam = active_imaging_device(microscope=microscope)
365 beam = ut.beam_type(selected_beam, microscope)
367 rotation_deg = beam.scanning.rotation.value * Conversions.RAD_TO_DEG
368 dwell_time_us = beam.scanning.dwell_time.value * Conversions.S_TO_US
369 current_res = beam.scanning.resolution.value
370 res = string_to_res(current_res)
371 resolution = tbt.PresetResolution(res)
373 active_scan = tbt.Scan(
374 rotation_deg=rotation_deg,
375 dwell_time_us=dwell_time_us,
376 resolution=resolution,
377 # mode=mode,
378 )
380 return active_scan
383def active_stage_position_settings(microscope: tbt.Microscope) -> tbt.StagePositionUser:
384 """
385 Retrieve the current stage position in the raw coordinate system and user units [mm, deg].
387 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.
389 Parameters
390 ----------
391 microscope : tbt.Microscope
392 The microscope object from which to retrieve the current stage position.
394 Returns
395 -------
396 tbt.StagePositionUser
397 The current stage position in user units [mm, deg].
398 """
399 stage.coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
400 # encoder positions (pos) are in meters and radians
401 direct_encoder_pos = microscope.specimen.stage.current_position
402 x_m, y_m, z_m = direct_encoder_pos.x, direct_encoder_pos.y, direct_encoder_pos.z
403 r_rad, t_rad = direct_encoder_pos.r, direct_encoder_pos.t
404 coord_system_str = direct_encoder_pos.coordinate_system
406 encoder_pos = tbt.StagePositionEncoder(
407 x=x_m, y=y_m, z=z_m, r=r_rad, t=t_rad, coordinate_system=coord_system_str
408 )
409 user_pos = stage.encoder_to_user_position(encoder_pos)
411 # ensure r-axis is kept in axis limit
412 # ------------------------------------------------
413 # THIS IS NOT NEEDED, AUTOSCRIPT DOES THIS ALREADY
414 # ------------------------------------------------
415 # if not ut.in_interval(
416 # val=user_pos.r_deg,
417 # limit=Constants.rotation_axis_limit_deg,
418 # type=tbt.IntervalType.RIGHT_OPEN,
419 # ):
420 # # used as right-open internal: 180.0 is not valid and should be converted to -180.0
421 # while user_pos.r_deg >= Constants.rotation_axis_limit_deg.max:
422 # new_r_deg = user_pos.r_deg - 360.0
423 # while user_pos.r_deg < Constants.rotation_axis_limit_deg.min:
424 # new_r_deg = user_pos.r_deg + 360.0
425 # new_r_deg = round(new_r_deg, 6)
426 # user_pos = tbt.StagePositionUser(
427 # x_mm=user_pos.x_mm,
428 # y_mm=user_pos.y_mm,
429 # z_mm=user_pos.z_mm,
430 # r_deg=new_r_deg,
431 # t_deg=user_pos.t_deg,
432 # )
434 return user_pos
437def active_laser_state() -> tbt.LaserState:
438 """
439 Retrieve the current state of the laser, including various properties that can be quickly read.
441 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.
443 Returns
444 -------
445 tbt.LaserState
446 The current state of the laser, including wavelength, frequency, pulse divider, pulse energy, objective position, beam shift, pattern, and expected pattern duration.
448 Raises
449 ------
450 KeyError
451 If an unsupported LaserPatternType is encountered.
452 """
453 vals = fs_laser.tfs_laser.Laser_ReadValues()
454 vals["objective_position_mm"] = fs_laser.tfs_laser.LIP_GetZPosition()
455 vals["beam_shift_um_x"] = fs_laser.tfs_laser.BeamShift_Get_X()
456 vals["beam_shift_um_y"] = fs_laser.tfs_laser.BeamShift_Get_Y()
457 vals["shutter_state"] = fs_laser.tfs_laser.Shutter_GetState()
458 vals["pattern"] = fs_laser.tfs_laser.Patterning_ReadValues()
459 vals["expected_pattern_duration_s"] = (
460 fs_laser.tfs_laser.Patterning_GetExpectedDuration()
461 )
463 pattern_db = vals["pattern"]
464 pattern_type = pattern_db["patternType"].lower()
465 if not ut.valid_enum_entry(pattern_type, tbt.LaserPatternType):
466 raise KeyError(
467 f"Unsupported LaserPatternType of {pattern_type}, supported types include: {[i.value for i in tbt.LaserPatternType]}"
468 )
469 pattern_type = tbt.LaserPatternType(pattern_type)
470 mode = tbt.LaserPatternMode(pattern_db["patterningMode"].lower())
471 if mode == tbt.LaserPatternMode.COARSE:
472 pixel_dwell_ms = pattern_db["dwellTime"]
473 pulses_per_pixel = None
474 elif mode == tbt.LaserPatternMode.FINE:
475 pixel_dwell_ms = None
476 pulses_per_pixel = pattern_db["pulsesPerPixel"]
477 rotation_deg = pattern_db["patternRotation_deg"]
479 if pattern_type == tbt.LaserPatternType.BOX:
480 geometry = tbt.LaserBoxPattern(
481 passes=pattern_db["passes"],
482 size_x_um=pattern_db["xSize_um"],
483 size_y_um=pattern_db["ySize_um"],
484 pitch_x_um=pattern_db["xPitch_um"],
485 pitch_y_um=pattern_db["yPitch_um"],
486 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()),
487 coordinate_ref=tbt.CoordinateReference(
488 pattern_db["coordReference"].lower()
489 ),
490 )
491 if pattern_type == tbt.LaserPatternType.LINE:
492 geometry = tbt.LaserLinePattern(
493 passes=pattern_db["passes"],
494 size_um=pattern_db["xSize_um"],
495 pitch_um=pattern_db["xPitch_um"],
496 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()),
497 )
499 pattern = tbt.LaserPattern(
500 mode=mode,
501 rotation_deg=rotation_deg,
502 geometry=geometry,
503 pulses_per_pixel=pulses_per_pixel,
504 pixel_dwell_ms=pixel_dwell_ms,
505 )
507 state = tbt.LaserState(
508 wavelength_nm=vals["wavelength_nm"],
509 frequency_khz=vals["frequency_kHz"],
510 pulse_divider=vals["pulse_divider"],
511 pulse_energy_uj=vals["pulse_energy_uJ"],
512 objective_position_mm=vals["objective_position_mm"],
513 beam_shift_um=tbt.Point(x=vals["beam_shift_um_x"], y=vals["beam_shift_um_y"]),
514 pattern=pattern,
515 expected_pattern_duration_s=vals["expected_pattern_duration_s"],
516 )
518 return state
521def active_laser_settings(microscope: tbt.Microscope) -> tbt.LaserSettings:
522 """
523 Retrieve the current active laser settings from the microscope to create a laser settings object.
525 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.
527 Parameters
528 ----------
529 microscope : tbt.Microscope
530 The microscope object from which to retrieve the active laser settings.
532 Returns
533 -------
534 tbt.LaserSettings
535 The active laser settings object.
536 """
537 state = active_laser_state()
539 settings = tbt.LaserSettings(
540 microscope=microscope,
541 pulse=tbt.LaserPulse(
542 wavelength_nm=state.wavelength_nm,
543 divider=state.pulse_divider,
544 energy_uj=state.pulse_energy_uj,
545 polarization=tbt.LaserPolarization.VERTICAL, # TODO, can't read this, can only set it
546 ),
547 objective_position_mm=state.objective_position_mm,
548 beam_shift_um=state.beam_shift_um,
549 pattern=state.pattern,
550 )
552 return settings
555def available_detector_types(microscope: tbt.Microscope) -> List[str]:
556 """
557 Retrieve the available detector types on the current microscope.
559 This function returns a list of available detector types on the current microscope.
561 Parameters
562 ----------
563 microscope : tbt.Microscope
564 The microscope object from which to retrieve the available detector types.
566 Returns
567 -------
568 List[str]
569 A list of available detector types.
570 """
571 detectors = microscope.detector.type.available_values
572 return detectors
575def available_detector_modes(microscope: tbt.Microscope) -> List[str]:
576 """
577 Retrieve the available detector modes on the current microscope.
579 This function returns a list of available detector modes on the current microscope.
581 Parameters
582 ----------
583 microscope : tbt.Microscope
584 The microscope object from which to retrieve the available detector modes.
586 Returns
587 -------
588 List[str]
589 A list of available detector modes.
590 """
591 modes = microscope.detector.mode.available_values
592 # available = [tbt.DetectorType(i) for i in modes]
593 return modes
596def beam_object_type(type: tbt.BeamType) -> tbt.Beam:
597 """
598 Retrieve the beam object type based on the given beam type.
600 This function returns the appropriate beam object type (electron or ion) based on the provided beam type.
602 Parameters
603 ----------
604 type : tbt.BeamType
605 The type of the beam (electron or ion).
607 Returns
608 -------
609 tbt.Beam
610 The corresponding beam object type.
612 Raises
613 ------
614 NotImplementedError
615 If the provided beam type is unsupported.
616 """
617 if not isinstance(type, tbt.BeamType):
618 raise ValueError("Electron and Ion are the only allowed beam object types.")
619 if type.value == "electron":
620 return tbt.ElectronBeam
621 if type.value == "ion":
622 return tbt.IonBeam
625def stage_limits(microscope: tbt.Microscope) -> tbt.StageLimits:
626 """
627 Retrieve the stage limits from the current microscope connection.
629 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.
631 Parameters
632 ----------
633 microscope : tbt.Microscope
634 The microscope object from which to retrieve the stage limits.
636 Returns
637 -------
638 tbt.StageLimits
639 The stage limits for the X, Y, Z, R, and T axes in user units (mm and degrees).
640 """
641 stage.coordinate_system(microscope=microscope)
642 # x position
643 min_x_mm = (
644 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).min
645 * Conversions.M_TO_MM
646 )
647 max_x_mm = (
648 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).max
649 * Conversions.M_TO_MM
650 )
651 # y position
652 min_y_mm = (
653 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).min
654 * Conversions.M_TO_MM
655 )
656 max_y_mm = (
657 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).max
658 * Conversions.M_TO_MM
659 )
660 # z position
661 min_z_mm = (
662 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).min
663 * Conversions.M_TO_MM
664 )
665 max_z_mm = (
666 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).max
667 * Conversions.M_TO_MM
668 )
669 # r position
670 min_r_deg = Constants.rotation_axis_limit_deg.min
671 max_r_deg = Constants.rotation_axis_limit_deg.max
672 # t position
673 min_t_deg = (
674 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).min
675 * Conversions.RAD_TO_DEG
676 )
677 max_t_deg = (
678 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).max
679 * Conversions.RAD_TO_DEG
680 )
682 return tbt.StageLimits(
683 x_mm=tbt.Limit(min=min_x_mm, max=max_x_mm),
684 y_mm=tbt.Limit(min=min_y_mm, max=max_y_mm),
685 z_mm=tbt.Limit(min=min_z_mm, max=max_z_mm),
686 r_deg=tbt.Limit(min=min_r_deg, max=max_r_deg),
687 t_deg=tbt.Limit(min=min_t_deg, max=max_t_deg),
688 )
691def beam_limits(
692 selected_beam: property,
693 beam_type: tbt.BeamType,
694) -> tbt.BeamLimits:
695 """
696 Retrieve the beam limits for the selected beam and beam type.
698 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.
700 Parameters
701 ----------
702 selected_beam : property
703 The selected beam property from which to retrieve the limits.
704 beam_type : tbt.BeamType
705 The type of the beam (electron or ion).
707 Returns
708 -------
709 tbt.BeamLimits
710 The beam limits for voltage, current, HFW, and working distance in user units (kV, nA, mm).
712 Raises
713 ------
714 ValueError
715 If the beam type is unsupported.
716 """
717 # voltage range
718 min_kv = selected_beam.high_voltage.limits.min * Conversions.V_TO_KV
719 max_kv = selected_beam.high_voltage.limits.max * Conversions.V_TO_KV
721 # current range
722 if beam_type == tbt.BeamType.ELECTRON:
723 min_na = selected_beam.beam_current.limits.min * Conversions.A_TO_NA
724 max_na = selected_beam.beam_current.limits.max * Conversions.A_TO_NA
725 if beam_type == tbt.BeamType.ION:
726 available_currents = selected_beam.beam_current.available_values
727 min_na = min(available_currents) * Conversions.A_TO_NA
728 max_na = max(available_currents) * Conversions.A_TO_NA
730 # hfw range
731 min_hfw_mm = selected_beam.horizontal_field_width.limits.min * Conversions.M_TO_MM
732 max_hfw_mm = selected_beam.horizontal_field_width.limits.max * Conversions.M_TO_MM
734 # working_dist range
735 min_wd_mm = selected_beam.working_distance.limits.min * Conversions.M_TO_MM
736 max_wd_mm = selected_beam.working_distance.limits.max * Conversions.M_TO_MM
738 return tbt.BeamLimits(
739 voltage_kv=tbt.Limit(min=min_kv, max=max_kv),
740 current_na=tbt.Limit(min=min_na, max=max_na),
741 hfw_mm=tbt.Limit(min=min_hfw_mm, max=max_hfw_mm),
742 working_distance_mm=tbt.Limit(min=min_wd_mm, max=max_wd_mm),
743 )
746def general(
747 general_db: dict,
748 yml_format: tbt.YMLFormatVersion,
749) -> tbt.GeneralSettings:
750 """
751 Convert a general settings dictionary to a built-in type and perform schema checking.
753 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.
755 Parameters
756 ----------
757 general_db : dict
758 The general settings dictionary from the .yml file.
759 yml_format : tbt.YMLFormatVersion
760 The format specified by the version of the .yml file.
762 Returns
763 -------
764 tbt.GeneralSettings
765 The general settings object.
767 Raises
768 ------
769 NotImplementedError
770 If the provided yml format is unsupported.
771 """
773 if not yml_format in tbt.YMLFormatVersion:
774 raise NotImplementedError(
775 """Due to the complexity and number of variables,
776 image objects should only be constructed using a yml file."""
777 )
778 if not isinstance(yml_format, tbt.YMLFormatVersion):
779 raise NotImplementedError(f"Unsupported yml format of {yml_format}.")
781 validate_general_settings(
782 settings=general_db,
783 yml_format=yml_format,
784 )
786 if yml_format.version >= 1.0:
787 # slice thickness
788 slice_thickness_um = general_db["slice_thickness_um"]
789 # max slice number
790 max_slice_number = general_db["max_slice_num"]
791 # pre tilt
792 pre_tilt_deg = general_db["pre_tilt_deg"]
793 # sectioning axis
794 sectioning_axis = tbt.SectioningAxis(general_db["sectioning_axis"])
795 # stage tolerance
796 stage_tolerance = tbt.StageTolerance(
797 translational_um=general_db["stage_translational_tol_um"],
798 angular_deg=general_db["stage_angular_tol_deg"],
799 )
800 # connection
801 connection = tbt.MicroscopeConnection(
802 general_db["connection_host"], general_db["connection_port"]
803 )
804 # EBSD OEM
805 ebsd_oem = tbt.ExternalDeviceOEM(general_db["EBSD_OEM"])
806 # EDS OEM
807 eds_oem = tbt.ExternalDeviceOEM(general_db["EDS_OEM"])
808 # exp dir
809 exp_dir = general_db["exp_dir"]
810 # h5 log name
811 h5_log_name = general_db["h5_log_name"]
812 # remove log file extension if the user provided it
813 log_extension = Constants.logfile_extension
814 if h5_log_name.endswith(log_extension):
815 h5_log_name = h5_log_name[: -len(log_extension)]
816 # step count
817 step_count = general_db["step_count"]
818 yml_version = 1.0
820 general_settings = tbt.GeneralSettings(
821 yml_version=yml_version,
822 slice_thickness_um=slice_thickness_um,
823 max_slice_number=max_slice_number,
824 pre_tilt_deg=pre_tilt_deg,
825 sectioning_axis=sectioning_axis,
826 stage_tolerance=stage_tolerance,
827 connection=connection,
828 EBSD_OEM=ebsd_oem,
829 EDS_OEM=eds_oem,
830 exp_dir=Path(exp_dir),
831 h5_log_name=h5_log_name,
832 step_count=step_count,
833 )
835 return general_settings
838def laser_box_pattern(settings: dict) -> tbt.LaserBoxPattern:
839 """
840 Convert a dictionary of laser box pattern settings to a `LaserBoxPattern` object.
842 This function takes a dictionary of laser box pattern settings and converts it to a `LaserBoxPattern` object.
844 Parameters
845 ----------
846 settings : dict
847 The dictionary containing laser box pattern settings.
849 Returns
850 -------
851 tbt.LaserBoxPattern
852 The laser box pattern object.
853 """
854 return tbt.LaserBoxPattern(
855 passes=settings["passes"],
856 size_x_um=settings["size_x_um"],
857 size_y_um=settings["size_y_um"],
858 pitch_x_um=settings["pitch_x_um"],
859 pitch_y_um=settings["pitch_y_um"],
860 scan_type=tbt.LaserScanType(settings["scan_type"]),
861 coordinate_ref=tbt.CoordinateReference(settings["coordinate_ref"]),
862 )
865def laser_line_pattern(settings: dict) -> tbt.LaserBoxPattern:
866 """
867 Convert a dictionary of laser line pattern settings to a `LaserLinePattern` object.
869 This function takes a dictionary of laser line pattern settings and converts it to a `LaserLinePattern` object.
871 Parameters
872 ----------
873 settings : dict
874 The dictionary containing laser line pattern settings.
876 Returns
877 -------
878 tbt.LaserLinePattern
879 The laser line pattern object.
880 """
881 return tbt.LaserLinePattern(
882 passes=settings["passes"],
883 size_um=settings["size_um"],
884 pitch_um=settings["pitch_um"],
885 scan_type=tbt.LaserScanType(settings["scan_type"]),
886 )
889def laser(
890 microscope: tbt.Microscope,
891 step_settings: dict,
892 step_name: str,
893 yml_format: tbt.YMLFormatVersion,
894) -> tbt.LaserSettings:
895 """
896 Convert a laser step from a .yml file to microscope settings for performing laser milling.
898 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.
900 Parameters
901 ----------
902 microscope : tbt.Microscope
903 The microscope object for which to set the laser settings.
904 step_settings : dict
905 The dictionary containing the laser step settings from the .yml file.
906 step_name : str
907 The name of the step in the .yml file.
908 yml_format : tbt.YMLFormatVersion
909 The format specified by the version of the .yml file.
911 Returns
912 -------
913 tbt.LaserSettings
914 The laser settings object.
916 Raises
917 ------
918 KeyError
919 If required settings are missing from the .yml file.
920 """
921 if yml_format.version >= 1.0:
922 # pulse settings
923 pulse_set_db = step_settings.get("pulse")
924 if pulse_set_db is None:
925 raise KeyError(
926 f"Invalid .yml file, no 'pulse' settings found in 'laser' step_type for step '{step_name}'."
927 )
928 # laser optics settings
929 optics_set_db = {
930 "objective_position_mm": step_settings.get("objective_position_mm"),
931 "beam_shift_um_x": step_settings.get("beam_shift").get("x_um"),
932 "beam_shift_um_y": step_settings.get("beam_shift").get("y_um"),
933 }
934 # pattern settings
935 pattern_set_db = step_settings.get("pattern")
936 if pattern_set_db is None:
937 raise KeyError(
938 f"Invalid .yml file, no 'pattern' settings found in 'laser' step_type for step '{step_name}'."
939 )
940 line_pattern_db = pattern_set_db.get("type").get("line")
941 box_pattern_db = pattern_set_db.get("type").get("box")
943 validate_pulse_settings(
944 settings=pulse_set_db,
945 yml_format=yml_format,
946 step_name=step_name,
947 )
948 pulse = tbt.LaserPulse(
949 wavelength_nm=tbt.LaserWavelength(pulse_set_db["wavelength_nm"]),
950 divider=pulse_set_db["divider"],
951 energy_uj=pulse_set_db["energy_uj"],
952 polarization=tbt.LaserPolarization(pulse_set_db["polarization"]),
953 )
955 validate_laser_optics_settings(
956 settings=optics_set_db,
957 yml_format=yml_format,
958 step_name=step_name,
959 )
961 pattern_type = validate_laser_pattern_settings(
962 settings=pattern_set_db,
963 yml_format=yml_format,
964 step_name=step_name,
965 )
966 if pattern_type == tbt.LaserPatternType.BOX:
967 geometry = laser_box_pattern(box_pattern_db)
968 if pattern_type == tbt.LaserPatternType.LINE:
969 geometry = laser_line_pattern(line_pattern_db)
970 pattern = tbt.LaserPattern(
971 mode=tbt.LaserPatternMode(pattern_set_db["mode"]),
972 rotation_deg=pattern_set_db["rotation_deg"],
973 pulses_per_pixel=pattern_set_db["pulses_per_pixel"],
974 pixel_dwell_ms=pattern_set_db["pixel_dwell_ms"],
975 geometry=geometry,
976 )
978 laser_settings = tbt.LaserSettings(
979 microscope=microscope,
980 pulse=pulse,
981 objective_position_mm=optics_set_db["objective_position_mm"],
982 beam_shift_um=tbt.Point(
983 x=optics_set_db["beam_shift_um_x"],
984 y=optics_set_db["beam_shift_um_y"],
985 ),
986 pattern=pattern,
987 )
989 return laser_settings
992def image(
993 microscope: tbt.Microscope,
994 step_settings: dict,
995 step_name: str,
996 yml_format: tbt.YMLFormatVersion,
997) -> tbt.ImageSettings:
998 """
999 Convert an image step from a .yml file to microscope settings for capturing an image.
1001 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.
1003 Parameters
1004 ----------
1005 microscope : tbt.Microscope
1006 The microscope object for which to set the image settings.
1007 step_settings : dict
1008 The dictionary containing the image step settings from the .yml file.
1009 step_name : str
1010 The name of the step in the .yml file.
1011 yml_format : tbt.YMLFormatVersion
1012 The format specified by the version of the .yml file.
1014 Returns
1015 -------
1016 tbt.ImageSettings
1017 The image settings object.
1019 Raises
1020 ------
1021 KeyError
1022 If required settings are missing from the .yml file.
1023 NotImplementedError
1024 If the provided beam type is unsupported.
1025 ValueError
1026 If invalid scan rotation is requested with dynamic focus or tilt correction, or if the bit depth is unsupported.
1027 """
1028 if yml_format.version >= 1.0:
1029 step_general = step_settings.get(yml_format.step_general_key)
1030 if step_general is None:
1031 raise KeyError(
1032 f"Invalid .yml file, no 'step_general' settings found in step '{step_name}'."
1033 )
1034 step_type = step_general.get(yml_format.step_type_key)
1035 if step_type is None:
1036 raise KeyError(
1037 f"Invalid .yml file, no 'step_type' settings found in step '{step_name}'."
1038 )
1039 # beam settings
1040 beam_set_db = step_settings.get("beam")
1041 if beam_set_db is None:
1042 raise KeyError(
1043 f"Invalid .yml file, no 'beam' settings found in '{step_type}' step_type for step '{step_name}'."
1044 )
1045 beam_type_value = beam_set_db.get("type")
1046 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
1047 raise NotImplementedError(
1048 f"Unsupported beam type of '{beam_type_value}', supported beam types are: {[i.value for i in tbt.BeamType]}."
1049 )
1050 beam_type = tbt.BeamType(beam_set_db.get("type"))
1052 # detector settings
1053 detector_set_db = step_settings.get("detector")
1054 if detector_set_db is None:
1055 raise KeyError(
1056 f"Invalid .yml file, no 'detector' settings found in '{step_type}' step_type for step '{step_name}'."
1057 )
1058 auto_cb_set_db = detector_set_db.get("auto_cb")
1060 # scan settings
1061 scan_set_db = step_settings.get("scan")
1062 if scan_set_db is None:
1063 raise KeyError(
1064 f"Invalid .yml file, no 'scan' settings found in '{step_type}' step_type for step '{step_name}'."
1065 )
1066 scan_res = string_to_res(scan_set_db.get("resolution"))
1068 # misc settings
1069 bit_depth = step_settings.get("bit_depth")
1071 # TODO incorporate tile settings
1072 if yml_format.version >= 1.1:
1073 # tile settings
1074 tile_set_db = step_settings.get("tile_settings")
1076 validate_beam_settings(
1077 microscope=microscope,
1078 beam_type=beam_type,
1079 settings=beam_set_db,
1080 yml_format=yml_format,
1081 step_name=step_name,
1082 )
1083 beam_settings = tbt.BeamSettings(
1084 voltage_kv=beam_set_db["voltage_kv"],
1085 voltage_tol_kv=beam_set_db["voltage_tol_kv"],
1086 current_na=beam_set_db["current_na"],
1087 current_tol_na=beam_set_db["current_tol_na"],
1088 hfw_mm=beam_set_db["hfw_mm"],
1089 working_dist_mm=beam_set_db["working_dist_mm"],
1090 dynamic_focus=beam_set_db["dynamic_focus"],
1091 tilt_correction=beam_set_db["tilt_correction"],
1092 )
1094 validate_auto_cb_settings(
1095 settings=auto_cb_set_db,
1096 yml_format=yml_format,
1097 step_name=step_name,
1098 )
1099 auto_cb_settings = tbt.ScanArea(
1100 left=auto_cb_set_db["left"],
1101 top=auto_cb_set_db["top"],
1102 width=auto_cb_set_db["width"],
1103 height=auto_cb_set_db["height"],
1104 )
1106 validate_detector_settings(
1107 microscope=microscope,
1108 beam_type=beam_type,
1109 settings=detector_set_db,
1110 yml_format=yml_format,
1111 step_name=step_name,
1112 )
1113 detector_settings = tbt.Detector(
1114 type=tbt.DetectorType(detector_set_db["type"]),
1115 mode=tbt.DetectorMode(detector_set_db["mode"]),
1116 brightness=detector_set_db["brightness"],
1117 contrast=detector_set_db["contrast"],
1118 auto_cb_settings=auto_cb_settings,
1119 # custom_settings=None,
1120 )
1122 validate_scan_settings(
1123 microscope=microscope,
1124 beam_type=beam_type,
1125 settings=scan_set_db,
1126 yml_format=yml_format,
1127 step_name=step_name,
1128 )
1130 # cast resolution to preset if applicable
1131 if ut.valid_enum_entry(obj=scan_res, check_type=tbt.PresetResolution):
1132 scan_res = tbt.PresetResolution(scan_res)
1134 scan_settings = tbt.Scan(
1135 rotation_deg=scan_set_db["rotation_deg"],
1136 dwell_time_us=scan_set_db["dwell_time_us"],
1137 resolution=scan_res,
1138 # mode=tbt.ScanMode(scan_set_db["mode"]),
1139 )
1141 # make sure Scan rotation is 0 if dynamic focus or tilt correction is on (using auto mode only for angular correction)
1142 if beam_settings.dynamic_focus or beam_settings.tilt_correction:
1143 if not math.isclose(a=scan_settings.rotation_deg, b=0.0):
1144 raise ValueError(
1145 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."
1146 )
1148 # validate bit_depth
1149 if not ut.valid_enum_entry(bit_depth, tbt.ColorDepth):
1150 valid_bit_depths = [i.value for i in tbt.ColorDepth]
1151 raise ValueError(
1152 f"Unsupported bit depth of {bit_depth}, available depths are {valid_bit_depths}"
1153 )
1155 image_settings = tbt.ImageSettings(
1156 microscope=microscope,
1157 beam=beam_object_type(beam_type)(settings=beam_settings),
1158 detector=detector_settings,
1159 scan=scan_settings,
1160 bit_depth=bit_depth,
1161 )
1163 return image_settings
1166def fib(
1167 microscope: tbt.Microscope,
1168 step_settings: dict,
1169 step_name: str,
1170 yml_format: tbt.YMLFormatVersion,
1171) -> tbt.FIBSettings:
1172 """
1173 Convert a FIB step from a .yml file to microscope settings for performing a FIB operation.
1175 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.
1177 Parameters
1178 ----------
1179 microscope : tbt.Microscope
1180 The microscope object for which to set the FIB settings.
1181 step_settings : dict
1182 The dictionary containing the FIB step settings from the .yml file.
1183 step_name : str
1184 The name of the step in the .yml file.
1185 yml_format : tbt.YMLFormatVersion
1186 The format specified by the version of the .yml file.
1188 Returns
1189 -------
1190 tbt.FIBSettings
1191 The FIB settings object.
1193 Raises
1194 ------
1195 KeyError
1196 If required settings are missing from the .yml file.
1197 ValueError
1198 If invalid beam type is requested.
1199 """
1200 ## create image_step_settings from this
1201 if yml_format.version >= 1.0:
1202 image_step_settings = step_settings.get("image")
1203 image_step_settings["step_general"] = step_settings.get("step_general")
1205 mill_step_settings = step_settings.get("mill")
1206 mill_beam_db = mill_step_settings.get("beam")
1207 mill_pattern_db = mill_step_settings.get("pattern")
1209 # ensure image is with an ion beam
1210 enforce_beam_type(
1211 tbt.IonBeam(settings=None),
1212 step_settings=image_step_settings,
1213 step_name=step_name,
1214 yml_format=yml_format,
1215 )
1216 image_settings = image(
1217 microscope=microscope,
1218 step_settings=image_step_settings,
1219 step_name=step_name,
1220 yml_format=yml_format,
1221 )
1223 ## fib mill settings
1224 # ensure milling is with an ion beam
1225 enforce_beam_type(
1226 tbt.IonBeam(settings=None),
1227 step_settings=mill_step_settings,
1228 step_name=step_name,
1229 yml_format=yml_format,
1230 )
1231 beam_type = tbt.BeamType(mill_beam_db.get("type"))
1232 # mill beam
1233 validate_beam_settings(
1234 microscope=microscope,
1235 beam_type=beam_type,
1236 settings=mill_beam_db,
1237 yml_format=yml_format,
1238 step_name=step_name,
1239 )
1240 mill_beam = tbt.IonBeam(
1241 settings=tbt.BeamSettings(
1242 voltage_kv=mill_beam_db["voltage_kv"],
1243 voltage_tol_kv=mill_beam_db["voltage_tol_kv"],
1244 current_na=mill_beam_db["current_na"],
1245 current_tol_na=mill_beam_db["current_tol_na"],
1246 hfw_mm=mill_beam_db["hfw_mm"],
1247 working_dist_mm=mill_beam_db["working_dist_mm"],
1248 dynamic_focus=mill_beam_db["dynamic_focus"],
1249 tilt_correction=mill_beam_db["tilt_correction"],
1250 )
1251 )
1253 # fib pattern settings
1254 pattern = validate_fib_pattern_settings(
1255 microscope=microscope,
1256 settings=mill_pattern_db,
1257 yml_format=yml_format,
1258 step_name=step_name,
1259 )
1261 fib_settings = tbt.FIBSettings(
1262 microscope=microscope,
1263 image=image_settings,
1264 mill_beam=mill_beam,
1265 pattern=pattern,
1266 )
1267 return fib_settings
1270@singledispatch
1271def enforce_beam_type(
1272 beam_type,
1273 step_settings: dict,
1274 step_name: str,
1275 yml_format: tbt.YMLFormatVersion,
1276) -> bool:
1277 """
1278 Enforce a specific beam type is used for an operation based on a dictionary.
1280 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'.
1282 Parameters
1283 ----------
1284 beam_type : Any
1285 The beam type to enforce.
1286 step_settings : dict
1287 The dictionary containing the step settings.
1288 step_name : str
1289 The name of the step in the .yml file.
1290 yml_format : tbt.YMLFormatVersion
1291 The format specified by the version of the .yml file.
1293 Returns
1294 -------
1295 bool
1296 True if the beam type is enforced successfully.
1298 Raises
1299 ------
1300 NotImplementedError
1301 If no handler is available for the provided type.
1302 """
1303 _ = beam_type
1304 __ = step_settings
1305 ___ = step_name
1306 ____ = yml_format
1307 raise NotImplementedError(f"No handler for type {type(step_settings)}")
1310@enforce_beam_type.register
1311def _(
1312 beam_type: tbt.ElectronBeam,
1313 step_settings: dict,
1314 step_name: str,
1315 yml_format: tbt.YMLFormatVersion,
1316) -> bool:
1317 """
1318 Enforce that an electron beam is used for an operation based on a dictionary.
1320 This function ensures that an electron beam is used for an operation based on the provided settings dictionary.
1322 Parameters
1323 ----------
1324 beam_type : tbt.ElectronBeam
1325 The electron beam type to enforce.
1326 step_settings : dict
1327 The dictionary containing the step settings.
1328 step_name : str
1329 The name of the step in the .yml file.
1330 yml_format : tbt.YMLFormatVersion
1331 The format specified by the version of the .yml file.
1333 Returns
1334 -------
1335 bool
1336 True if the electron beam type is enforced successfully.
1338 Raises
1339 ------
1340 KeyError
1341 If the 'beam' settings are missing from the .yml file.
1342 NotImplementedError
1343 If the beam type is unsupported or not an electron beam.
1344 """
1345 # beam must be electron
1346 if yml_format.version >= 1.0:
1347 beam_set_db = step_settings.get("beam")
1348 if beam_set_db is None:
1349 raise KeyError(
1350 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'."
1351 )
1352 beam_type_value = beam_set_db.get("type")
1354 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."
1355 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
1356 raise NotImplementedError(electron_beam_error_message)
1357 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ELECTRON:
1358 raise NotImplementedError(electron_beam_error_message)
1361@enforce_beam_type.register
1362def _(
1363 beam_type: tbt.IonBeam,
1364 step_settings: dict,
1365 step_name: str,
1366 yml_format: tbt.YMLFormatVersion,
1367) -> bool:
1368 """
1369 Enforce that an ion beam is used for an operation based on a dictionary.
1371 This function ensures that an ion beam is used for an operation based on the provided settings dictionary.
1373 Parameters
1374 ----------
1375 beam_type : tbt.IonBeam
1376 The ion beam type to enforce.
1377 step_settings : dict
1378 The dictionary containing the step settings.
1379 step_name : str
1380 The name of the step in the .yml file.
1381 yml_format : tbt.YMLFormatVersion
1382 The format specified by the version of the .yml file.
1384 Returns
1385 -------
1386 bool
1387 True if the ion beam type is enforced successfully.
1389 Raises
1390 ------
1391 KeyError
1392 If the 'beam' settings are missing from the .yml file.
1393 NotImplementedError
1394 If the beam type is unsupported or not an ion beam.
1395 """
1396 # beam must be ion
1397 if yml_format.version >= 1.0:
1398 beam_set_db = step_settings.get("beam")
1399 if beam_set_db is None:
1400 raise KeyError(
1401 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'."
1402 )
1403 beam_type_value = beam_set_db.get("type")
1405 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."
1406 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType):
1407 raise NotImplementedError(ion_beam_error_message)
1408 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ION:
1409 raise NotImplementedError(ion_beam_error_message)
1412def ebsd(
1413 microscope: tbt.Microscope,
1414 step_settings: dict,
1415 step_name: str,
1416 yml_format: tbt.YMLFormatVersion,
1417) -> tbt.EBSDSettings:
1418 """
1419 Convert an EBSD step from a .yml file to microscope settings for performing an EBSD operation.
1421 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.
1423 Parameters
1424 ----------
1425 microscope : tbt.Microscope
1426 The microscope object for which to set the EBSD settings.
1427 step_settings : dict
1428 The dictionary containing the EBSD step settings from the .yml file.
1429 step_name : str
1430 The name of the step in the .yml file.
1431 yml_format : tbt.YMLFormatVersion
1432 The format specified by the version of the .yml file.
1434 Returns
1435 -------
1436 tbt.EBSDSettings
1437 The EBSD settings object.
1439 Raises
1440 ------
1441 KeyError
1442 If required settings are missing from the .yml file or if the 'concurrent_EDS' key is invalid.
1443 """
1444 enforce_beam_type(
1445 tbt.ElectronBeam(settings=None),
1446 step_settings=step_settings,
1447 step_name=step_name,
1448 yml_format=yml_format,
1449 )
1450 image_settings = image(
1451 microscope=microscope,
1452 step_settings=step_settings,
1453 step_name=step_name,
1454 yml_format=yml_format,
1455 )
1456 concurrent_EDS = step_settings.get("concurrent_EDS")
1457 if (concurrent_EDS is None) or (not concurrent_EDS):
1458 enable_eds = False
1459 elif concurrent_EDS == True:
1460 enable_eds = True
1461 else:
1462 raise KeyError(
1463 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)."
1464 )
1466 ebsd_settings = tbt.EBSDSettings(
1467 image=image_settings,
1468 enable_eds=enable_eds,
1469 enable_ebsd=True,
1470 )
1471 return ebsd_settings
1474def eds(
1475 microscope: tbt.Microscope,
1476 step_settings: dict,
1477 step_name: str,
1478 yml_format: tbt.YMLFormatVersion,
1479) -> tbt.EDSSettings:
1480 """
1481 Convert an EDS step from a .yml file to microscope settings for performing an EDS operation.
1483 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.
1485 Parameters
1486 ----------
1487 microscope : tbt.Microscope
1488 The microscope object for which to set the EDS settings.
1489 step_settings : dict
1490 The dictionary containing the EDS step settings from the .yml file.
1491 step_name : str
1492 The name of the step in the .yml file.
1493 yml_format : tbt.YMLFormatVersion
1494 The format specified by the version of the .yml file.
1496 Returns
1497 -------
1498 tbt.EDSSettings
1499 The EDS settings object.
1500 """
1501 enforce_beam_type(
1502 tbt.ElectronBeam(settings=None),
1503 step_settings=step_settings,
1504 step_name=step_name,
1505 yml_format=yml_format,
1506 )
1507 image_settings = image(
1508 microscope=microscope,
1509 step_settings=step_settings,
1510 step_name=step_name,
1511 yml_format=yml_format,
1512 )
1513 eds_settings = tbt.EDSSettings(
1514 image=image_settings,
1515 enable_eds=True,
1516 )
1517 return eds_settings
1520def custom(
1521 microscope: tbt.Microscope,
1522 step_settings: dict,
1523 step_name: str,
1524 yml_format: tbt.YMLFormatVersion,
1525) -> tbt.CustomSettings:
1526 """
1527 Convert a custom step from a .yml file to custom settings for the microscope.
1529 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.
1531 Parameters
1532 ----------
1533 microscope : tbt.Microscope
1534 The microscope object for which to set the custom settings.
1535 step_settings : dict
1536 The dictionary containing the custom step settings from the .yml file.
1537 step_name : str
1538 The name of the step in the .yml file.
1539 yml_format : tbt.YMLFormatVersion
1540 The format specified by the version of the .yml file.
1542 Returns
1543 -------
1544 tbt.CustomSettings
1545 The custom settings object.
1547 Raises
1548 ------
1549 KeyError
1550 If required settings are missing from the .yml file.
1551 ValueError
1552 If the specified script or executable path does not exist.
1553 """
1554 if yml_format.version >= 1.0:
1555 script_path = step_settings.get("script_path")
1556 if script_path is None:
1557 raise KeyError(
1558 f"Invalid .yml file, no 'script_path' found in custom step_type for step '{step_name}'."
1559 )
1560 if not Path(script_path).is_file():
1561 raise ValueError(
1562 f"Invalid location for script at location {script_path}. File does not exist."
1563 )
1565 executable_path = step_settings.get("executable_path")
1566 if executable_path is None:
1567 raise KeyError(
1568 f"Invalid .yml file, no 'executable_path' found in custom step_type for step '{step_name}'."
1569 )
1570 if not Path(executable_path).is_file():
1571 raise ValueError(
1572 f"Invalid location for executable at location {executable_path}. File does not exist."
1573 )
1575 custom_settings = tbt.CustomSettings(
1576 script_path=Path(script_path),
1577 executable_path=Path(executable_path),
1578 )
1579 return custom_settings
1582# def fib_pattern_type(
1583# settings: tbt.FIBSettings,
1584# ) -> Union[tbt.FIBBoxPattern, tbt.FIBStreamPattern]:
1585# """Returns specific pattern type settings for a properly formatted FIBSettings object"""
1588def scan_limits(
1589 selected_beam: property,
1590) -> tbt.ScanLimits:
1591 """
1592 Retrieve the scan settings limits for the selected beam.
1594 This function retrieves the limits for rotation and dwell time for the selected beam and returns them as a `ScanLimits` object.
1596 Parameters
1597 ----------
1598 selected_beam : property
1599 The selected beam property from which to retrieve the scan limits.
1601 Returns
1602 -------
1603 tbt.ScanLimits
1604 The scan limits for rotation (degrees) and dwell time (microseconds).
1605 """
1606 # rotation
1607 min_deg = selected_beam.scanning.rotation.limits.min * Conversions.RAD_TO_DEG
1608 max_deg = selected_beam.scanning.rotation.limits.max * Conversions.RAD_TO_DEG
1609 # dwell_time
1610 min_dwell_us = selected_beam.scanning.dwell_time.limits.min * Conversions.S_TO_US
1611 max_dwell_us = selected_beam.scanning.dwell_time.limits.max * Conversions.S_TO_US
1613 return tbt.ScanLimits(
1614 rotation_deg=tbt.Limit(min=min_deg, max=max_deg),
1615 dwell_us=tbt.Limit(min=min_dwell_us, max=max_dwell_us),
1616 )
1619def string_to_res(input: str) -> tbt.Resolution:
1620 """
1621 Convert a string in the format "{{width}}x{{height}}" to a resolution object.
1623 This function takes a string representing the resolution in the format "WIDTHxHEIGHT" and converts it to a `Resolution` object.
1625 Parameters
1626 ----------
1627 input : str
1628 The string representing the resolution in the format "WIDTHxHEIGHT".
1630 Returns
1631 -------
1632 tbt.Resolution
1633 The resolution object.
1635 Raises
1636 ------
1637 ValueError
1638 If the input string is not in the expected format.
1639 """
1640 try:
1641 split_res = (input.lower()).split("x")
1642 width, height = int(split_res[0]), int(split_res[1])
1643 except:
1644 raise ValueError(
1645 f"""Invalid string format,
1646 expected string format of "WIDTHxHEIGHT",
1647 but received the following: "{input}"."""
1648 )
1650 return tbt.Resolution(width=width, height=height)
1653def valid_string_resolution(string_resolution: str) -> bool:
1654 """
1655 Validate a string resolution.
1657 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.
1659 Parameters
1660 ----------
1661 string_resolution : str
1662 The string representing the resolution in the format "WIDTHxHEIGHT".
1664 Returns
1665 -------
1666 bool
1667 True if the resolution is valid, False otherwise.
1668 """
1669 res = string_to_res(string_resolution)
1670 width, height = res.width, res.height
1671 return (
1672 ut.in_interval(
1673 width,
1674 limit=Constants.scan_resolution_limit,
1675 type=tbt.IntervalType.CLOSED,
1676 )
1677 ) and (
1678 ut.in_interval(
1679 height,
1680 limit=Constants.scan_resolution_limit,
1681 type=tbt.IntervalType.CLOSED,
1682 )
1683 )
1686def validate_auto_cb_settings(
1687 settings: dict,
1688 yml_format: tbt.YMLFormatVersion,
1689 step_name: str,
1690) -> bool:
1691 """
1692 Perform schema checking for auto contrast/brightness setting dictionary.
1694 This function validates the auto contrast/brightness settings dictionary based on the specified yml format.
1696 Parameters
1697 ----------
1698 settings : dict
1699 The dictionary containing the auto contrast/brightness settings.
1700 yml_format : tbt.YMLFormatVersion
1701 The format specified by the version of the .yml file.
1702 step_name : str
1703 The name of the step in the .yml file.
1705 Returns
1706 -------
1707 bool
1708 True if the settings are valid, False otherwise.
1710 Raises
1711 ------
1712 KeyError
1713 If required keys are missing from the settings dictionary.
1714 ValueError
1715 If the settings do not satisfy the specified schema.
1716 """
1718 if ut.none_value_dictionary(settings):
1719 return True
1721 if settings.get("left") is None or settings.get("top") is None:
1722 if settings.get("left") is None:
1723 missing_key = "left"
1724 else:
1725 missing_key = "top"
1726 raise KeyError(
1727 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."
1728 )
1730 origin_limit = tbt.Limit(min=0.0, max=1.0) # reduced area limit for scan window
1731 width_limit = tbt.Limit(min=0.0, max=1.0 - settings["left"])
1732 height_limit = tbt.Limit(min=0.0, max=1.0 - settings["top"])
1734 if yml_format.version >= 1.0:
1735 schema = Schema(
1736 {
1737 "left": And(
1738 float,
1739 lambda x: ut.in_interval(
1740 x,
1741 limit=origin_limit,
1742 type=tbt.IntervalType.RIGHT_OPEN,
1743 ),
1744 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.",
1745 ),
1746 "top": And(
1747 float,
1748 lambda x: ut.in_interval(
1749 x,
1750 limit=origin_limit,
1751 type=tbt.IntervalType.RIGHT_OPEN,
1752 ),
1753 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.",
1754 ),
1755 "width": And(
1756 float,
1757 lambda x: ut.in_interval(
1758 x,
1759 limit=width_limit,
1760 type=tbt.IntervalType.LEFT_OPEN,
1761 ),
1762 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",
1763 ),
1764 "height": And(
1765 float,
1766 lambda x: ut.in_interval(
1767 x,
1768 limit=height_limit,
1769 type=tbt.IntervalType.LEFT_OPEN,
1770 ),
1771 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",
1772 ),
1773 },
1774 ignore_extra_keys=True,
1775 )
1777 try:
1778 schema.validate(settings)
1779 except UnboundLocalError:
1780 raise ValueError(
1781 f"Error. Unsupported yml version {yml_format.version} provided."
1782 )
1783 return True
1786def validate_stage_position(
1787 microscope: tbt.Microscope,
1788 step_name: str,
1789 settings: dict,
1790 yml_format: tbt.YMLFormatVersion,
1791) -> bool:
1792 """
1793 Perform schema checking for stage position dictionary.
1795 This function validates the stage position settings dictionary based on the specified yml format and the stage limits of the microscope.
1797 Parameters
1798 ----------
1799 microscope : tbt.Microscope
1800 The microscope object for which to validate the stage position settings.
1801 step_name : str
1802 The name of the step in the .yml file.
1803 settings : dict
1804 The dictionary containing the stage position settings.
1805 yml_format : tbt.YMLFormatVersion
1806 The format specified by the version of the .yml file.
1808 Returns
1809 -------
1810 bool
1811 True if the settings are valid, False otherwise.
1813 Raises
1814 ------
1815 ValueError
1816 If the yml version is unsupported.
1817 """
1818 limits = stage_limits(microscope=microscope)
1820 if yml_format.version >= 1.0:
1821 schema = Schema(
1822 {
1823 "x_mm": And(
1824 float,
1825 lambda x: ut.in_interval(
1826 x,
1827 limit=limits.x_mm,
1828 type=tbt.IntervalType.CLOSED,
1829 ),
1830 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}'",
1831 ),
1832 "y_mm": And(
1833 float,
1834 lambda x: ut.in_interval(
1835 x,
1836 limit=limits.y_mm,
1837 type=tbt.IntervalType.CLOSED,
1838 ),
1839 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}'",
1840 ),
1841 "z_mm": And(
1842 float,
1843 lambda x: ut.in_interval(
1844 x,
1845 limit=limits.z_mm,
1846 type=tbt.IntervalType.CLOSED,
1847 ),
1848 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}'",
1849 ),
1850 "r_deg": And(
1851 float,
1852 lambda x: ut.in_interval(
1853 x,
1854 limit=limits.r_deg,
1855 type=tbt.IntervalType.RIGHT_OPEN,
1856 ),
1857 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}'",
1858 ),
1859 "t_deg": And(
1860 float,
1861 lambda x: ut.in_interval(
1862 x,
1863 limit=limits.t_deg,
1864 type=tbt.IntervalType.CLOSED,
1865 ),
1866 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}'",
1867 ),
1868 },
1869 ignore_extra_keys=True,
1870 )
1872 try:
1873 schema.validate(settings)
1874 except UnboundLocalError:
1875 raise ValueError(
1876 f"Error. Unsupported yml version {yml_format.version} provided."
1877 )
1880def validate_beam_settings(
1881 microscope: tbt.Microscope,
1882 beam_type: tbt.BeamType,
1883 settings: dict,
1884 yml_format: tbt.YMLFormatVersion,
1885 step_name: str,
1886) -> bool:
1887 """
1888 Perform schema checking for beam setting dictionary.
1890 This function validates the beam settings dictionary based on the specified yml format and the beam limits of the microscope.
1892 Parameters
1893 ----------
1894 microscope : tbt.Microscope
1895 The microscope object for which to validate the beam settings.
1896 beam_type : tbt.BeamType
1897 The type of the beam (electron or ion).
1898 settings : dict
1899 The dictionary containing the beam settings.
1900 yml_format : tbt.YMLFormatVersion
1901 The format specified by the version of the .yml file.
1902 step_name : str
1903 The name of the step in the .yml file.
1905 Returns
1906 -------
1907 bool
1908 True if the settings are valid, False otherwise.
1910 Raises
1911 ------
1912 ValueError
1913 If the yml version is unsupported or if the settings do not satisfy the specified schema.
1914 """
1915 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
1916 selected_beam = ut.beam_type(specified_beam, microscope)
1918 limits = beam_limits(selected_beam, beam_type)
1920 if yml_format.version >= 1.0:
1921 schema = Schema(
1922 {
1923 "voltage_kv": And(
1924 float,
1925 lambda x: ut.in_interval(
1926 x,
1927 limit=limits.voltage_kv,
1928 type=tbt.IntervalType.CLOSED,
1929 ),
1930 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.",
1931 ),
1932 "voltage_tol_kv": And(
1933 float,
1934 lambda x: x > 0,
1935 error=f"In step '{step_name}', requested voltage tolerance of '{settings['voltage_tol_kv']}' kV must be a positive float (greater than 0).",
1936 ),
1937 "current_na": And(
1938 float,
1939 lambda x: ut.in_interval(
1940 x,
1941 limit=limits.current_na,
1942 type=tbt.IntervalType.CLOSED,
1943 ),
1944 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",
1945 ),
1946 "current_tol_na": And(
1947 float,
1948 lambda x: x > 0,
1949 error=f"In step '{step_name}', 'current_tol_na' must be a positive float (greater than 0)",
1950 ),
1951 "hfw_mm": And(
1952 float,
1953 lambda x: ut.in_interval(
1954 x,
1955 limits.hfw_mm,
1956 type=tbt.IntervalType.CLOSED,
1957 ),
1958 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",
1959 ),
1960 "working_dist_mm": And(
1961 float,
1962 lambda x: ut.in_interval(
1963 x,
1964 limit=limits.working_distance_mm,
1965 type=tbt.IntervalType.CLOSED,
1966 ),
1967 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",
1968 ),
1969 },
1970 ignore_extra_keys=True,
1971 )
1973 e_beam_schema = Schema(
1974 {
1975 "dynamic_focus": Or(
1976 None,
1977 bool,
1978 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.",
1979 ),
1980 "tilt_correction": Or(
1981 None,
1982 bool,
1983 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.",
1984 ),
1985 },
1986 ignore_extra_keys=True,
1987 )
1988 i_beam_schema = Schema(
1989 {
1990 "dynamic_focus": Or(
1991 None,
1992 False,
1993 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.",
1994 ),
1995 "tilt_correction": Or(
1996 None,
1997 False,
1998 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.",
1999 ),
2000 },
2001 ignore_extra_keys=True,
2002 )
2004 try:
2005 schema.validate(settings)
2006 if specified_beam.type == tbt.BeamType.ELECTRON:
2007 e_beam_schema.validate(settings)
2008 if specified_beam.type == tbt.BeamType.ION:
2009 i_beam_schema.validate(settings)
2010 except UnboundLocalError:
2011 raise ValueError(
2012 f"Error. Unsupported yml version {yml_format.version} provided."
2013 )
2016def validate_detector_settings(
2017 microscope: tbt.Microscope,
2018 beam_type: tbt.BeamType,
2019 settings: dict,
2020 yml_format: tbt.YMLFormatVersion,
2021 step_name: str,
2022) -> bool:
2023 """
2024 Perform schema checking for detector setting dictionary.
2026 This function validates the detector settings dictionary based on the specified yml format and the detector capabilities of the microscope.
2028 Parameters
2029 ----------
2030 microscope : tbt.Microscope
2031 The microscope object for which to validate the detector settings.
2032 beam_type : tbt.BeamType
2033 The type of the beam (electron or ion).
2034 settings : dict
2035 The dictionary containing the detector settings.
2036 yml_format : tbt.YMLFormatVersion
2037 The format specified by the version of the .yml file.
2038 step_name : str
2039 The name of the step in the .yml file.
2041 Returns
2042 -------
2043 bool
2044 True if the settings are valid, False otherwise.
2046 Raises
2047 ------
2048 KeyError
2049 If auto contrast/brightness settings conflict with fixed brightness/contrast values.
2050 ValueError
2051 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.
2052 """
2054 # switch to top left quad, enable e-beam
2055 devices.device_access(microscope=microscope)
2057 # set specified beam to active imaging device
2058 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
2059 img.set_beam_device(
2060 microscope=microscope,
2061 device=specified_beam.device,
2062 )
2064 auto_cb_db = settings.get("auto_cb")
2065 use_auto_cb = not ut.none_value_dictionary(settings.get("auto_cb"))
2066 if use_auto_cb and (
2067 settings.get("brightness") is not None or settings.get("contrast") is not None
2068 ):
2069 raise KeyError(
2070 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."
2071 )
2073 if yml_format.version >= 1.0:
2074 detector = settings.get("type")
2075 mode = settings.get("mode")
2076 cb_limit = tbt.Limit(min=0.0, max=1.0)
2077 schema = Schema(
2078 {
2079 "brightness": Or(
2080 None,
2081 And(
2082 float,
2083 lambda x: ut.in_interval(
2084 x,
2085 limit=cb_limit,
2086 type=tbt.IntervalType.LEFT_OPEN,
2087 ),
2088 ),
2089 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}'.",
2090 ),
2091 "contrast": Or(
2092 None,
2093 And(
2094 float,
2095 lambda x: ut.in_interval(
2096 x,
2097 limit=cb_limit,
2098 type=tbt.IntervalType.LEFT_OPEN,
2099 ),
2100 ),
2101 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}'.",
2102 ),
2103 },
2104 ignore_extra_keys=True,
2105 )
2107 # check detector type
2108 if not ut.valid_enum_entry(detector, tbt.DetectorType):
2109 raise ValueError(
2110 f"Unsupported detector type of '{detector}' on step '{step_name}'."
2111 )
2112 detector_type = tbt.DetectorType(detector)
2113 microscope_detector_types = available_detector_types(microscope=microscope)
2114 if detector_type.value not in microscope_detector_types:
2115 raise ValueError(
2116 f"Requested detector of {detector_type.value} is unavailable on this tool. Available detectors are: {microscope_detector_types}"
2117 )
2118 # make sure to set this detector as the active one to access modes
2119 img.detector_type(
2120 microscope=microscope,
2121 detector=detector_type,
2122 )
2124 # check detector mode
2125 if not ut.valid_enum_entry(mode, tbt.DetectorMode):
2126 raise ValueError(
2127 f'Unsupported detector mode of "{mode}" for "{detector}" detector'
2128 )
2129 detector_mode = tbt.DetectorMode(mode)
2130 microscope_detector_modes = available_detector_modes(microscope=microscope)
2131 if detector_mode.value not in microscope_detector_modes:
2132 raise ValueError(
2133 f"Requested mode of {detector_mode.value} for {detector_type.value} detector is invalid. Valid mode types are: {microscope_detector_modes}"
2134 )
2136 # check detector settings
2137 try:
2138 schema.validate(settings)
2139 except UnboundLocalError:
2140 raise ValueError(
2141 f"Error. Unsupported yml version {yml_format.version} provided."
2142 )
2145def validate_EBSD_EDS_settings(
2146 ebsd_oem: str,
2147 eds_oem: str,
2148) -> bool:
2149 """
2150 Check EBSD and EDS OEM and connection for supported OEMs.
2152 This function ensures that the specified EBSD and EDS OEMs are supported and that the connection settings are valid.
2154 Parameters
2155 ----------
2156 yml_format : tbt.YMLFormatVersion
2157 The format specified by the version of the .yml file.
2158 connection_host : str
2159 The host for the microscope connection.
2160 connection_port : str
2161 The port for the microscope connection.
2162 ebsd_oem : str
2163 The OEM for the EBSD device.
2164 eds_oem : str
2165 The OEM for the EDS device.
2167 Returns
2168 -------
2169 bool
2170 True if the settings are valid, False otherwise.
2172 Raises
2173 ------
2174 NotImplementedError
2175 If differing EBSD and EDS OEMs are requested.
2176 ValueError
2177 If the EBSD or EDS OEM is unsupported.
2178 SystemError
2179 If the Laser API is not accessible.
2180 """
2181 # ensure same manufacturer for both EBSD and EDS
2182 if (ebsd_oem is not None) and (eds_oem is not None) and (ebsd_oem != eds_oem):
2183 raise NotImplementedError(
2184 f"Differing EBSD and EDS OEMs are not supported. Requested EBSD OEM of '{ebsd_oem}' and EDS OEM of '{eds_oem}'."
2185 )
2187 # Check EBSD OEM
2188 if not ut.valid_enum_entry(ebsd_oem, tbt.ExternalDeviceOEM):
2189 raise ValueError(
2190 f"Unsupported EBSD OEM of '{ebsd_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}"
2191 )
2192 ebsd_device = tbt.ExternalDeviceOEM(ebsd_oem)
2193 # Check EDS OEM
2194 if not ut.valid_enum_entry(eds_oem, tbt.ExternalDeviceOEM):
2195 raise ValueError(
2196 f"Unsupported EDS OEM of '{eds_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}"
2197 )
2198 eds_device = tbt.ExternalDeviceOEM(eds_oem)
2200 # exit if both devices are none, no need for laser control
2201 if ebsd_device == eds_device == tbt.ExternalDeviceOEM.NONE:
2202 return True
2204 # check EBSD and EDS connection
2205 try:
2206 fs_laser.tfs_laser
2207 except:
2208 raise SystemError(
2209 "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."
2210 )
2212 ###################### PROPOSED CHANGE ######################
2213 # Retracting devices here is unnecessary
2214 # This causes devices to retract during validation
2215 # Device retraction is needed only when startin/ending experiments
2216 # Both of which are outside the scope of validation and already handled elsewhere
2217 ###################### PROPOSED CHANGE ######################
2218 # microscope = tbt.Microscope()
2219 # ut.connect_microscope(
2220 # microscope=microscope,
2221 # quiet_output=True,
2222 # connection_host=connection_host,
2223 # connection_port=connection_port,
2224 # )
2225 # if tbt.ExternalDeviceOEM(eds_oem) != tbt.ExternalDeviceOEM.NONE:
2226 # devices.retract_EDS(microscope=microscope)
2227 # if tbt.ExternalDeviceOEM(ebsd_oem) != tbt.ExternalDeviceOEM.NONE:
2228 # devices.retract_EBSD(microscope=microscope)
2229 # ut.disconnect_microscope(
2230 # microscope=microscope,
2231 # quiet_output=True,
2232 # )
2234 return True
2237def validate_general_settings(
2238 settings: dict,
2239 yml_format: tbt.YMLFormatVersion,
2240) -> bool:
2241 """
2242 Perform schema checking for general setting dictionary.
2244 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.
2246 Parameters
2247 ----------
2248 settings : dict
2249 The dictionary containing the general settings.
2250 yml_format : tbt.YMLFormatVersion
2251 The format specified by the version of the .yml file.
2253 Returns
2254 -------
2255 bool
2256 True if the settings are valid, False otherwise.
2258 Raises
2259 ------
2260 ValueError
2261 If the general settings dictionary is empty, or if the settings do not satisfy the specified schema, or if the connection is invalid.
2262 NotImplementedError
2263 If the sectioning axis is unsupported.
2264 """
2266 if settings == {}:
2267 raise ValueError("General settings dictionary is empty.")
2269 slice_thickness_limit_um = Constants.slice_thickness_limit_um
2270 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_generic
2272 if yml_format.version >= 1.0:
2273 sectioning_axis = settings.get("sectioning_axis")
2274 connection_host = settings.get("connection_host")
2275 connection_port = settings.get("connection_port")
2276 ebsd_oem = settings.get("EBSD_OEM")
2277 eds_oem = settings.get("EDS_OEM")
2278 exp_dir = settings.get("exp_dir")
2279 h5_log_name = settings.get("h5_log_name")
2281 # Validate the non-numeric values
2282 # Check sectioning axis
2283 if not ut.valid_enum_entry(sectioning_axis, tbt.SectioningAxis):
2284 raise ValueError(f"Unsupported sectioning axis of {sectioning_axis}.")
2285 # TODO
2286 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z:
2287 raise NotImplementedError("Currently only Z-axis sectioning is supported.")
2288 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z:
2289 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_non_Z_sectioning # overwrite
2290 warnings.warn(
2291 "Pre-tilt value must be zero (0.0) degrees when using a sectioning axis other than 'Z'"
2292 )
2293 # Check connection host and port
2294 if not ut.valid_microscope_connection(
2295 host=connection_host,
2296 port=connection_port,
2297 ):
2298 raise ValueError(
2299 f"Unsupported connection with host of {connection_host} and port of {connection_port}."
2300 )
2302 # check EBSD and EDS
2303 validate_EBSD_EDS_settings(
2304 ebsd_oem=ebsd_oem,
2305 eds_oem=eds_oem,
2306 )
2308 # Check exp dir
2309 try:
2310 Path(exp_dir).mkdir(
2311 parents=True,
2312 exist_ok=True,
2313 )
2314 except TypeError:
2315 raise ValueError(
2316 f'Requested experimental directory of "{exp_dir}", which is not a valid path.'
2317 )
2318 # Check h5 log name
2319 if not isinstance(h5_log_name, str):
2320 raise ValueError(f'Unsupported h5 log name of "{h5_log_name}"')
2322 schema = Schema(
2323 {
2324 "slice_thickness_um": And(
2325 float,
2326 lambda x: ut.in_interval(
2327 x,
2328 limit=slice_thickness_limit_um,
2329 type=tbt.IntervalType.CLOSED,
2330 ),
2331 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}'",
2332 ),
2333 "max_slice_num": And(
2334 int,
2335 lambda x: x > 0,
2336 error=f"Requested max slice number of {settings['max_slice_num']} must satisfy '0 < max_slice_number'",
2337 ),
2338 "pre_tilt_deg": And(
2339 float,
2340 lambda x: ut.in_interval(
2341 x,
2342 limit=pre_tilt_limit_deg,
2343 type=tbt.IntervalType.CLOSED,
2344 ),
2345 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}'",
2346 ),
2347 "stage_translational_tol_um": And(
2348 float,
2349 lambda x: x > 0,
2350 error=f"Requested stage translational tolerance of {settings['stage_translational_tol_um']} um must be a positive float (greater than 0)",
2351 ),
2352 "stage_angular_tol_deg": And(
2353 float,
2354 lambda x: x > 0,
2355 error=f"Requested stage angular tolerance of {settings['stage_angular_tol_deg']} degrees must be a positive float (greater than 0)",
2356 ),
2357 "step_count": And(
2358 int,
2359 lambda x: x > 0,
2360 error=f"Requested step count of {settings['step_count']} must be a positive int (greater than 0)",
2361 ),
2362 },
2363 ignore_extra_keys=True,
2364 )
2366 try:
2367 schema.validate(settings)
2368 except UnboundLocalError:
2369 raise ValueError(
2370 f"Error. Unsupported yml version {yml_format.version} provided."
2371 )
2374def validate_scan_settings(
2375 microscope: tbt.Microscope,
2376 beam_type: tbt.BeamType,
2377 settings: dict,
2378 yml_format: tbt.YMLFormatVersion,
2379 step_name: str,
2380) -> bool:
2381 """
2382 Perform schema checking for scan setting dictionary.
2384 This function validates the scan settings dictionary based on the specified yml format and the scan limits of the microscope.
2386 Parameters
2387 ----------
2388 microscope : tbt.Microscope
2389 The microscope object for which to validate the scan settings.
2390 beam_type : tbt.BeamType
2391 The type of the beam (electron or ion).
2392 settings : dict
2393 The dictionary containing the scan settings.
2394 yml_format : tbt.YMLFormatVersion
2395 The format specified by the version of the .yml file.
2396 step_name : str
2397 The name of the step in the .yml file.
2399 Returns
2400 -------
2401 bool
2402 True if the settings are valid, False otherwise.
2404 Raises
2405 ------
2406 ValueError
2407 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2408 """
2409 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings())
2410 selected_beam = ut.beam_type(specified_beam, microscope)
2412 """Schema checking for scan setting dictionary, format specified by yml_format"""
2413 if yml_format.version >= 1.0:
2414 limits = scan_limits(selected_beam)
2415 resolution = settings["resolution"]
2416 rotation_limit = limits.rotation_deg
2417 dwell_limit = limits.dwell_us
2419 # check resolution
2420 res = string_to_res(resolution)
2422 schema = Schema(
2423 {
2424 "resolution": And(
2425 str,
2426 lambda x: valid_string_resolution(x),
2427 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}",
2428 ),
2429 "rotation_deg": And(
2430 float,
2431 lambda x: ut.in_interval(
2432 x,
2433 limit=rotation_limit,
2434 type=tbt.IntervalType.CLOSED,
2435 ),
2436 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'])}.",
2437 ),
2438 "dwell_time_us": And(
2439 float,
2440 lambda x: ut.in_interval(
2441 x,
2442 limit=dwell_limit,
2443 type=tbt.IntervalType.CLOSED,
2444 ),
2445 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'])}.",
2446 ),
2447 },
2448 ignore_extra_keys=True,
2449 )
2451 try:
2452 schema.validate(settings)
2453 except UnboundLocalError:
2454 raise ValueError(
2455 f"Error. Unsupported yml version {yml_format.version} provided."
2456 )
2459def stage_position_settings(
2460 microscope: tbt.Microscope,
2461 step_name: str,
2462 general_settings: tbt.GeneralSettings,
2463 step_stage_settings: dict,
2464 yml_format: tbt.YMLFormatVersion,
2465) -> tbt.StageSettings:
2466 """
2467 Create a StagePositionUser object from settings, including validation.
2469 This function creates a `StagePositionUser` object from the provided settings and performs validation.
2471 Parameters
2472 ----------
2473 microscope : tbt.Microscope
2474 The microscope object for which to set the stage position.
2475 step_name : str
2476 The name of the step in the .yml file.
2477 general_settings : tbt.GeneralSettings
2478 The general settings object.
2479 step_stage_settings : dict
2480 The dictionary containing the stage position settings for the step.
2481 yml_format : tbt.YMLFormatVersion
2482 The format specified by the version of the .yml file.
2484 Returns
2485 -------
2486 tbt.StageSettings
2487 The stage settings object.
2489 Raises
2490 ------
2491 NotImplementedError
2492 If the rotation side value is unsupported.
2493 ValueError
2494 If the stage position settings do not satisfy the specified schema.
2495 """
2497 if yml_format.version >= 1.0:
2498 pos_db = step_stage_settings.get("initial_position")
2500 rotation_side = step_stage_settings["rotation_side"]
2501 if not ut.valid_enum_entry(rotation_side, tbt.RotationSide):
2502 raise NotImplementedError(
2503 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]}."
2504 )
2506 validate_stage_position(
2507 microscope=microscope,
2508 step_name=step_name,
2509 settings=pos_db,
2510 yml_format=yml_format,
2511 )
2513 initial_position = tbt.StagePositionUser(
2514 x_mm=pos_db["x_mm"],
2515 y_mm=pos_db["y_mm"],
2516 z_mm=pos_db["z_mm"],
2517 r_deg=pos_db["r_deg"],
2518 t_deg=pos_db["t_deg"],
2519 )
2521 pretilt_angle_deg = general_settings.pre_tilt_deg
2522 sectioning_axis = general_settings.sectioning_axis
2524 stage_settings = tbt.StageSettings(
2525 microscope=microscope,
2526 initial_position=initial_position,
2527 pretilt_angle_deg=pretilt_angle_deg,
2528 sectioning_axis=sectioning_axis,
2529 rotation_side=tbt.RotationSide(rotation_side),
2530 # movement_mode=tbt.StageMovementMode.OUT_OF_PLANE,
2531 )
2533 return stage_settings
2536def validate_pulse_settings(
2537 settings: dict,
2538 yml_format: tbt.YMLFormatVersion,
2539 step_name: str,
2540) -> bool:
2541 """
2542 Perform schema checking for pulse setting dictionary.
2544 This function validates the pulse settings dictionary based on the specified yml format.
2546 Parameters
2547 ----------
2548 settings : dict
2549 The dictionary containing the pulse settings.
2550 yml_format : tbt.YMLFormatVersion
2551 The format specified by the version of the .yml file.
2552 step_name : str
2553 The name of the step in the .yml file.
2555 Returns
2556 -------
2557 bool
2558 True if the settings are valid, False otherwise.
2560 Raises
2561 ------
2562 NotImplementedError
2563 If the wavelength or polarization value is unsupported.
2564 ValueError
2565 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2566 """
2567 if yml_format.version >= 1.0:
2568 wavelength_nm = settings.get("wavelength_nm")
2569 if not ut.valid_enum_entry(wavelength_nm, tbt.LaserWavelength):
2570 raise NotImplementedError(
2571 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]}."
2572 )
2573 polarization = settings.get("polarization")
2574 if not ut.valid_enum_entry(polarization, tbt.LaserPolarization):
2575 raise NotImplementedError(
2576 f"In 'laser' step_type for step '{step_name}', unsupported laser polarization of '{polarization}', supported values are: {[i.value for i in tbt.LaserPolarization]}."
2577 )
2579 schema = Schema(
2580 {
2581 "divider": And(
2582 int,
2583 lambda x: x > 0,
2584 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.",
2585 ),
2586 "energy_uj": And(
2587 float,
2588 lambda x: x > 0,
2589 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.",
2590 ),
2591 },
2592 ignore_extra_keys=True,
2593 )
2594 try:
2595 schema.validate(settings)
2596 except UnboundLocalError:
2597 raise ValueError(
2598 f"Error. Unsupported yml version {yml_format.version} provided."
2599 )
2602def validate_laser_optics_settings(
2603 settings: dict,
2604 yml_format: tbt.YMLFormatVersion,
2605 step_name: str,
2606) -> bool:
2607 """
2608 Perform schema checking for laser optics setting dictionary.
2610 This function validates the laser optics settings dictionary based on the specified yml format.
2612 Parameters
2613 ----------
2614 settings : dict
2615 The dictionary containing the laser optics settings.
2616 yml_format : tbt.YMLFormatVersion
2617 The format specified by the version of the .yml file.
2618 step_name : str
2619 The name of the step in the .yml file.
2621 Returns
2622 -------
2623 bool
2624 True if the settings are valid, False otherwise.
2626 Raises
2627 ------
2628 ValueError
2629 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2630 """
2631 if yml_format.version >= 1.0:
2632 schema = Schema(
2633 {
2634 "objective_position_mm": And(
2635 float,
2636 lambda x: ut.in_interval(
2637 x,
2638 limit=Constants.laser_objective_limit_mm,
2639 type=tbt.IntervalType.CLOSED,
2640 ),
2641 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.",
2642 ),
2643 "beam_shift_um_x": And(
2644 float,
2645 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.",
2646 ),
2647 "beam_shift_um_y": And(
2648 float,
2649 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.",
2650 ),
2651 },
2652 ignore_extra_keys=True,
2653 )
2654 try:
2655 schema.validate(settings)
2656 except UnboundLocalError:
2657 raise ValueError(
2658 f"Error. Unsupported yml version {yml_format.version} provided."
2659 )
2662def validate_laser_box_settings(
2663 settings: dict,
2664 yml_format: tbt.YMLFormatVersion,
2665 step_name: str,
2666) -> bool:
2667 """
2668 Perform schema checking for laser box pattern setting dictionary.
2670 This function validates the laser box pattern settings dictionary based on the specified yml format.
2672 Parameters
2673 ----------
2674 settings : dict
2675 The dictionary containing the laser box pattern settings.
2676 yml_format : tbt.YMLFormatVersion
2677 The format specified by the version of the .yml file.
2678 step_name : str
2679 The name of the step in the .yml file.
2681 Returns
2682 -------
2683 bool
2684 True if the settings are valid, False otherwise.
2686 Raises
2687 ------
2688 NotImplementedError
2689 If the scan type or coordinate reference value is unsupported.
2690 ValueError
2691 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2692 """
2693 if yml_format.version >= 1.0:
2694 # scan type
2695 scan_type = settings.get("scan_type")
2696 scan_types = [
2697 tbt.LaserScanType.RASTER,
2698 tbt.LaserScanType.SERPENTINE,
2699 ]
2700 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]}."
2701 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType):
2702 raise NotImplementedError(scan_type_error_msg)
2703 if tbt.LaserScanType(scan_type) not in scan_types:
2704 raise NotImplementedError(scan_type_error_msg)
2706 # coordinate reference
2707 coordinate_ref = settings.get("coordinate_ref")
2708 coord_refs = [
2709 tbt.CoordinateReference.CENTER,
2710 tbt.CoordinateReference.UPPER_CENTER,
2711 tbt.CoordinateReference.UPPER_LEFT,
2712 ]
2713 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]}."
2714 if not ut.valid_enum_entry(coordinate_ref, tbt.CoordinateReference):
2715 raise NotImplementedError(coord_error_msg)
2716 if tbt.CoordinateReference(coordinate_ref) not in coord_refs:
2717 raise NotImplementedError(coord_error_msg)
2719 schema = Schema(
2720 {
2721 "passes": And(
2722 int,
2723 lambda x: x > 0,
2724 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.",
2725 ),
2726 "size_x_um": And(
2727 float,
2728 lambda x: x > 0,
2729 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.",
2730 ),
2731 "size_y_um": And(
2732 float,
2733 lambda x: x > 0,
2734 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.",
2735 ),
2736 "pitch_x_um": And(
2737 float,
2738 lambda x: x > 0,
2739 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.",
2740 ),
2741 "pitch_y_um": And(
2742 float,
2743 lambda x: x > 0,
2744 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.",
2745 ),
2746 },
2747 ignore_extra_keys=True,
2748 )
2749 try:
2750 schema.validate(settings)
2751 except UnboundLocalError:
2752 raise ValueError(
2753 f"Error. Unsupported yml version {yml_format.version} provided."
2754 )
2757def validate_laser_line_settings(
2758 settings: dict,
2759 yml_format: tbt.YMLFormatVersion,
2760 step_name: str,
2761) -> bool:
2762 """
2763 Perform schema checking for laser line pattern setting dictionary.
2765 This function validates the laser line pattern settings dictionary based on the specified yml format.
2767 Parameters
2768 ----------
2769 settings : dict
2770 The dictionary containing the laser line pattern settings.
2771 yml_format : tbt.YMLFormatVersion
2772 The format specified by the version of the .yml file.
2773 step_name : str
2774 The name of the step in the .yml file.
2776 Returns
2777 -------
2778 bool
2779 True if the settings are valid, False otherwise.
2781 Raises
2782 ------
2783 NotImplementedError
2784 If the scan type value is unsupported.
2785 ValueError
2786 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2787 """
2788 if yml_format.version >= 1.0:
2789 # scan type
2790 scan_type = settings.get("scan_type")
2791 scan_types = [
2792 tbt.LaserScanType.SINGLE,
2793 tbt.LaserScanType.LAP,
2794 ]
2795 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]}."
2796 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType):
2797 raise NotImplementedError(scan_type_error_msg)
2798 if tbt.LaserScanType(scan_type) not in scan_types:
2799 raise NotImplementedError(scan_type_error_msg)
2801 schema = Schema(
2802 {
2803 "passes": And(
2804 int,
2805 lambda x: x > 0,
2806 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.",
2807 ),
2808 "size_um": And(
2809 float,
2810 lambda x: x > 0,
2811 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.",
2812 ),
2813 "pitch_um": And(
2814 float,
2815 lambda x: x > 0,
2816 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.",
2817 ),
2818 },
2819 ignore_extra_keys=True,
2820 )
2821 try:
2822 schema.validate(settings)
2823 except UnboundLocalError:
2824 raise ValueError(
2825 f"Error. Unsupported yml version {yml_format.version} provided."
2826 )
2829def validate_laser_mode_settings(
2830 settings: dict,
2831 yml_format: tbt.YMLFormatVersion,
2832 step_name: str,
2833) -> bool:
2834 """
2835 Perform schema checking for laser mode setting dictionary.
2837 This function validates the laser mode settings dictionary based on the specified yml format.
2839 Parameters
2840 ----------
2841 settings : dict
2842 The dictionary containing the laser mode settings.
2843 yml_format : tbt.YMLFormatVersion
2844 The format specified by the version of the .yml file.
2845 step_name : str
2846 The name of the step in the .yml file.
2848 Returns
2849 -------
2850 bool
2851 True if the settings are valid, False otherwise.
2853 Raises
2854 ------
2855 NotImplementedError
2856 If the laser pattern mode value is unsupported.
2857 ValueError
2858 If the yml version is unsupported or if the settings do not satisfy the specified schema.
2859 """
2860 if yml_format.version >= 1.0:
2861 mode = settings.get("mode")
2862 if not ut.valid_enum_entry(mode, tbt.LaserPatternMode):
2863 raise NotImplementedError(
2864 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]}."
2865 )
2867 schema = Schema(
2868 {
2869 "rotation_deg": And(
2870 float,
2871 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.",
2872 ),
2873 },
2874 ignore_extra_keys=True,
2875 )
2876 schema_fine = Schema(
2877 {
2878 "pixel_dwell_ms": Or(
2879 None,
2880 "null",
2881 "None",
2882 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.",
2883 ),
2884 "pulses_per_pixel": And(
2885 int,
2886 lambda x: x > 0,
2887 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.",
2888 ),
2889 },
2890 ignore_extra_keys=True,
2891 )
2892 schema_coarse = Schema(
2893 {
2894 "pixel_dwell_ms": And(
2895 float,
2896 lambda x: x > 0.0,
2897 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.",
2898 ),
2899 "pulses_per_pixel": Or(
2900 None,
2901 "null",
2902 "None",
2903 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.",
2904 ),
2905 },
2906 ignore_extra_keys=True,
2907 )
2908 try:
2909 schema.validate(settings)
2910 if mode == "fine":
2911 schema_fine.validate(settings)
2912 if mode == "coarse":
2913 schema_coarse.validate(settings)
2914 except UnboundLocalError:
2915 raise ValueError(
2916 f"Error. Unsupported yml version {yml_format.version} provided."
2917 )
2920def validate_laser_pattern_settings(
2921 settings: dict,
2922 yml_format: tbt.YMLFormatVersion,
2923 step_name: str,
2924) -> tbt.LaserPatternType:
2925 """
2926 Perform schema checking for laser pattern setting dictionary.
2928 This function validates the laser pattern settings dictionary based on the specified yml format and determines the type of pattern (box or line).
2930 Parameters
2931 ----------
2932 settings : dict
2933 The dictionary containing the laser pattern settings.
2934 yml_format : tbt.YMLFormatVersion
2935 The format specified by the version of the .yml file.
2936 step_name : str
2937 The name of the step in the .yml file.
2939 Returns
2940 -------
2941 tbt.LaserPatternType
2942 The type of the laser pattern (box or line).
2944 Raises
2945 ------
2946 KeyError
2947 If required settings are missing from the .yml file or if multiple pattern types are specified.
2948 ValueError
2949 If the laser pattern settings are invalid.
2950 """
2951 if yml_format.version >= 1.0:
2952 # determine type of pattern (only one allowed)
2953 type_set_db = settings.get("type")
2954 if type_set_db is None:
2955 raise KeyError(
2956 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'laser' step_type for step '{step_name}'."
2957 )
2959 box_settings = type_set_db.get("box")
2960 line_settings = type_set_db.get("line")
2961 if box_settings is None:
2962 box_pattern = False
2963 else:
2964 box_pattern = not ut.none_value_dictionary(box_settings)
2965 if line_settings is None:
2966 line_pattern = False
2967 else:
2968 line_pattern = not ut.none_value_dictionary(line_settings)
2969 if box_pattern == line_pattern == False:
2970 raise KeyError(
2971 f"Invalid .yml file in 'laser' step_type for step '{step_name}'. No pattern type settings found."
2972 )
2973 if box_pattern == line_pattern == True:
2974 raise KeyError(
2975 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."
2976 )
2977 validate_laser_mode_settings(
2978 settings=settings,
2979 yml_format=yml_format,
2980 step_name=step_name,
2981 )
2983 if box_pattern:
2984 validate_laser_box_settings(
2985 settings=box_settings,
2986 yml_format=yml_format,
2987 step_name=step_name,
2988 )
2989 return tbt.LaserPatternType.BOX
2990 elif line_pattern:
2991 validate_laser_line_settings(
2992 settings=line_settings,
2993 yml_format=yml_format,
2994 step_name=step_name,
2995 )
2996 return tbt.LaserPatternType.LINE
2997 else:
2998 raise ValueError(
2999 f"Invalid laser pattern settings for step {step_name}. Supported types are {[i.value for i in tbt.LaserPatternType]}"
3000 )
3003def validate_fib_pattern_settings(
3004 microscope: tbt.Microscope,
3005 settings: dict,
3006 yml_format: tbt.YMLFormatVersion,
3007 step_name: str,
3008) -> Union[
3009 tbt.FIBRectanglePattern,
3010 tbt.FIBRegularCrossSection,
3011 tbt.FIBCleaningCrossSection,
3012 tbt.FIBStreamPattern,
3013]:
3014 """
3015 Perform schema checking for FIB pattern setting dictionary.
3017 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).
3019 Parameters
3020 ----------
3021 microscope : tbt.Microscope
3022 The microscope object for which to validate the FIB pattern settings.
3023 settings : dict
3024 The dictionary containing the FIB pattern settings.
3025 yml_format : tbt.YMLFormatVersion
3026 The format specified by the version of the .yml file.
3027 step_name : str
3028 The name of the step in the .yml file.
3030 Returns
3031 -------
3032 Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern]
3033 The validated FIB pattern object.
3035 Raises
3036 ------
3037 KeyError
3038 If required settings are missing from the .yml file or if multiple pattern types are specified.
3039 ValueError
3040 If the application file is unsupported or invalid for the specified pattern type.
3041 """
3043 if yml_format.version >= 1.0:
3044 application_file = settings.get("application_file")
3045 # determine type of pattern (only one allowed)
3046 type_set_db = settings.get("type")
3047 if type_set_db is None:
3048 raise KeyError(
3049 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'fib' step_type for step '{step_name}'."
3050 )
3051 rectangle_settings = type_set_db.get("rectangle")
3052 regular_cross_section_settings = type_set_db.get("regular_cross_section")
3053 cleaning_cross_section_settings = type_set_db.get("cleaning_cross_section")
3054 selected_area_settings = type_set_db.get("selected_area")
3056 pattern_settings = [
3057 rectangle_settings,
3058 regular_cross_section_settings,
3059 cleaning_cross_section_settings,
3060 selected_area_settings,
3061 ]
3062 (
3063 rectangle_pattern,
3064 regular_cross_section_pattern,
3065 cleaning_cross_section_pattern,
3066 selected_area_pattern,
3067 ) = (None, None, None, None)
3068 # pattern_names = [val.value for val in tbt.FIBPatternType] # remembers order
3069 # assert pattern_names == [
3070 # "rectangle",
3071 # "regular_cross_section",
3072 # "cleaning_cross_section",
3073 # "selected_area",
3074 # ]
3075 pattern_names = [
3076 "rectangle",
3077 "regular_cross_section",
3078 "cleaning_cross_section",
3079 "selected_area",
3080 ]
3081 pattern_types = [
3082 rectangle_pattern,
3083 regular_cross_section_pattern,
3084 cleaning_cross_section_pattern,
3085 selected_area_pattern,
3086 ]
3088 for type in range(len(pattern_settings)):
3089 db = pattern_settings[type]
3090 if db is None:
3091 pattern_types[type] = False
3092 else:
3093 pattern_types[type] = not ut.none_value_dictionary(db)
3095 # one and only one type of pattern can have settings
3096 if sum(pattern_types) != 1:
3097 raise KeyError(
3098 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."
3099 )
3100 pattern_type = tbt.FIBPatternType(pattern_names[pattern_types.index(True)])
3102 # check application file
3103 valid_applications = application_files(microscope=microscope)
3104 # TODO make this print out pretty
3105 # display_applications = ",".join(valid_applications)
3106 # display_applications = ut.tabular_list(data=valid_applications)
3107 if application_file not in valid_applications:
3108 raise ValueError(
3109 f"Unsupported FIB application file of '{application_file}' on step '{step_name}. Supported applications on this microscope are: \n{valid_applications}'"
3110 )
3112 if pattern_type == tbt.FIBPatternType.RECTANGLE:
3113 validate_fib_box_settings(
3114 settings=rectangle_settings,
3115 yml_format=yml_format,
3116 step_name=step_name,
3117 pattern_type=pattern_type,
3118 )
3120 pattern = tbt.FIBPattern(
3121 application=application_file,
3122 type=pattern_type,
3123 geometry=tbt.FIBRectanglePattern(
3124 center_um=tbt.Point(
3125 x=rectangle_settings.get("center").get("x_um"),
3126 y=rectangle_settings.get("center").get("y_um"),
3127 ),
3128 width_um=rectangle_settings.get("width_um"),
3129 height_um=rectangle_settings.get("height_um"),
3130 depth_um=rectangle_settings.get("depth_um"),
3131 scan_direction=tbt.FIBPatternScanDirection(
3132 rectangle_settings.get("scan_direction")
3133 ),
3134 scan_type=tbt.FIBPatternScanType(rectangle_settings.get("scan_type")),
3135 ),
3136 )
3137 # make sure application file is valid for this pattern type:
3138 try:
3139 geometry = pattern.geometry
3140 microscope.patterning.set_default_application_file(pattern.application)
3141 microscope.patterning.create_rectangle(
3142 center_x=geometry.center_um.x * Conversions.UM_TO_M,
3143 center_y=geometry.center_um.y * Conversions.UM_TO_M,
3144 width=geometry.width_um * Conversions.UM_TO_M,
3145 height=geometry.height_um * Conversions.UM_TO_M,
3146 depth=geometry.depth_um * Conversions.UM_TO_M,
3147 )
3148 microscope.patterning.clear_patterns()
3149 except:
3150 raise ValueError(
3151 f'Invalid application file of "{pattern.application}" for Rectangle pattern type. Please select or create an appropriate application file.'
3152 )
3154 return pattern
3155 elif pattern_type == tbt.FIBPatternType.REGULAR_CROSS_SECTION:
3156 validate_fib_box_settings(
3157 settings=regular_cross_section_settings,
3158 yml_format=yml_format,
3159 step_name=step_name,
3160 pattern_type=pattern_type,
3161 )
3162 pattern = tbt.FIBPattern(
3163 application=application_file,
3164 type=pattern_type,
3165 geometry=tbt.FIBRegularCrossSection(
3166 center_um=tbt.Point(
3167 x=regular_cross_section_settings.get("center").get("x_um"),
3168 y=regular_cross_section_settings.get("center").get("y_um"),
3169 ),
3170 width_um=regular_cross_section_settings.get("width_um"),
3171 height_um=regular_cross_section_settings.get("height_um"),
3172 depth_um=regular_cross_section_settings.get("depth_um"),
3173 scan_direction=tbt.FIBPatternScanDirection(
3174 regular_cross_section_settings.get("scan_direction")
3175 ),
3176 scan_type=tbt.FIBPatternScanType(
3177 regular_cross_section_settings.get("scan_type")
3178 ),
3179 ),
3180 )
3181 # make sure application file is valid for this pattern type:
3182 try:
3183 geometry = pattern.geometry
3184 microscope.patterning.set_default_application_file(pattern.application)
3185 microscope.patterning.create_regular_cross_section(
3186 center_x=geometry.center_um.x * Conversions.UM_TO_M,
3187 center_y=geometry.center_um.y * Conversions.UM_TO_M,
3188 width=geometry.width_um * Conversions.UM_TO_M,
3189 height=geometry.height_um * Conversions.UM_TO_M,
3190 depth=geometry.depth_um * Conversions.UM_TO_M,
3191 )
3192 microscope.patterning.clear_patterns()
3193 except:
3194 raise ValueError(
3195 f'Invalid application file of "{pattern.application}" for Regular Cross Section pattern type. Please select or create an appropriate application file.'
3196 )
3197 return pattern
3198 elif pattern_type == tbt.FIBPatternType.CLEANING_CROSS_SECTION:
3199 validate_fib_box_settings(
3200 settings=cleaning_cross_section_settings,
3201 yml_format=yml_format,
3202 step_name=step_name,
3203 pattern_type=pattern_type,
3204 )
3205 pattern = tbt.FIBPattern(
3206 application=application_file,
3207 type=pattern_type,
3208 geometry=tbt.FIBCleaningCrossSection(
3209 center_um=tbt.Point(
3210 x=cleaning_cross_section_settings.get("center").get("x_um"),
3211 y=cleaning_cross_section_settings.get("center").get("y_um"),
3212 ),
3213 width_um=cleaning_cross_section_settings.get("width_um"),
3214 height_um=cleaning_cross_section_settings.get("height_um"),
3215 depth_um=cleaning_cross_section_settings.get("depth_um"),
3216 scan_direction=tbt.FIBPatternScanDirection(
3217 cleaning_cross_section_settings.get("scan_direction")
3218 ),
3219 scan_type=tbt.FIBPatternScanType(
3220 cleaning_cross_section_settings.get("scan_type")
3221 ),
3222 ),
3223 )
3224 # make sure application file is valid for this pattern type:
3225 try:
3226 geometry = pattern.geometry
3227 microscope.patterning.set_default_application_file(pattern.application)
3228 microscope.patterning.create_cleaning_cross_section(
3229 center_x=geometry.center_um.x * Conversions.UM_TO_M,
3230 center_y=geometry.center_um.y * Conversions.UM_TO_M,
3231 width=geometry.width_um * Conversions.UM_TO_M,
3232 height=geometry.height_um * Conversions.UM_TO_M,
3233 depth=geometry.depth_um * Conversions.UM_TO_M,
3234 )
3235 microscope.patterning.clear_patterns()
3236 except:
3237 raise ValueError(
3238 f'Invalid application file of "{pattern.application}" for Cleaning Cross Section pattern type. Please select or create an appropriate application file.'
3239 )
3240 return pattern
3241 elif pattern_type == tbt.FIBPatternType.SELECTED_AREA:
3242 validate_fib_selected_area_settings(
3243 settings=selected_area_settings,
3244 yml_format=yml_format,
3245 step_name=step_name,
3246 pattern_type=pattern_type,
3247 )
3248 pattern = tbt.FIBPattern(
3249 application=application_file,
3250 type=pattern_type,
3251 geometry=tbt.FIBStreamPattern(
3252 dwell_us=selected_area_settings.get("dwell_us"),
3253 repeats=selected_area_settings.get("repeats"),
3254 recipe=Path(selected_area_settings.get("recipe_file")),
3255 mask=Path(selected_area_settings.get("mask_file")),
3256 ),
3257 )
3259 # make sure application file is valid for this pattern type:
3260 try:
3261 microscope.patterning.set_default_application_file(pattern.application)
3262 microscope.patterning.create_rectangle(
3263 center_x=0.0,
3264 center_y=0.0,
3265 width=10.0e-6,
3266 height=10.0e-6,
3267 depth=1.0e-6,
3268 )
3269 microscope.patterning.clear_patterns()
3270 except:
3271 raise ValueError(
3272 f'Invalid application file of "{pattern.application}" for Selected Area pattern. Please use an application file for Rectangle milling.'
3273 )
3275 return pattern
3276 else:
3277 raise KeyError(
3278 f"Invalid pattern type of {pattern_type}. Supported pattern types are: {[i.value for i in tbt.FIBPatternType]}"
3279 )
3282def validate_fib_box_settings(
3283 settings: dict,
3284 yml_format: tbt.YMLFormatVersion,
3285 step_name: str,
3286 pattern_type: tbt.FIBPatternType,
3287) -> bool:
3288 """
3289 Perform schema checking for FIB box pattern setting dictionary.
3291 This function validates the FIB box pattern settings dictionary based on the specified yml format.
3293 Parameters
3294 ----------
3295 settings : dict
3296 The dictionary containing the FIB box pattern settings.
3297 yml_format : tbt.YMLFormatVersion
3298 The format specified by the version of the .yml file.
3299 step_name : str
3300 The name of the step in the .yml file.
3301 pattern_type : tbt.FIBPatternType
3302 The type of the FIB pattern.
3304 Returns
3305 -------
3306 bool
3307 True if the settings are valid, False otherwise.
3309 Raises
3310 ------
3311 ValueError
3312 If the yml version is unsupported or if the settings do not satisfy the specified schema.
3313 """
3314 if yml_format.version >= 1.0:
3315 # flattens nested dictionary, adding "_" separator
3316 flat_settings = ut._flatten(settings)
3317 schema = Schema(
3318 {
3319 "center_x_um": And(
3320 float,
3321 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.",
3322 ),
3323 "center_y_um": And(
3324 float,
3325 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.",
3326 ),
3327 "width_um": And(
3328 float,
3329 lambda x: x > 0,
3330 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.",
3331 ),
3332 "height_um": And(
3333 float,
3334 lambda x: x > 0,
3335 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.",
3336 ),
3337 "depth_um": And(
3338 float,
3339 lambda x: x > 0,
3340 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.",
3341 ),
3342 "scan_direction": And(
3343 str,
3344 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanDirection),
3345 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]}",
3346 ),
3347 "scan_type": And(
3348 str,
3349 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanType),
3350 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]}",
3351 ),
3352 },
3353 ignore_extra_keys=True,
3354 )
3355 try:
3356 schema.validate(flat_settings)
3357 except UnboundLocalError:
3358 raise ValueError(
3359 f"Error. Unsupported yml version {yml_format.version} provided."
3360 )
3361 return True
3364def validate_fib_selected_area_settings(
3365 settings: dict,
3366 yml_format: tbt.YMLFormatVersion,
3367 step_name: str,
3368 pattern_type: tbt.FIBPatternType,
3369) -> bool:
3370 """
3371 Perform schema checking for FIB selected area pattern setting dictionary.
3373 This function validates the FIB selected area pattern settings dictionary based on the specified yml format.
3375 Parameters
3376 ----------
3377 settings : dict
3378 The dictionary containing the FIB selected area pattern settings.
3379 yml_format : tbt.YMLFormatVersion
3380 The format specified by the version of the .yml file.
3381 step_name : str
3382 The name of the step in the .yml file.
3383 pattern_type : tbt.FIBPatternType
3384 The type of the FIB pattern.
3386 Returns
3387 -------
3388 bool
3389 True if the settings are valid, False otherwise.
3391 Raises
3392 ------
3393 ValueError
3394 If the yml version is unsupported or if the settings do not satisfy the specified schema.
3395 """
3396 if yml_format.version >= 1.0:
3397 schema = Schema(
3398 {
3399 "dwell_us": And(
3400 float,
3401 lambda x: x > 0,
3402 # check all float error versions of modulus
3403 Or(
3404 lambda x: math.isclose(
3405 x % Constants.stream_pattern_base_dwell_us,
3406 0,
3407 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3408 ),
3409 lambda x: math.isclose(
3410 x % Constants.stream_pattern_base_dwell_us,
3411 Constants.stream_pattern_base_dwell_us,
3412 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3413 ),
3414 lambda x: math.isclose(
3415 x % Constants.stream_pattern_base_dwell_us,
3416 -Constants.stream_pattern_base_dwell_us,
3417 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5,
3418 ),
3419 ),
3420 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.",
3421 ),
3422 "repeats": And(
3423 int,
3424 lambda x: x > 0,
3425 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.",
3426 ),
3427 "recipe_file": And(
3428 str,
3429 lambda x: Path(x).is_file(),
3430 lambda x: Path(x).suffix == ".py",
3431 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.",
3432 ),
3433 "mask_file": And(
3434 str,
3435 lambda x: Path(x).suffix == ".tif",
3436 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.",
3437 ),
3438 },
3439 ignore_extra_keys=True,
3440 )
3441 try:
3442 schema.validate(settings)
3443 except UnboundLocalError:
3444 raise ValueError(
3445 f"Error. Unsupported yml version {yml_format.version} provided."
3446 )
3447 return True
3450def step(
3451 microscope: tbt.Microscope,
3452 # slice_number: str,
3453 step_name: str,
3454 step_settings: dict,
3455 general_settings: tbt.GeneralSettings,
3456 yml_format: tbt.YMLFormatVersion,
3457) -> tbt.Step:
3458 """
3459 Create a step object for different step types, including validation.
3461 This function creates a `Step` object for the specified step type and performs validation.
3463 Parameters
3464 ----------
3465 microscope : tbt.Microscope
3466 The microscope object for which to create the step.
3467 step_name : str
3468 The name of the step in the .yml file.
3469 step_settings : dict
3470 The dictionary containing the step settings.
3471 general_settings : tbt.GeneralSettings
3472 The general settings object.
3473 yml_format : tbt.YMLFormatVersion
3474 The format specified by the version of the .yml file.
3476 Returns
3477 -------
3478 tbt.Step
3479 The step object.
3481 Raises
3482 ------
3483 NotImplementedError
3484 If the step type is unsupported.
3485 KeyError
3486 If required settings are missing or invalid.
3487 """
3489 # parsing settings
3490 step_type_value = step_settings[yml_format.step_general_key][
3491 yml_format.step_type_key
3492 ]
3493 if not ut.valid_enum_entry(step_type_value, tbt.StepType):
3494 raise NotImplementedError(
3495 f"Unsupported step type of '{step_type_value}', for step name '{step_name}' supported types are: {[i.value for i in tbt.StepType]}."
3496 )
3497 step_type = tbt.StepType(step_type_value)
3499 step_number = step_settings[yml_format.step_general_key][yml_format.step_number_key]
3500 if not isinstance(step_number, int) or (step_number < 1):
3501 raise KeyError(
3502 f"Invalid step number of '{step_number}', for step name '{step_name}'. Must be a positive integer greater than 0."
3503 )
3504 step_frequency = step_settings[yml_format.step_general_key][
3505 yml_format.step_frequency_key
3506 ]
3507 if not isinstance(step_frequency, int) or (step_frequency < 1):
3508 raise KeyError(
3509 f"Invalid step frequency of '{step_frequency}', for step name '{step_name}'. Must be a positive integer greater than 0."
3510 )
3511 stage_db = step_settings[yml_format.step_general_key][
3512 yml_format.step_stage_settings_key
3513 ]
3515 # check and validate stage
3516 stage_settings = stage_position_settings(
3517 microscope=microscope,
3518 step_name=step_name,
3519 general_settings=general_settings,
3520 step_stage_settings=stage_db,
3521 yml_format=yml_format,
3522 )
3524 # TODO
3525 # operation_settings, could use match statement in python >= 3.10
3526 if step_type == tbt.StepType.EBSD:
3527 operation_settings = ebsd(
3528 microscope=microscope,
3529 step_settings=step_settings,
3530 step_name=step_name,
3531 yml_format=yml_format,
3532 )
3533 if step_type == tbt.StepType.EDS:
3534 operation_settings = eds(
3535 microscope=microscope,
3536 step_settings=step_settings,
3537 step_name=step_name,
3538 yml_format=yml_format,
3539 )
3540 if step_type == tbt.StepType.IMAGE:
3541 operation_settings = image(
3542 microscope=microscope,
3543 step_settings=step_settings,
3544 step_name=step_name,
3545 yml_format=yml_format,
3546 )
3547 if step_type == tbt.StepType.LASER:
3548 operation_settings = laser(
3549 microscope=microscope,
3550 step_settings=step_settings,
3551 step_name=step_name,
3552 yml_format=yml_format,
3553 )
3554 if step_type == tbt.StepType.CUSTOM:
3555 operation_settings = custom(
3556 microscope=microscope,
3557 step_settings=step_settings,
3558 step_name=step_name,
3559 yml_format=yml_format,
3560 )
3561 if step_type == tbt.StepType.FIB:
3562 operation_settings = fib(
3563 microscope=microscope,
3564 step_settings=step_settings,
3565 step_name=step_name,
3566 yml_format=yml_format,
3567 )
3569 step_object = tbt.Step(
3570 type=step_type,
3571 name=step_name,
3572 number=step_number,
3573 frequency=step_frequency,
3574 stage=stage_settings,
3575 operation_settings=operation_settings,
3576 )
3578 return step_object