Coverage for src/pytribeam/factory.py: 77%

677 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-09-09 18:27 +0000

1#!/usr/bin/python3 

2"""Factory and validation functions for `pytribeam` settings objects. 

3 

4This module converts raw configuration dictionaries and live microscope state 

5into structured `pytribeam.types` objects. It is the main construction layer used 

6by the workflow to validate YAML input, read current hardware settings, enforce 

7limits, and create typed settings for imaging, FIB, laser, EBSD, EDS, custom, 

8stage, beam, detector, and scan operations. 

9 

10Most users should not need to call the low-level validation helpers directly. 

11Workflow code typically enters this module through `general`, `step`, or one of 

12the `active_*` functions. 

13 

14## Typical usage 

15 

16Create experiment-wide general settings from a parsed YAML dictionary: 

17 

18```python 

19import pytribeam.factory as factory 

20 

21general_settings = factory.general( 

22 general_db=general_db, 

23 yml_format=yml_format, 

24) 

25``` 

26 

27Create a typed workflow step from a YAML step dictionary: 

28 

29```python 

30step = factory.step( 

31 microscope=microscope, 

32 step_name=step_name, 

33 step_settings=step_settings, 

34 general_settings=general_settings, 

35 yml_format=yml_format, 

36) 

37``` 

38 

39Read the current microscope state into `pytribeam` settings objects: 

40 

41```python 

42beam = factory.active_beam_with_settings(microscope) 

43image_settings = factory.active_image_settings(microscope) 

44stage_position = factory.active_stage_position_settings(microscope) 

45``` 

46 

47## Main entry points 

48 

49- `general`: validate and create `tbt.GeneralSettings`. 

50- `step`: validate and create a `tbt.Step` for the configured step type. 

51- `active_image_settings`: read the current microscope imaging state. 

52- `active_beam_with_settings`: read the active beam and beam settings. 

53- `active_detector_settings`: read the active detector settings. 

54- `active_scan_settings`: read the active scan settings. 

55- `active_stage_position_settings`: read the current stage position in user 

56 units. 

57- `active_laser_state`: read the current laser state. 

58- `stage_limits`, `beam_limits`, and `scan_limits`: read hardware limits from 

59 the active microscope connection. 

60 

61## Configuration workflow 

62 

63The workflow module uses this factory layer to transform parsed YAML data into 

64validated runtime objects: 

65 

66```text 

67YAML file 

68 -> raw dictionaries 

69 -> validation helpers 

70 -> typed settings objects 

71 -> workflow execution 

72``` 

73 

74For example, image, FIB, laser, EBSD, EDS, and custom steps are all constructed 

75from dictionaries using step-type-specific factory functions. These functions 

76validate required keys, convert strings to enums, check numeric limits, and 

77return the appropriate `tbt.*Settings` object. 

78 

79## Validation behavior 

80 

81Validation helpers check user configuration against package schema information, 

82supported enum values, microscope hardware limits, and workflow constraints. 

83Invalid settings generally raise `ValueError`, `TypeError`, `KeyError`, or 

84`NotImplementedError` with context about the step or setting that failed. 

85 

86## Active-state helpers 

87 

88Functions beginning with `active_` read the current microscope or laser state and 

89return a corresponding `pytribeam.types` object. These are useful for logging, 

90GUI display, default-setting generation, and validating that requested hardware 

91changes were applied successfully. 

92 

93## Units 

94 

95Factory functions preserve the package-wide unit conventions used by 

96`pytribeam.types`. User-facing values generally use explicit suffixes such as 

97`_mm`, `_um`, `_deg`, `_kv`, `_na`, and `_us`, while values read from or written 

98to hardware APIs may be converted internally as needed. 

99 

100> **Warning** 

101> 

102> Many functions in this module query live microscope or laser state. Ensure that 

103> the required hardware connections and external APIs are available before using 

104> active-state or hardware-limit factory functions. 

105 

106<hr style="height: 12px; background-color: #333; border: none;"> 

107""" 

108 

109__all__ = [ 

110 "active_fib_applications", 

111 "active_beam_with_settings", 

112 "active_detector_settings", 

113 "active_image_settings", 

114 "active_imaging_device", 

115 "active_scan_settings", 

116 "active_stage_position_settings", 

117 "active_laser_state", 

118 "active_laser_settings", 

119 "available_detector_types", 

120 "available_detector_modes", 

121 "beam_object_type", 

122 "stage_limits", 

123 "beam_limits", 

124 "scan_limits", 

125 "general", 

126 "image", 

127 "fib", 

128 "laser", 

129 "ebsd", 

130 "eds", 

131 "custom", 

132 "step", 

133 "string_to_res", 

134 "valid_string_resolution", 

135] 

136 

137## python standard libraries 

138from pathlib import Path 

139from typing import List, Union 

140import warnings 

141from functools import singledispatch 

142import math 

143 

144 

145# 3rd party libraries 

146from schema import And, Or, Schema 

147 

148# Local 

149import pytribeam.insertable_devices as devices 

150import pytribeam.image as img 

151import pytribeam.utilities as ut 

152import pytribeam.stage as stage 

153from pytribeam.constants import Conversions, Constants 

154from pytribeam.utilities import application_files 

155 

156try: 

157 import pytribeam.laser as fs_laser 

158except: 

159 pass 

160import pytribeam.types as tbt 

161 

162 

163def active_fib_applications( 

164 microscope: tbt.Microscope, 

165) -> list: 

166 """ 

167 Retrieve a list of all active FIB (Focused Ion Beam) patterning application files from the microscope. 

168 

169 ## Parameters 

170 

171 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files. 

172 

173 ## Returns 

174 

175 - `list`: A list of active FIB patterning application files. 

176 

177 """ 

178 return application_files(microscope) 

179 

180 

181def active_beam_with_settings( 

182 microscope: tbt.Microscope, 

183) -> tbt.Beam: 

184 """ 

185 Retrieve the current active beam and its settings from the microscope to create a beam object. 

186 

187 This function grabs the current beam and its settings on the microscope to make a beam object. These settings fully depend on the currently active beam as determined by xTUI. Tolerance values for voltage and current are auto-populated as a ratio of current values predetermined in the `Constants` class. 

188 

189 ## Parameters 

190 

191 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active beam and its settings. 

192 

193 ## Returns 

194 

195 - `tbt.Beam`: The active beam object with its settings. 

196 """ 

197 selected_beam = active_imaging_device(microscope=microscope) 

198 beam = ut.beam_type(selected_beam, microscope) 

199 

200 voltage_kv = round(beam.high_voltage.value * Conversions.V_TO_KV, 6) 

201 voltage_tol_kv = round(voltage_kv * Constants.voltage_tol_ratio, 6) 

202 

203 current_na = round(beam.beam_current.value * Conversions.A_TO_NA, 6) 

204 current_tol_na = round(current_na * Constants.current_tol_ratio, 6) 

205 

206 hfw_mm = round(beam.horizontal_field_width.value * Conversions.M_TO_MM, 6) 

207 

208 working_dist_mm = round(beam.working_distance.value * Conversions.M_TO_MM, 6) 

209 

210 angular_correction = getattr(beam, "angular_correction", None) 

211 if angular_correction is None: 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true

212 dynamic_focus = None 

213 tilt_correction = None 

214 else: 

215 dynamic_focus = bool(angular_correction.dynamic_focus.is_on) 

216 tilt_correction = bool(angular_correction.tilt_correction.is_on) 

217 

218 active_settings = tbt.BeamSettings( 

219 voltage_kv=voltage_kv, 

220 current_na=current_na, 

221 hfw_mm=hfw_mm, 

222 working_dist_mm=working_dist_mm, 

223 voltage_tol_kv=voltage_tol_kv, 

224 current_tol_na=current_tol_na, 

225 dynamic_focus=dynamic_focus, 

226 tilt_correction=tilt_correction, 

227 ) 

228 

229 return type(selected_beam)(settings=active_settings) 

230 

231 

232def active_detector_settings( 

233 microscope: tbt.Microscope, 

234) -> tbt.Detector: 

235 """ 

236 Retrieve the current active detector settings from the microscope to create a detector object. 

237 

238 This function grabs the current detector settings on the microscope to make a detector object. These settings fully depend on the currently active detector as determined by xTUI. 

239 

240 ## Parameters 

241 

242 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active detector settings. 

243 

244 ## Returns 

245 

246 - `tbt.Detector`: The active detector object with its settings. 

247 """ 

248 

249 detector_type = microscope.detector.type.value 

250 detector_mode = microscope.detector.mode.value 

251 brightness = microscope.detector.brightness.value 

252 contrast = microscope.detector.contrast.value 

253 auto_cb_settings = tbt.ScanArea( 

254 left=None, 

255 top=None, 

256 width=None, 

257 height=None, 

258 ) 

259 custom_settings = None 

260 

261 active_detector = tbt.Detector( 

262 type=detector_type, 

263 mode=detector_mode, 

264 brightness=brightness, 

265 contrast=contrast, 

266 auto_cb_settings=auto_cb_settings, 

267 custom_settings=custom_settings, 

268 ) 

269 

270 return active_detector 

271 

272 

273def active_image_settings(microscope: tbt.Microscope) -> tbt.ImageSettings: 

274 """ 

275 Retrieve the current active image settings from the microscope to create an image settings object. 

276 

277 This function grabs the current beam, detector, and scan settings on the microscope to make an image settings object. The bit depth is set to the default color depth defined in the `Constants` class. 

278 

279 ## Parameters 

280 

281 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active image settings. 

282 

283 ## Returns 

284 

285 - `tbt.ImageSettings`: The active image settings object. 

286 """ 

287 beam = active_beam_with_settings(microscope=microscope) 

288 detector = active_detector_settings(microscope=microscope) 

289 scan = active_scan_settings(microscope=microscope) 

290 bit_depth = Constants.default_color_depth 

291 

292 active_image_settings = tbt.ImageSettings( 

293 microscope=microscope, 

294 beam=beam, 

295 detector=detector, 

296 scan=scan, 

297 bit_depth=bit_depth, 

298 ) 

299 

300 return active_image_settings 

301 

302 

303def active_imaging_device(microscope: tbt.Microscope) -> tbt.Beam: 

304 """ 

305 Determine the active imaging device and return the corresponding internal beam type object with null beam settings. 

306 

307 This function identifies the currently active imaging device on the microscope and returns the appropriate beam type object (electron or ion) with null beam settings. 

308 

309 ## Parameters 

310 

311 - `microscope` (`tbt.Microscope`): The microscope object from which to determine the active imaging device. 

312 

313 ## Returns 

314 

315 - `tbt.Beam`: The active beam object with null beam settings. 

316 

317 ## Raises 

318 

319 - `ValueError`: If the currently selected device is neither an electron beam nor an ion beam. 

320 """ 

321 curr_device = tbt.Device(microscope.imaging.get_active_device()) 

322 if curr_device == tbt.Device.ELECTRON_BEAM: 

323 selected_beam = beam_object_type(type=tbt.BeamType.ELECTRON)( 

324 settings=tbt.BeamSettings() 

325 ) 

326 elif curr_device == tbt.Device.ION_BEAM: 326 ↛ 331line 326 didn't jump to line 331 because the condition on line 326 was always true

327 selected_beam = beam_object_type(type=tbt.BeamType.ION)( 

328 settings=tbt.BeamSettings() 

329 ) 

330 else: 

331 raise ValueError( 

332 f"Currently selected device {curr_device}, make sure a quadrant in xTUI with either an Electron beam or Ion beam active is selected." 

333 ) 

334 return selected_beam 

335 

336 

337def active_scan_settings( 

338 microscope: tbt.Microscope, 

339) -> tbt.Scan: 

340 """ 

341 Retrieve the current active scan settings from the microscope to create a scan object. 

342 

343 This function grabs the current scan settings on the microscope to make a scan object. These settings fully depend on the currently active scan settings as determined by xTUI. 

344 

345 ## Parameters 

346 

347 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active scan settings. 

348 

349 ## Returns 

350 

351 - `tbt.Scan`: The active scan object with its settings. 

352 """ 

353 selected_beam = active_imaging_device(microscope=microscope) 

354 beam = ut.beam_type(selected_beam, microscope) 

355 

356 rotation_deg = beam.scanning.rotation.value * Conversions.RAD_TO_DEG 

357 dwell_time_us = beam.scanning.dwell_time.value * Conversions.S_TO_US 

358 current_res = beam.scanning.resolution.value 

359 res = string_to_res(current_res) 

360 resolution = tbt.PresetResolution(res) 

361 

362 active_scan = tbt.Scan( 

363 rotation_deg=rotation_deg, 

364 dwell_time_us=dwell_time_us, 

365 resolution=resolution, 

366 # mode=mode, 

367 ) 

368 

369 return active_scan 

370 

371 

372def active_stage_position_settings(microscope: tbt.Microscope) -> tbt.StagePositionUser: 

373 """ 

374 Retrieve the current stage position in the raw coordinate system and user units [mm, deg]. 

375 

376 This function sets the stage coordinate system to RAW, retrieves the current stage position in encoder units (meters and radians), converts it to user units (millimeters and degrees), and ensures the r-axis is within the axis limit. 

377 

378 ## Parameters 

379 

380 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the current stage position. 

381 

382 ## Returns 

383 

384 - `tbt.StagePositionUser`: The current stage position in user units [mm, deg]. 

385 """ 

386 stage.coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW) 

387 # encoder positions (pos) are in meters and radians 

388 direct_encoder_pos = microscope.specimen.stage.current_position 

389 x_m, y_m, z_m = direct_encoder_pos.x, direct_encoder_pos.y, direct_encoder_pos.z 

390 r_rad, t_rad = direct_encoder_pos.r, direct_encoder_pos.t 

391 coord_system_str = direct_encoder_pos.coordinate_system 

392 

393 encoder_pos = tbt.StagePositionEncoder( 

394 x=x_m, y=y_m, z=z_m, r=r_rad, t=t_rad, coordinate_system=coord_system_str 

395 ) 

396 user_pos = stage.encoder_to_user_position(encoder_pos) 

397 

398 # ensure r-axis is kept in axis limit 

399 # ------------------------------------------------ 

400 # THIS IS NOT NEEDED, AUTOSCRIPT DOES THIS ALREADY 

401 # ------------------------------------------------ 

402 # if not ut.in_interval( 

403 # val=user_pos.r_deg, 

404 # limit=Constants.rotation_axis_limit_deg, 

405 # type=tbt.IntervalType.RIGHT_OPEN, 

406 # ): 

407 # # used as right-open internal: 180.0 is not valid and should be converted to -180.0 

408 # while user_pos.r_deg >= Constants.rotation_axis_limit_deg.max: 

409 # new_r_deg = user_pos.r_deg - 360.0 

410 # while user_pos.r_deg < Constants.rotation_axis_limit_deg.min: 

411 # new_r_deg = user_pos.r_deg + 360.0 

412 # new_r_deg = round(new_r_deg, 6) 

413 # user_pos = tbt.StagePositionUser( 

414 # x_mm=user_pos.x_mm, 

415 # y_mm=user_pos.y_mm, 

416 # z_mm=user_pos.z_mm, 

417 # r_deg=new_r_deg, 

418 # t_deg=user_pos.t_deg, 

419 # ) 

420 

421 return user_pos 

422 

423 

424def active_laser_state() -> tbt.LaserState: 

425 """ 

426 Retrieve the current state of the laser, including various properties that can be quickly read. 

427 

428 This function returns a dictionary object for all properties that can be quickly read from the laser (not exhaustive). Power can be read but has its own method and is more involved. Flipper configuration can only be set, not read. 

429 

430 ## Returns 

431 

432 tbt.LaserState 

433 The current state of the laser, including wavelength, frequency, pulse divider, pulse energy, objective position, beam shift, pattern, and expected pattern duration. 

434 

435 ## Raises 

436 

437 KeyError 

438 If an unsupported LaserPatternType is encountered. 

439 

440 """ 

441 vals = fs_laser.tfs_laser.Laser_ReadValues() 

442 vals["objective_position_mm"] = fs_laser.tfs_laser.LIP_GetZPosition() 

443 vals["beam_shift_um_x"] = fs_laser.tfs_laser.BeamShift_Get_X() 

444 vals["beam_shift_um_y"] = fs_laser.tfs_laser.BeamShift_Get_Y() 

445 vals["shutter_state"] = fs_laser.tfs_laser.Shutter_GetState() 

446 vals["pattern"] = fs_laser.tfs_laser.Patterning_ReadValues() 

447 vals["expected_pattern_duration_s"] = ( 

448 fs_laser.tfs_laser.Patterning_GetExpectedDuration() 

449 ) 

450 

451 pattern_db = vals["pattern"] 

452 pattern_type = pattern_db["patternType"].lower() 

453 if not ut.valid_enum_entry(pattern_type, tbt.LaserPatternType): 453 ↛ 457line 453 didn't jump to line 457 because the condition on line 453 was always true

454 raise KeyError( 454 ↛ exit,   454 ↛ exit2 missed branches: 1) line 454 didn't jump to the function exit, 2) line 454 didn't except from function 'active_laser_state' because the raise on line 454 wasn't executed

455 f"Unsupported LaserPatternType of {pattern_type}, supported types include: {[i.value for i in tbt.LaserPatternType]}" 

456 ) 

457 pattern_type = tbt.LaserPatternType(pattern_type) 

458 mode = tbt.LaserPatternMode(pattern_db["patterningMode"].lower()) 

459 if mode == tbt.LaserPatternMode.COARSE: 459 ↛ 460,   459 ↛ 4622 missed branches: 1) line 459 didn't jump to line 460 because the condition on line 459 was never true, 2) line 459 didn't jump to line 462 because the condition on line 459 was always true

460 pixel_dwell_ms = pattern_db["dwellTime"] 

461 pulses_per_pixel = None 

462 elif mode == tbt.LaserPatternMode.FINE: 

463 pixel_dwell_ms = None 

464 pulses_per_pixel = pattern_db["pulsesPerPixel"] 

465 rotation_deg = pattern_db["patternRotation_deg"] 

466 

467 if pattern_type == tbt.LaserPatternType.BOX: 

468 geometry = tbt.LaserBoxPattern( 

469 passes=pattern_db["passes"], 

470 size_x_um=pattern_db["xSize_um"], 

471 size_y_um=pattern_db["ySize_um"], 

472 pitch_x_um=pattern_db["xPitch_um"], 

473 pitch_y_um=pattern_db["yPitch_um"], 

474 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()), 

475 coordinate_ref=tbt.CoordinateReference( 

476 pattern_db["coordReference"].lower() 

477 ), 

478 ) 

479 if pattern_type == tbt.LaserPatternType.LINE: 

480 geometry = tbt.LaserLinePattern( 

481 passes=pattern_db["passes"], 

482 size_um=pattern_db["xSize_um"], 

483 pitch_um=pattern_db["xPitch_um"], 

484 scan_type=tbt.LaserScanType(pattern_db["scanningMode"].lower()), 

485 ) 

486 

487 pattern = tbt.LaserPattern( 

488 mode=mode, 

489 rotation_deg=rotation_deg, 

490 geometry=geometry, 

491 pulses_per_pixel=pulses_per_pixel, 

492 pixel_dwell_ms=pixel_dwell_ms, 

493 ) 

494 

495 state = tbt.LaserState( 

496 wavelength_nm=vals["wavelength_nm"], 

497 frequency_khz=vals["frequency_kHz"], 

498 pulse_divider=vals["pulse_divider"], 

499 pulse_energy_uj=vals["pulse_energy_uJ"], 

500 objective_position_mm=vals["objective_position_mm"], 

501 beam_shift_um=tbt.Point(x=vals["beam_shift_um_x"], y=vals["beam_shift_um_y"]), 

502 pattern=pattern, 

503 expected_pattern_duration_s=vals["expected_pattern_duration_s"], 

504 ) 

505 

506 return state 

507 

508 

509def active_laser_settings(microscope: tbt.Microscope) -> tbt.LaserSettings: 

510 """ 

511 Retrieve the current active laser settings from the microscope to create a laser settings object. 

512 

513 This function grabs the current laser state and uses it to create a laser settings object. Some values cannot be read by Laser Control and can only be set. For example, polarization will default to "Vertical" as this value cannot be read. 

514 

515 ## Parameters 

516 

517 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the active laser settings. 

518 

519 ## Returns 

520 

521 - `tbt.LaserSettings`: The active laser settings object. 

522 """ 

523 state = active_laser_state() 

524 

525 settings = tbt.LaserSettings( 

526 microscope=microscope, 

527 pulse=tbt.LaserPulse( 

528 wavelength_nm=state.wavelength_nm, 

529 divider=state.pulse_divider, 

530 energy_uj=state.pulse_energy_uj, 

531 polarization=tbt.LaserPolarization.VERTICAL, # TODO, can't read this, can only set it 

532 ), 

533 objective_position_mm=state.objective_position_mm, 

534 beam_shift_um=state.beam_shift_um, 

535 pattern=state.pattern, 

536 ) 

537 

538 return settings 

539 

540 

541def available_detector_types(microscope: tbt.Microscope) -> List[str]: 

542 """ 

543 Retrieve the available detector types on the current microscope. 

544 

545 This function returns a list of available detector types on the current microscope. 

546 

547 ## Parameters 

548 

549 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the available detector types. 

550 

551 ## Returns 

552 

553 - `List[str]`: A list of available detector types. 

554 """ 

555 detectors = microscope.detector.type.available_values 

556 return detectors 

557 

558 

559def available_detector_modes(microscope: tbt.Microscope) -> List[str]: 

560 """ 

561 Retrieve the available detector modes on the current microscope. 

562 

563 This function returns a list of available detector modes on the current microscope. 

564 

565 ## Parameters 

566 

567 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the available detector modes. 

568 

569 ## Returns 

570 

571 - `List[str]`: A list of available detector modes. 

572 """ 

573 modes = microscope.detector.mode.available_values 

574 # available = [tbt.DetectorType(i) for i in modes] 

575 return modes 

576 

577 

578def beam_object_type(type: tbt.BeamType) -> tbt.Beam: 

579 """ 

580 Retrieve the beam object type based on the given beam type. 

581 

582 This function returns the appropriate beam object type (electron or ion) based on the provided beam type. 

583 

584 ## Parameters 

585 

586 - `type` (`tbt.BeamType`): The type of the beam (electron or ion). 

587 

588 ## Returns 

589 

590 - `tbt.Beam`: The corresponding beam object type. 

591 

592 ## Raises 

593 

594 - `NotImplementedError`: If the provided beam type is unsupported. 

595 """ 

596 if not isinstance(type, tbt.BeamType): 

597 raise ValueError("Electron and Ion are the only allowed beam object types.") 

598 if type.value == "electron": 

599 return tbt.ElectronBeam 

600 if type.value == "ion": 600 ↛ exitline 600 didn't return from function 'beam_object_type' because the condition on line 600 was always true

601 return tbt.IonBeam 

602 

603 

604def stage_limits(microscope: tbt.Microscope) -> tbt.StageLimits: 

605 """ 

606 Retrieve the stage limits from the current microscope connection. 

607 

608 This function retrieves the stage limits for the X, Y, Z, R, and T axes from the current microscope connection and returns them as a `StageLimits` object. 

609 

610 ## Parameters 

611 

612 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the stage limits. 

613 

614 ## Returns 

615 

616 - `tbt.StageLimits`: The stage limits for the X, Y, Z, R, and T axes in user units (mm and degrees). 

617 """ 

618 stage.coordinate_system(microscope=microscope) 

619 # x position 

620 min_x_mm = ( 

621 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).min 

622 * Conversions.M_TO_MM 

623 ) 

624 max_x_mm = ( 

625 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.X).max 

626 * Conversions.M_TO_MM 

627 ) 

628 # y position 

629 min_y_mm = ( 

630 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).min 

631 * Conversions.M_TO_MM 

632 ) 

633 max_y_mm = ( 

634 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Y).max 

635 * Conversions.M_TO_MM 

636 ) 

637 # z position 

638 min_z_mm = ( 

639 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).min 

640 * Conversions.M_TO_MM 

641 ) 

642 max_z_mm = ( 

643 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.Z).max 

644 * Conversions.M_TO_MM 

645 ) 

646 # r position 

647 min_r_deg = Constants.rotation_axis_limit_deg.min 

648 max_r_deg = Constants.rotation_axis_limit_deg.max 

649 # t position 

650 min_t_deg = ( 

651 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).min 

652 * Conversions.RAD_TO_DEG 

653 ) 

654 max_t_deg = ( 

655 microscope.specimen.stage.get_axis_limits(tbt.StageAxis.T).max 

656 * Conversions.RAD_TO_DEG 

657 ) 

658 

659 return tbt.StageLimits( 

660 x_mm=tbt.Limit(min=min_x_mm, max=max_x_mm), 

661 y_mm=tbt.Limit(min=min_y_mm, max=max_y_mm), 

662 z_mm=tbt.Limit(min=min_z_mm, max=max_z_mm), 

663 r_deg=tbt.Limit(min=min_r_deg, max=max_r_deg), 

664 t_deg=tbt.Limit(min=min_t_deg, max=max_t_deg), 

665 ) 

666 

667 

668def beam_limits( 

669 selected_beam: property, 

670 beam_type: tbt.BeamType, 

671) -> tbt.BeamLimits: 

672 """ 

673 Retrieve the beam limits for the selected beam and beam type. 

674 

675 This function retrieves the limits for voltage, current, horizontal field width (HFW), and working distance for the selected beam and beam type, and returns them as a `BeamLimits` object. 

676 

677 ## Parameters 

678 

679 - `selected_beam` (`property`): The selected beam property from which to retrieve the limits. 

680 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion). 

681 

682 ## Returns 

683 

684 - `tbt.BeamLimits`: The beam limits for voltage, current, HFW, and working distance in user units (kV, nA, mm). 

685 

686 ## Raises 

687 

688 - `ValueError`: If the beam type is unsupported. 

689 """ 

690 # voltage range 

691 min_kv = selected_beam.high_voltage.limits.min * Conversions.V_TO_KV 

692 max_kv = selected_beam.high_voltage.limits.max * Conversions.V_TO_KV 

693 

694 # current range 

695 if beam_type == tbt.BeamType.ELECTRON: 

696 min_na = selected_beam.beam_current.limits.min * Conversions.A_TO_NA 

697 max_na = selected_beam.beam_current.limits.max * Conversions.A_TO_NA 

698 if beam_type == tbt.BeamType.ION: 

699 available_currents = selected_beam.beam_current.available_values 

700 min_na = min(available_currents) * Conversions.A_TO_NA 

701 max_na = max(available_currents) * Conversions.A_TO_NA 

702 

703 # hfw range 

704 min_hfw_mm = selected_beam.horizontal_field_width.limits.min * Conversions.M_TO_MM 

705 max_hfw_mm = selected_beam.horizontal_field_width.limits.max * Conversions.M_TO_MM 

706 

707 # working_dist range 

708 min_wd_mm = selected_beam.working_distance.limits.min * Conversions.M_TO_MM 

709 max_wd_mm = selected_beam.working_distance.limits.max * Conversions.M_TO_MM 

710 

711 return tbt.BeamLimits( 

712 voltage_kv=tbt.Limit(min=min_kv, max=max_kv), 

713 current_na=tbt.Limit(min=min_na, max=max_na), 

714 hfw_mm=tbt.Limit(min=min_hfw_mm, max=max_hfw_mm), 

715 working_distance_mm=tbt.Limit(min=min_wd_mm, max=max_wd_mm), 

716 ) 

717 

718 

719def general( 

720 general_db: dict, 

721 yml_format: tbt.YMLFormatVersion, 

722) -> tbt.GeneralSettings: 

723 """ 

724 Convert a general settings dictionary to a built-in type and perform schema checking. 

725 

726 This function converts a general settings dictionary from a .yml file to a `GeneralSettings` object. It performs schema checking to ensure valid inputs are requested. 

727 

728 ## Parameters 

729 

730 - `general_db` (`dict`): The general settings dictionary from the .yml file. 

731 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

732 

733 ## Returns 

734 

735 - `tbt.GeneralSettings`: The general settings object. 

736 

737 ## Raises 

738 

739 - `NotImplementedError`: If the provided yml format is unsupported. 

740 """ 

741 

742 if not yml_format in tbt.YMLFormatVersion: 

743 raise NotImplementedError( 

744 """Due to the complexity and number of variables, 

745 image objects should only be constructed using a yml file.""" 

746 ) 

747 if not isinstance(yml_format, tbt.YMLFormatVersion): 

748 raise NotImplementedError(f"Unsupported yml format of {yml_format}.") 

749 

750 validate_general_settings( 

751 settings=general_db, 

752 yml_format=yml_format, 

753 ) 

754 

755 if yml_format.version >= 1.0: 755 ↛ 789line 755 didn't jump to line 789 because the condition on line 755 was always true

756 # slice thickness 

757 slice_thickness_um = general_db["slice_thickness_um"] 

758 # max slice number 

759 max_slice_number = general_db["max_slice_num"] 

760 # pre tilt 

761 pre_tilt_deg = general_db["pre_tilt_deg"] 

762 # sectioning axis 

763 sectioning_axis = tbt.SectioningAxis(general_db["sectioning_axis"]) 

764 # stage tolerance 

765 stage_tolerance = tbt.StageTolerance( 

766 translational_um=general_db["stage_translational_tol_um"], 

767 angular_deg=general_db["stage_angular_tol_deg"], 

768 ) 

769 # connection 

770 connection = tbt.MicroscopeConnection( 

771 general_db["connection_host"], general_db["connection_port"] 

772 ) 

773 # EBSD OEM 

774 ebsd_oem = tbt.ExternalDeviceOEM(general_db["EBSD_OEM"]) 

775 # EDS OEM 

776 eds_oem = tbt.ExternalDeviceOEM(general_db["EDS_OEM"]) 

777 # exp dir 

778 exp_dir = general_db["exp_dir"] 

779 # h5 log name 

780 h5_log_name = general_db["h5_log_name"] 

781 # remove log file extension if the user provided it 

782 log_extension = Constants.logfile_extension 

783 if h5_log_name.endswith(log_extension): 783 ↛ 784line 783 didn't jump to line 784 because the condition on line 783 was never true

784 h5_log_name = h5_log_name[: -len(log_extension)] 

785 # step count 

786 step_count = general_db["step_count"] 

787 yml_version = 1.0 

788 

789 general_settings = tbt.GeneralSettings( 

790 yml_version=yml_version, 

791 slice_thickness_um=slice_thickness_um, 

792 max_slice_number=max_slice_number, 

793 pre_tilt_deg=pre_tilt_deg, 

794 sectioning_axis=sectioning_axis, 

795 stage_tolerance=stage_tolerance, 

796 connection=connection, 

797 EBSD_OEM=ebsd_oem, 

798 EDS_OEM=eds_oem, 

799 exp_dir=Path(exp_dir), 

800 h5_log_name=h5_log_name, 

801 step_count=step_count, 

802 ) 

803 

804 return general_settings 

805 

806 

807def laser_box_pattern(settings: dict) -> tbt.LaserBoxPattern: 

808 """ 

809 Convert a dictionary of laser box pattern settings to a `LaserBoxPattern` object. 

810 

811 This function takes a dictionary of laser box pattern settings and converts it to a `LaserBoxPattern` object. 

812 

813 ## Parameters 

814 

815 - `settings` (`dict`): The dictionary containing laser box pattern settings. 

816 

817 ## Returns 

818 

819 - `tbt.LaserBoxPattern`: The laser box pattern object. 

820 """ 

821 return tbt.LaserBoxPattern( 

822 passes=settings["passes"], 

823 size_x_um=settings["size_x_um"], 

824 size_y_um=settings["size_y_um"], 

825 pitch_x_um=settings["pitch_x_um"], 

826 pitch_y_um=settings["pitch_y_um"], 

827 scan_type=tbt.LaserScanType(settings["scan_type"]), 

828 coordinate_ref=tbt.CoordinateReference(settings["coordinate_ref"]), 

829 ) 

830 

831 

832def laser_line_pattern(settings: dict) -> tbt.LaserBoxPattern: 

833 """ 

834 Convert a dictionary of laser line pattern settings to a `LaserLinePattern` object. 

835 

836 This function takes a dictionary of laser line pattern settings and converts it to a `LaserLinePattern` object. 

837 

838 ## Parameters 

839 

840 - `settings` (`dict`): The dictionary containing laser line pattern settings. 

841 

842 ## Returns 

843 

844 - `tbt.LaserLinePattern`: The laser line pattern object. 

845 """ 

846 return tbt.LaserLinePattern( 

847 passes=settings["passes"], 

848 size_um=settings["size_um"], 

849 pitch_um=settings["pitch_um"], 

850 scan_type=tbt.LaserScanType(settings["scan_type"]), 

851 ) 

852 

853 

854def laser( 

855 microscope: tbt.Microscope, 

856 step_settings: dict, 

857 step_name: str, 

858 yml_format: tbt.YMLFormatVersion, 

859) -> tbt.LaserSettings: 

860 """ 

861 Convert a laser step from a .yml file to microscope settings for performing laser milling. 

862 

863 This function converts a laser step from a .yml file to `LaserSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

864 

865 ## Parameters 

866 

867 - `microscope` (`tbt.Microscope`): The microscope object for which to set the laser settings. 

868 - `step_settings` (`dict`): The dictionary containing the laser step settings from the .yml file. 

869 - `step_name` (`str`): The name of the step in the .yml file. 

870 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

871 

872 ## Returns 

873 

874 - `tbt.LaserSettings`: The laser settings object. 

875 

876 ## Raises 

877 

878 - `KeyError`: If required settings are missing from the .yml file. 

879 """ 

880 if yml_format.version >= 1.0: 880 ↛ 902line 880 didn't jump to line 902 because the condition on line 880 was always true

881 # pulse settings 

882 pulse_set_db = step_settings.get("pulse") 

883 if pulse_set_db is None: 883 ↛ 884line 883 didn't jump to line 884 because the condition on line 883 was never true

884 raise KeyError( 

885 f"Invalid .yml file, no 'pulse' settings found in 'laser' step_type for step '{step_name}'." 

886 ) 

887 # laser optics settings 

888 optics_set_db = { 

889 "objective_position_mm": step_settings.get("objective_position_mm"), 

890 "beam_shift_um_x": step_settings.get("beam_shift").get("x_um"), 

891 "beam_shift_um_y": step_settings.get("beam_shift").get("y_um"), 

892 } 

893 # pattern settings 

894 pattern_set_db = step_settings.get("pattern") 

895 if pattern_set_db is None: 895 ↛ 896line 895 didn't jump to line 896 because the condition on line 895 was never true

896 raise KeyError( 

897 f"Invalid .yml file, no 'pattern' settings found in 'laser' step_type for step '{step_name}'." 

898 ) 

899 line_pattern_db = pattern_set_db.get("type").get("line") 

900 box_pattern_db = pattern_set_db.get("type").get("box") 

901 

902 validate_pulse_settings( 

903 settings=pulse_set_db, 

904 yml_format=yml_format, 

905 step_name=step_name, 

906 ) 

907 pulse = tbt.LaserPulse( 

908 wavelength_nm=tbt.LaserWavelength(pulse_set_db["wavelength_nm"]), 

909 divider=pulse_set_db["divider"], 

910 energy_uj=pulse_set_db["energy_uj"], 

911 polarization=tbt.LaserPolarization(pulse_set_db["polarization"]), 

912 ) 

913 

914 validate_laser_optics_settings( 

915 settings=optics_set_db, 

916 yml_format=yml_format, 

917 step_name=step_name, 

918 ) 

919 

920 pattern_type = validate_laser_pattern_settings( 

921 settings=pattern_set_db, 

922 yml_format=yml_format, 

923 step_name=step_name, 

924 ) 

925 if pattern_type == tbt.LaserPatternType.BOX: 

926 geometry = laser_box_pattern(box_pattern_db) 

927 if pattern_type == tbt.LaserPatternType.LINE: 

928 geometry = laser_line_pattern(line_pattern_db) 

929 pattern = tbt.LaserPattern( 

930 mode=tbt.LaserPatternMode(pattern_set_db["mode"]), 

931 rotation_deg=pattern_set_db["rotation_deg"], 

932 pulses_per_pixel=pattern_set_db["pulses_per_pixel"], 

933 pixel_dwell_ms=pattern_set_db["pixel_dwell_ms"], 

934 geometry=geometry, 

935 ) 

936 

937 laser_settings = tbt.LaserSettings( 

938 microscope=microscope, 

939 pulse=pulse, 

940 objective_position_mm=optics_set_db["objective_position_mm"], 

941 beam_shift_um=tbt.Point( 

942 x=optics_set_db["beam_shift_um_x"], 

943 y=optics_set_db["beam_shift_um_y"], 

944 ), 

945 pattern=pattern, 

946 ) 

947 

948 return laser_settings 

949 

950 

951def image( 

952 microscope: tbt.Microscope, 

953 step_settings: dict, 

954 step_name: str, 

955 yml_format: tbt.YMLFormatVersion, 

956) -> tbt.ImageSettings: 

957 """ 

958 Convert an image step from a .yml file to microscope settings for capturing an image. 

959 

960 This function converts an image step from a .yml file to `ImageSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

961 

962 ## Parameters 

963 

964 - `microscope` (`tbt.Microscope`): The microscope object for which to set the image settings. 

965 - `step_settings` (`dict`): The dictionary containing the image step settings from the .yml file. 

966 - `step_name` (`str`): The name of the step in the .yml file. 

967 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

968 

969 ## Returns 

970 

971 - `tbt.ImageSettings`: The image settings object. 

972 

973 ## Raises 

974 

975 - `KeyError`: If required settings are missing from the .yml file. 

976 - `NotImplementedError`: If the provided beam type is unsupported. 

977 - `ValueError`: If invalid scan rotation is requested with dynamic focus or tilt correction, or if the bit depth is unsupported. 

978 """ 

979 if yml_format.version >= 1.0: 979 ↛ 1023line 979 didn't jump to line 1023 because the condition on line 979 was always true

980 step_general = step_settings.get(yml_format.step_general_key) 

981 if step_general is None: 981 ↛ 982line 981 didn't jump to line 982 because the condition on line 981 was never true

982 raise KeyError( 

983 f"Invalid .yml file, no 'step_general' settings found in step '{step_name}'." 

984 ) 

985 step_type = step_general.get(yml_format.step_type_key) 

986 if step_type is None: 986 ↛ 987line 986 didn't jump to line 987 because the condition on line 986 was never true

987 raise KeyError( 

988 f"Invalid .yml file, no 'step_type' settings found in step '{step_name}'." 

989 ) 

990 # beam settings 

991 beam_set_db = step_settings.get("beam") 

992 if beam_set_db is None: 992 ↛ 993line 992 didn't jump to line 993 because the condition on line 992 was never true

993 raise KeyError( 

994 f"Invalid .yml file, no 'beam' settings found in '{step_type}' step_type for step '{step_name}'." 

995 ) 

996 beam_type_value = beam_set_db.get("type") 

997 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType): 

998 raise NotImplementedError( 

999 f"Unsupported beam type of '{beam_type_value}', supported beam types are: {[i.value for i in tbt.BeamType]}." 

1000 ) 

1001 beam_type = tbt.BeamType(beam_set_db.get("type")) 

1002 

1003 # detector settings 

1004 detector_set_db = step_settings.get("detector") 

1005 if detector_set_db is None: 1005 ↛ 1006line 1005 didn't jump to line 1006 because the condition on line 1005 was never true

1006 raise KeyError( 

1007 f"Invalid .yml file, no 'detector' settings found in '{step_type}' step_type for step '{step_name}'." 

1008 ) 

1009 auto_cb_set_db = detector_set_db.get("auto_cb") 

1010 

1011 # scan settings 

1012 scan_set_db = step_settings.get("scan") 

1013 if scan_set_db is None: 1013 ↛ 1014line 1013 didn't jump to line 1014 because the condition on line 1013 was never true

1014 raise KeyError( 

1015 f"Invalid .yml file, no 'scan' settings found in '{step_type}' step_type for step '{step_name}'." 

1016 ) 

1017 scan_res = string_to_res(scan_set_db.get("resolution")) 

1018 

1019 # misc settings 

1020 bit_depth = step_settings.get("bit_depth") 

1021 

1022 # TODO incorporate tile settings 

1023 if yml_format.version >= 1.1: 1023 ↛ 1025line 1023 didn't jump to line 1025 because the condition on line 1023 was never true

1024 # tile settings 

1025 tile_set_db = step_settings.get("tile_settings") 

1026 

1027 validate_beam_settings( 

1028 microscope=microscope, 

1029 beam_type=beam_type, 

1030 settings=beam_set_db, 

1031 yml_format=yml_format, 

1032 step_name=step_name, 

1033 ) 

1034 beam_settings = tbt.BeamSettings( 

1035 voltage_kv=beam_set_db["voltage_kv"], 

1036 voltage_tol_kv=beam_set_db["voltage_tol_kv"], 

1037 current_na=beam_set_db["current_na"], 

1038 current_tol_na=beam_set_db["current_tol_na"], 

1039 hfw_mm=beam_set_db["hfw_mm"], 

1040 working_dist_mm=beam_set_db["working_dist_mm"], 

1041 dynamic_focus=beam_set_db["dynamic_focus"], 

1042 tilt_correction=beam_set_db["tilt_correction"], 

1043 ) 

1044 

1045 validate_auto_cb_settings( 

1046 settings=auto_cb_set_db, 

1047 yml_format=yml_format, 

1048 step_name=step_name, 

1049 ) 

1050 auto_cb_settings = tbt.ScanArea( 

1051 left=auto_cb_set_db["left"], 

1052 top=auto_cb_set_db["top"], 

1053 width=auto_cb_set_db["width"], 

1054 height=auto_cb_set_db["height"], 

1055 ) 

1056 

1057 validate_detector_settings( 

1058 microscope=microscope, 

1059 beam_type=beam_type, 

1060 settings=detector_set_db, 

1061 yml_format=yml_format, 

1062 step_name=step_name, 

1063 ) 

1064 detector_settings = tbt.Detector( 

1065 type=tbt.DetectorType(detector_set_db["type"]), 

1066 mode=tbt.DetectorMode(detector_set_db["mode"]), 

1067 brightness=detector_set_db["brightness"], 

1068 contrast=detector_set_db["contrast"], 

1069 auto_cb_settings=auto_cb_settings, 

1070 # custom_settings=None, 

1071 ) 

1072 

1073 validate_scan_settings( 

1074 microscope=microscope, 

1075 beam_type=beam_type, 

1076 settings=scan_set_db, 

1077 yml_format=yml_format, 

1078 step_name=step_name, 

1079 ) 

1080 

1081 # cast resolution to preset if applicable 

1082 if ut.valid_enum_entry(obj=scan_res, check_type=tbt.PresetResolution): 

1083 scan_res = tbt.PresetResolution(scan_res) 

1084 

1085 scan_settings = tbt.Scan( 

1086 rotation_deg=scan_set_db["rotation_deg"], 

1087 dwell_time_us=scan_set_db["dwell_time_us"], 

1088 resolution=scan_res, 

1089 # mode=tbt.ScanMode(scan_set_db["mode"]), 

1090 ) 

1091 

1092 # make sure Scan rotation is 0 if dynamic focus or tilt correction is on (using auto mode only for angular correction) 

1093 if beam_settings.dynamic_focus or beam_settings.tilt_correction: 

1094 if not math.isclose(a=scan_settings.rotation_deg, b=0.0): 

1095 raise ValueError( 

1096 f"Invalid .yml for step '{step_name}'. Scan rotation of '{scan_settings.rotation_deg}' degrees requested with tilt_correction and/or dynamic focus set to 'True'. Cannot use dynamic focus or tilt correction for non-zero scan rotation." 

1097 ) 

1098 

1099 # validate bit_depth 

1100 if not ut.valid_enum_entry(bit_depth, tbt.ColorDepth): 

1101 valid_bit_depths = [i.value for i in tbt.ColorDepth] 1101 ↛ exitline 1101 didn't run the list comprehension on line 1101

1102 raise ValueError( 

1103 f"Unsupported bit depth of {bit_depth}, available depths are {valid_bit_depths}" 

1104 ) 

1105 

1106 image_settings = tbt.ImageSettings( 

1107 microscope=microscope, 

1108 beam=beam_object_type(beam_type)(settings=beam_settings), 

1109 detector=detector_settings, 

1110 scan=scan_settings, 

1111 bit_depth=bit_depth, 

1112 ) 

1113 

1114 return image_settings 

1115 

1116 

1117def fib( 

1118 microscope: tbt.Microscope, 

1119 step_settings: dict, 

1120 step_name: str, 

1121 yml_format: tbt.YMLFormatVersion, 

1122) -> tbt.FIBSettings: 

1123 """ 

1124 Convert a FIB step from a .yml file to microscope settings for performing a FIB operation. 

1125 

1126 This function converts a FIB step from a .yml file to `FIBSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

1127 

1128 ## Parameters 

1129 

1130 - `microscope` (`tbt.Microscope`): The microscope object for which to set the FIB settings. 

1131 - `step_settings` (`dict`): The dictionary containing the FIB step settings from the .yml file. 

1132 - `step_name` (`str`): The name of the step in the .yml file. 

1133 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1134 

1135 ## Returns 

1136 

1137 - `tbt.FIBSettings`: The FIB settings object. 

1138 

1139 ## Raises 

1140 

1141 - `KeyError`: If required settings are missing from the .yml file. 

1142 - `ValueError`: If invalid beam type is requested. 

1143 """ 

1144 ## create image_step_settings from this 

1145 if yml_format.version >= 1.0: 1145 ↛ 1154line 1145 didn't jump to line 1154 because the condition on line 1145 was always true

1146 image_step_settings = step_settings.get("image") 

1147 image_step_settings["step_general"] = step_settings.get("step_general") 

1148 

1149 mill_step_settings = step_settings.get("mill") 

1150 mill_beam_db = mill_step_settings.get("beam") 

1151 mill_pattern_db = mill_step_settings.get("pattern") 

1152 

1153 # ensure image is with an ion beam 

1154 enforce_beam_type( 

1155 tbt.IonBeam(settings=None), 

1156 step_settings=image_step_settings, 

1157 step_name=step_name, 

1158 yml_format=yml_format, 

1159 ) 

1160 image_settings = image( 

1161 microscope=microscope, 

1162 step_settings=image_step_settings, 

1163 step_name=step_name, 

1164 yml_format=yml_format, 

1165 ) 

1166 

1167 ## fib mill settings 

1168 # ensure milling is with an ion beam 

1169 enforce_beam_type( 

1170 tbt.IonBeam(settings=None), 

1171 step_settings=mill_step_settings, 

1172 step_name=step_name, 

1173 yml_format=yml_format, 

1174 ) 

1175 beam_type = tbt.BeamType(mill_beam_db.get("type")) 

1176 # mill beam 

1177 validate_beam_settings( 

1178 microscope=microscope, 

1179 beam_type=beam_type, 

1180 settings=mill_beam_db, 

1181 yml_format=yml_format, 

1182 step_name=step_name, 

1183 ) 

1184 mill_beam = tbt.IonBeam( 

1185 settings=tbt.BeamSettings( 

1186 voltage_kv=mill_beam_db["voltage_kv"], 

1187 voltage_tol_kv=mill_beam_db["voltage_tol_kv"], 

1188 current_na=mill_beam_db["current_na"], 

1189 current_tol_na=mill_beam_db["current_tol_na"], 

1190 hfw_mm=mill_beam_db["hfw_mm"], 

1191 working_dist_mm=mill_beam_db["working_dist_mm"], 

1192 dynamic_focus=mill_beam_db["dynamic_focus"], 

1193 tilt_correction=mill_beam_db["tilt_correction"], 

1194 ) 

1195 ) 

1196 

1197 # fib pattern settings 

1198 pattern = validate_fib_pattern_settings( 

1199 microscope=microscope, 

1200 settings=mill_pattern_db, 

1201 yml_format=yml_format, 

1202 step_name=step_name, 

1203 ) 

1204 

1205 fib_settings = tbt.FIBSettings( 

1206 microscope=microscope, 

1207 image=image_settings, 

1208 mill_beam=mill_beam, 

1209 pattern=pattern, 

1210 ) 

1211 return fib_settings 

1212 

1213 

1214@singledispatch 

1215def enforce_beam_type( 

1216 beam_type, 

1217 step_settings: dict, 

1218 step_name: str, 

1219 yml_format: tbt.YMLFormatVersion, 

1220) -> bool: 

1221 """ 

1222 Enforce a specific beam type is used for an operation based on a dictionary. 

1223 

1224 This function ensures that the specified beam type is used for an operation based on the provided settings dictionary. The dictionary must contain a sub-dictionary with the key 'beam'. 

1225 

1226 ## Parameters 

1227 

1228 - `beam_type` (`Any`): The beam type to enforce. 

1229 - `step_settings` (`dict`): The dictionary containing the step settings. 

1230 - `step_name` (`str`): The name of the step in the .yml file. 

1231 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1232 

1233 ## Returns 

1234 

1235 - `bool`: True if the beam type is enforced successfully. 

1236 

1237 ## Raises 

1238 

1239 - `NotImplementedError`: If no handler is available for the provided type. 

1240 """ 

1241 _ = beam_type 

1242 __ = step_settings 

1243 ___ = step_name 

1244 ____ = yml_format 

1245 raise NotImplementedError(f"No handler for type {type(step_settings)}") 

1246 

1247 

1248@enforce_beam_type.register 

1249def _( 

1250 beam_type: tbt.ElectronBeam, 

1251 step_settings: dict, 

1252 step_name: str, 

1253 yml_format: tbt.YMLFormatVersion, 

1254) -> bool: 

1255 """ 

1256 Enforce that an electron beam is used for an operation based on a dictionary. 

1257 

1258 This function ensures that an electron beam is used for an operation based on the provided settings dictionary. 

1259 

1260 ## Parameters 

1261 

1262 - `beam_type` (`tbt.ElectronBeam`): The electron beam type to enforce. 

1263 - `step_settings` (`dict`): The dictionary containing the step settings. 

1264 - `step_name` (`str`): The name of the step in the .yml file. 

1265 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1266 

1267 ## Returns 

1268 

1269 - `bool`: True if the electron beam type is enforced successfully. 

1270 

1271 ## Raises 

1272 

1273 - `KeyError`: If the 'beam' settings are missing from the .yml file. 

1274 - `NotImplementedError`: If the beam type is unsupported or not an electron beam. 

1275 """ 

1276 # beam must be electron 

1277 if yml_format.version >= 1.0: 1277 ↛ exitline 1277 didn't return from function '_' because the condition on line 1277 was always true

1278 beam_set_db = step_settings.get("beam") 

1279 if beam_set_db is None: 1279 ↛ 1280line 1279 didn't jump to line 1280 because the condition on line 1279 was never true

1280 raise KeyError( 

1281 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'." 

1282 ) 

1283 beam_type_value = beam_set_db.get("type") 

1284 

1285 electron_beam_error_message = f"Unsupported beam type of '{beam_type_value}' in step '{step_name}'. '{tbt.BeamType.ELECTRON.value}' beam type must be used." 

1286 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType): 

1287 raise NotImplementedError(electron_beam_error_message) 

1288 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ELECTRON: 

1289 raise NotImplementedError(electron_beam_error_message) 

1290 

1291 

1292@enforce_beam_type.register 

1293def _( 

1294 beam_type: tbt.IonBeam, 

1295 step_settings: dict, 

1296 step_name: str, 

1297 yml_format: tbt.YMLFormatVersion, 

1298) -> bool: 

1299 """ 

1300 Enforce that an ion beam is used for an operation based on a dictionary. 

1301 

1302 This function ensures that an ion beam is used for an operation based on the provided settings dictionary. 

1303 

1304 ## Parameters 

1305 

1306 - `beam_type` (`tbt.IonBeam`): The ion beam type to enforce. 

1307 - `step_settings` (`dict`): The dictionary containing the step settings. 

1308 - `step_name` (`str`): The name of the step in the .yml file. 

1309 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1310 

1311 ## Returns 

1312 

1313 - `bool`: True if the ion beam type is enforced successfully. 

1314 

1315 ## Raises 

1316 

1317 - `KeyError`: If the 'beam' settings are missing from the .yml file. 

1318 - `NotImplementedError`: If the beam type is unsupported or not an ion beam. 

1319 """ 

1320 # beam must be ion 

1321 if yml_format.version >= 1.0: 1321 ↛ exitline 1321 didn't return from function '_' because the condition on line 1321 was always true

1322 beam_set_db = step_settings.get("beam") 

1323 if beam_set_db is None: 1323 ↛ 1324line 1323 didn't jump to line 1324 because the condition on line 1323 was never true

1324 raise KeyError( 

1325 f"Invalid .yml file, no 'beam' settings found in step '{step_name}'." 

1326 ) 

1327 beam_type_value = beam_set_db.get("type") 

1328 

1329 ion_beam_error_message = f"Unsupported beam type of '{beam_type_value}' for step '{step_name}'. '{tbt.BeamType.ION.value}' beam type must be used." 

1330 if not ut.valid_enum_entry(beam_type_value, tbt.BeamType): 

1331 raise NotImplementedError(ion_beam_error_message) 

1332 if not tbt.BeamType(beam_type_value) == tbt.BeamType.ION: 

1333 raise NotImplementedError(ion_beam_error_message) 

1334 

1335 

1336def ebsd( 

1337 microscope: tbt.Microscope, 

1338 step_settings: dict, 

1339 step_name: str, 

1340 yml_format: tbt.YMLFormatVersion, 

1341) -> tbt.EBSDSettings: 

1342 """ 

1343 Convert an EBSD step from a .yml file to microscope settings for performing an EBSD operation. 

1344 

1345 This function converts an EBSD step from a .yml file to `EBSDSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

1346 

1347 ## Parameters 

1348 

1349 - `microscope` (`tbt.Microscope`): The microscope object for which to set the EBSD settings. 

1350 - `step_settings` (`dict`): The dictionary containing the EBSD step settings from the .yml file. 

1351 - `step_name` (`str`): The name of the step in the .yml file. 

1352 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1353 

1354 ## Returns 

1355 

1356 - `tbt.EBSDSettings`: The EBSD settings object. 

1357 

1358 ## Raises 

1359 

1360 - `KeyError`: If required settings are missing from the .yml file or if the 'concurrent_EDS' key is invalid. 

1361 """ 

1362 enforce_beam_type( 

1363 tbt.ElectronBeam(settings=None), 

1364 step_settings=step_settings, 

1365 step_name=step_name, 

1366 yml_format=yml_format, 

1367 ) 

1368 image_settings = image( 

1369 microscope=microscope, 

1370 step_settings=step_settings, 

1371 step_name=step_name, 

1372 yml_format=yml_format, 

1373 ) 

1374 concurrent_EDS = step_settings.get("concurrent_EDS") 

1375 if (concurrent_EDS is None) or (not concurrent_EDS): 

1376 enable_eds = False 

1377 elif concurrent_EDS == True: 1377 ↛ 1380line 1377 didn't jump to line 1380 because the condition on line 1377 was always true

1378 enable_eds = True 

1379 else: 

1380 raise KeyError( 

1381 f"Invalid .yml file, for step '{step_name}', an EBSD type step. 'concurrent_EDS' key is '{concurrent_EDS}' of type {type(concurrent_EDS)} but must be boolean (True/False) or of NoneType (null in .yml)." 

1382 ) 

1383 

1384 ebsd_settings = tbt.EBSDSettings( 

1385 image=image_settings, 

1386 enable_eds=enable_eds, 

1387 enable_ebsd=True, 

1388 ) 

1389 return ebsd_settings 

1390 

1391 

1392def eds( 

1393 microscope: tbt.Microscope, 

1394 step_settings: dict, 

1395 step_name: str, 

1396 yml_format: tbt.YMLFormatVersion, 

1397) -> tbt.EDSSettings: 

1398 """ 

1399 Convert an EDS step from a .yml file to microscope settings for performing an EDS operation. 

1400 

1401 This function converts an EDS step from a .yml file to `EDSSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

1402 

1403 ## Parameters 

1404 

1405 - `microscope` (`tbt.Microscope`): The microscope object for which to set the EDS settings. 

1406 - `step_settings` (`dict`): The dictionary containing the EDS step settings from the .yml file. 

1407 - `step_name` (`str`): The name of the step in the .yml file. 

1408 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1409 

1410 ## Returns 

1411 

1412 - `tbt.EDSSettings`: The EDS settings object. 

1413 """ 

1414 enforce_beam_type( 

1415 tbt.ElectronBeam(settings=None), 

1416 step_settings=step_settings, 

1417 step_name=step_name, 

1418 yml_format=yml_format, 

1419 ) 

1420 image_settings = image( 

1421 microscope=microscope, 

1422 step_settings=step_settings, 

1423 step_name=step_name, 

1424 yml_format=yml_format, 

1425 ) 

1426 eds_settings = tbt.EDSSettings( 

1427 image=image_settings, 

1428 enable_eds=True, 

1429 ) 

1430 return eds_settings 

1431 

1432 

1433def custom( 

1434 microscope: tbt.Microscope, 

1435 step_settings: dict, 

1436 step_name: str, 

1437 yml_format: tbt.YMLFormatVersion, 

1438) -> tbt.CustomSettings: 

1439 """ 

1440 Convert a custom step from a .yml file to custom settings for the microscope. 

1441 

1442 This function converts a custom step from a .yml file to `CustomSettings` for the microscope. It performs schema checking to ensure valid inputs are requested. 

1443 

1444 ## Parameters 

1445 

1446 - `microscope` (`tbt.Microscope`): The microscope object for which to set the custom settings. 

1447 - `step_settings` (`dict`): The dictionary containing the custom step settings from the .yml file. 

1448 - `step_name` (`str`): The name of the step in the .yml file. 

1449 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1450 

1451 ## Returns 

1452 

1453 - `tbt.CustomSettings`: The custom settings object. 

1454 

1455 ## Raises 

1456 

1457 - `KeyError`: If required settings are missing from the .yml file. 

1458 - `ValueError`: If the specified script or executable path does not exist. 

1459 """ 

1460 if yml_format.version >= 1.0: 1460 ↛ 1481line 1460 didn't jump to line 1481 because the condition on line 1460 was always true

1461 script_path = step_settings.get("script_path") 

1462 if script_path is None: 1462 ↛ 1463line 1462 didn't jump to line 1463 because the condition on line 1462 was never true

1463 raise KeyError( 

1464 f"Invalid .yml file, no 'script_path' found in custom step_type for step '{step_name}'." 

1465 ) 

1466 if not Path(script_path).is_file(): 1466 ↛ 1467line 1466 didn't jump to line 1467 because the condition on line 1466 was never true

1467 raise ValueError( 

1468 f"Invalid location for script at location {script_path}. File does not exist." 

1469 ) 

1470 

1471 executable_path = step_settings.get("executable_path") 

1472 if executable_path is None: 1472 ↛ 1473line 1472 didn't jump to line 1473 because the condition on line 1472 was never true

1473 raise KeyError( 

1474 f"Invalid .yml file, no 'executable_path' found in custom step_type for step '{step_name}'." 

1475 ) 

1476 if not Path(executable_path).is_file(): 

1477 raise ValueError( 

1478 f"Invalid location for executable at location {executable_path}. File does not exist." 

1479 ) 

1480 

1481 custom_settings = tbt.CustomSettings( 

1482 script_path=Path(script_path), 

1483 executable_path=Path(executable_path), 

1484 ) 

1485 return custom_settings 

1486 

1487 

1488# def fib_pattern_type( 

1489# settings: tbt.FIBSettings, 

1490# ) -> Union[tbt.FIBBoxPattern, tbt.FIBStreamPattern]: 

1491# """Returns specific pattern type settings for a properly formatted FIBSettings object""" 

1492 

1493 

1494def scan_limits( 

1495 selected_beam: property, 

1496) -> tbt.ScanLimits: 

1497 """ 

1498 Retrieve the scan settings limits for the selected beam. 

1499 

1500 This function retrieves the limits for rotation and dwell time for the selected beam and returns them as a `ScanLimits` object. 

1501 

1502 ## Parameters 

1503 

1504 - `selected_beam` (`property`): The selected beam property from which to retrieve the scan limits. 

1505 

1506 ## Returns 

1507 

1508 - `tbt.ScanLimits`: The scan limits for rotation (degrees) and dwell time (microseconds). 

1509 """ 

1510 # rotation 

1511 min_deg = selected_beam.scanning.rotation.limits.min * Conversions.RAD_TO_DEG 

1512 max_deg = selected_beam.scanning.rotation.limits.max * Conversions.RAD_TO_DEG 

1513 # dwell_time 

1514 min_dwell_us = selected_beam.scanning.dwell_time.limits.min * Conversions.S_TO_US 

1515 max_dwell_us = selected_beam.scanning.dwell_time.limits.max * Conversions.S_TO_US 

1516 

1517 return tbt.ScanLimits( 

1518 rotation_deg=tbt.Limit(min=min_deg, max=max_deg), 

1519 dwell_us=tbt.Limit(min=min_dwell_us, max=max_dwell_us), 

1520 ) 

1521 

1522 

1523def string_to_res(input: str) -> tbt.Resolution: 

1524 """ 

1525 Convert a string in the format "{{width}}x{{height}}" to a resolution object. 

1526 

1527 This function takes a string representing the resolution in the format "WIDTHxHEIGHT" and converts it to a `Resolution` object. 

1528 

1529 ## Parameters 

1530 

1531 - `input` (`str`): The string representing the resolution in the format "WIDTHxHEIGHT". 

1532 

1533 ## Returns 

1534 

1535 - `tbt.Resolution`: The resolution object. 

1536 

1537 ## Raises 

1538 

1539 - `ValueError`: If the input string is not in the expected format. 

1540 """ 

1541 try: 

1542 split_res = (input.lower()).split("x") 

1543 width, height = int(split_res[0]), int(split_res[1]) 

1544 except: 

1545 raise ValueError( 

1546 f"""Invalid string format, 

1547 expected string format of "WIDTHxHEIGHT", 

1548 but received the following: "{input}".""" 

1549 ) 

1550 

1551 return tbt.Resolution(width=width, height=height) 

1552 

1553 

1554def valid_string_resolution(string_resolution: str) -> bool: 

1555 """ 

1556 Validate a string resolution. 

1557 

1558 This function validates a string resolution by converting it to a `Resolution` object and checking if the width and height are within the specified limits. 

1559 

1560 ## Parameters 

1561 

1562 - `string_resolution` (`str`): The string representing the resolution in the format "WIDTHxHEIGHT". 

1563 

1564 ## Returns 

1565 

1566 - `bool`: True if the resolution is valid, False otherwise. 

1567 """ 

1568 res = string_to_res(string_resolution) 

1569 width, height = res.width, res.height 

1570 return ( 

1571 ut.in_interval( 

1572 width, 

1573 limit=Constants.scan_resolution_limit, 

1574 type=tbt.IntervalType.CLOSED, 

1575 ) 

1576 ) and ( 

1577 ut.in_interval( 

1578 height, 

1579 limit=Constants.scan_resolution_limit, 

1580 type=tbt.IntervalType.CLOSED, 

1581 ) 

1582 ) 

1583 

1584 

1585def validate_auto_cb_settings( 

1586 settings: dict, 

1587 yml_format: tbt.YMLFormatVersion, 

1588 step_name: str, 

1589) -> bool: 

1590 """ 

1591 Perform schema checking for auto contrast/brightness setting dictionary. 

1592 

1593 This function validates the auto contrast/brightness settings dictionary based on the specified yml format. 

1594 

1595 ## Parameters 

1596 

1597 - `settings` (`dict`): The dictionary containing the auto contrast/brightness settings. 

1598 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1599 - `step_name` (`str`): The name of the step in the .yml file. 

1600 

1601 ## Returns 

1602 

1603 - `bool`: True if the settings are valid, False otherwise. 

1604 

1605 ## Raises 

1606 

1607 - `KeyError`: If required keys are missing from the settings dictionary. 

1608 - `ValueError`: If the settings do not satisfy the specified schema. 

1609 """ 

1610 

1611 if ut.none_value_dictionary(settings): 1611 ↛ 1614line 1611 didn't jump to line 1614 because the condition on line 1611 was always true

1612 return True 

1613 

1614 if settings.get("left") is None or settings.get("top") is None: 1614 ↛ 1623line 1614 didn't jump to line 1623 because the condition on line 1614 was always true

1615 if settings.get("left") is None: 1615 ↛ 1616,   1615 ↛ 16182 missed branches: 1) line 1615 didn't jump to line 1616 because the condition on line 1615 was never true, 2) line 1615 didn't jump to line 1618 because the condition on line 1615 was always true

1616 missing_key = "left" 

1617 else: 

1618 missing_key = "top" 

1619 raise KeyError( 

1620 f"Missing or no value for '{missing_key}' key in 'auto_cb' sub-dictionary in step '{step_name}' All 'auto_cb' sub-dictionary values must be declared by the user to implement this capability." 

1621 ) 

1622 

1623 origin_limit = tbt.Limit(min=0.0, max=1.0) # reduced area limit for scan window 

1624 width_limit = tbt.Limit(min=0.0, max=1.0 - settings["left"]) 

1625 height_limit = tbt.Limit(min=0.0, max=1.0 - settings["top"]) 

1626 

1627 if yml_format.version >= 1.0: 

1628 schema = Schema( 1628 ↛ exit,   1628 ↛ 16702 missed branches: 1) line 1628 didn't jump to the function exit, 2) line 1628 didn't jump to line 1670 because

1629 { 

1630 "left": And( 

1631 float, 

1632 lambda x: ut.in_interval( 

1633 x, 

1634 limit=origin_limit, 

1635 type=tbt.IntervalType.RIGHT_OPEN, 

1636 ), 

1637 error=f"In step '{step_name}', requested auto contrast/brightness window setting 'left' of '{settings['left']}' must satisfy '0 <= left < {origin_limit.max}'. Origin is in top-left corner of the field of view.", 

1638 ), 

1639 "top": And( 

1640 float, 

1641 lambda x: ut.in_interval( 

1642 x, 

1643 limit=origin_limit, 

1644 type=tbt.IntervalType.RIGHT_OPEN, 

1645 ), 

1646 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'top' of '{settings['top']}' must satisfy '0.0 <= top < {origin_limit.max}'. Origin is in top-left corner of the field of view.", 

1647 ), 

1648 "width": And( 

1649 float, 

1650 lambda x: ut.in_interval( 

1651 x, 

1652 limit=width_limit, 

1653 type=tbt.IntervalType.LEFT_OPEN, 

1654 ), 

1655 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'width' of {settings['width']} must satisfy '0.0 < width <= {width_limit.max}' with 'left' setting of {settings['left']} as total width ('left' + 'width') cannot exceed 1.0", 

1656 ), 

1657 "height": And( 

1658 float, 

1659 lambda x: ut.in_interval( 

1660 x, 

1661 limit=height_limit, 

1662 type=tbt.IntervalType.LEFT_OPEN, 

1663 ), 

1664 error=f"In step '{step_name}', requested auto contrast/brightness windows setting 'height' of {settings['height']} must satisfy '0.0 < height <= {height_limit.max}' with 'top' setting of {settings['top']} as total height ('top' + 'height') cannot exceed 1.0", 

1665 ), 

1666 }, 

1667 ignore_extra_keys=True, 

1668 ) 

1669 

1670 try: 

1671 schema.validate(settings) 

1672 except UnboundLocalError: 

1673 raise ValueError( 

1674 f"Error. Unsupported yml version {yml_format.version} provided." 

1675 ) 

1676 return True 

1677 

1678 

1679def validate_stage_position( 

1680 microscope: tbt.Microscope, 

1681 step_name: str, 

1682 settings: dict, 

1683 yml_format: tbt.YMLFormatVersion, 

1684) -> bool: 

1685 """ 

1686 Perform schema checking for stage position dictionary. 

1687 

1688 This function validates the stage position settings dictionary based on the specified yml format and the stage limits of the microscope. 

1689 

1690 ## Parameters 

1691 

1692 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the stage position settings. 

1693 - `step_name` (`str`): The name of the step in the .yml file. 

1694 - `settings` (`dict`): The dictionary containing the stage position settings. 

1695 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1696 

1697 ## Returns 

1698 

1699 - `bool`: True if the settings are valid, False otherwise. 

1700 

1701 ## Raises 

1702 

1703 - `ValueError`: If the yml version is unsupported. 

1704 """ 

1705 limits = stage_limits(microscope=microscope) 

1706 

1707 if yml_format.version >= 1.0: 1707 ↛ 1759line 1707 didn't jump to line 1759 because the condition on line 1707 was always true

1708 schema = Schema( 

1709 { 

1710 "x_mm": And( 

1711 float, 

1712 lambda x: ut.in_interval( 

1713 x, 

1714 limit=limits.x_mm, 

1715 type=tbt.IntervalType.CLOSED, 

1716 ), 

1717 error=f"Requested x-axis position of {settings['x_mm']} mm for step '{step_name}' must satisfy '{limits.x_mm.min} <= x_mm <= {limits.x_mm.max}'", 

1718 ), 

1719 "y_mm": And( 

1720 float, 

1721 lambda x: ut.in_interval( 

1722 x, 

1723 limit=limits.y_mm, 

1724 type=tbt.IntervalType.CLOSED, 

1725 ), 

1726 error=f"Requested y-axis position of {settings['y_mm']} mm for step '{step_name}' must satisfy '{limits.y_mm.min} <= y_mm <= {limits.y_mm.max}'", 

1727 ), 

1728 "z_mm": And( 

1729 float, 

1730 lambda x: ut.in_interval( 

1731 x, 

1732 limit=limits.z_mm, 

1733 type=tbt.IntervalType.CLOSED, 

1734 ), 

1735 error=f"Requested z-axis position of {settings['z_mm']} mm for step '{step_name}' must satisfy '{limits.z_mm.min} <= z_mm <= {limits.z_mm.max}'", 

1736 ), 

1737 "r_deg": And( 

1738 float, 

1739 lambda x: ut.in_interval( 

1740 x, 

1741 limit=limits.r_deg, 

1742 type=tbt.IntervalType.RIGHT_OPEN, 

1743 ), 

1744 error=f"Requested r-axis position of {settings['r_deg']} degree for step '{step_name}' must satisfy '{limits.r_deg.min} <= r_deg < {limits.r_deg.max}'", 

1745 ), 

1746 "t_deg": And( 

1747 float, 

1748 lambda x: ut.in_interval( 

1749 x, 

1750 limit=limits.t_deg, 

1751 type=tbt.IntervalType.CLOSED, 

1752 ), 

1753 error=f"Requested r-axis position of {settings['t_deg']} degree for step '{step_name}' must satisfy '{limits.t_deg.min} <= t_deg <= {limits.t_deg.max}'", 

1754 ), 

1755 }, 

1756 ignore_extra_keys=True, 

1757 ) 

1758 

1759 try: 

1760 schema.validate(settings) 

1761 except UnboundLocalError: 

1762 raise ValueError( 

1763 f"Error. Unsupported yml version {yml_format.version} provided." 

1764 ) 

1765 

1766 

1767def validate_beam_settings( 

1768 microscope: tbt.Microscope, 

1769 beam_type: tbt.BeamType, 

1770 settings: dict, 

1771 yml_format: tbt.YMLFormatVersion, 

1772 step_name: str, 

1773) -> bool: 

1774 """ 

1775 Perform schema checking for beam setting dictionary. 

1776 

1777 This function validates the beam settings dictionary based on the specified yml format and the beam limits of the microscope. 

1778 

1779 ## Parameters 

1780 

1781 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the beam settings. 

1782 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion). 

1783 - `settings` (`dict`): The dictionary containing the beam settings. 

1784 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1785 - `step_name` (`str`): The name of the step in the .yml file. 

1786 

1787 ## Returns 

1788 

1789 - `bool`: True if the settings are valid, False otherwise. 

1790 

1791 ## Raises 

1792 

1793 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

1794 """ 

1795 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings()) 

1796 selected_beam = ut.beam_type(specified_beam, microscope) 

1797 

1798 limits = beam_limits(selected_beam, beam_type) 

1799 

1800 if yml_format.version >= 1.0: 1800 ↛ 1884line 1800 didn't jump to line 1884 because the condition on line 1800 was always true

1801 schema = Schema( 

1802 { 

1803 "voltage_kv": And( 

1804 float, 

1805 lambda x: ut.in_interval( 

1806 x, 

1807 limit=limits.voltage_kv, 

1808 type=tbt.IntervalType.CLOSED, 

1809 ), 

1810 error=f"In step '{step_name}', requested voltage of '{settings['voltage_kv']}' kV not within limits of {limits.voltage_kv.min} kV and {limits.voltage_kv.max} kV.", 

1811 ), 

1812 "voltage_tol_kv": And( 

1813 float, 

1814 lambda x: x > 0, 

1815 error=f"In step '{step_name}', requested voltage tolerance of '{settings['voltage_tol_kv']}' kV must be a positive float (greater than 0).", 

1816 ), 

1817 "current_na": And( 

1818 float, 

1819 lambda x: ut.in_interval( 

1820 x, 

1821 limit=limits.current_na, 

1822 type=tbt.IntervalType.CLOSED, 

1823 ), 

1824 error=f"In step '{step_name}', requested voltage of '{settings['current_na']}' nA not within limits of {limits.current_na.min} nA and {limits.current_na.max} nA", 

1825 ), 

1826 "current_tol_na": And( 

1827 float, 

1828 lambda x: x > 0, 

1829 error=f"In step '{step_name}', 'current_tol_na' must be a positive float (greater than 0)", 

1830 ), 

1831 "hfw_mm": And( 

1832 float, 

1833 lambda x: ut.in_interval( 

1834 x, 

1835 limits.hfw_mm, 

1836 type=tbt.IntervalType.CLOSED, 

1837 ), 

1838 error=f"In step '{step_name}', requested horizontal field width of '{settings['hfw_mm']}' mm not within limits of {limits.hfw_mm.min} mm and {limits.hfw_mm.max} mm", 

1839 ), 

1840 "working_dist_mm": And( 

1841 float, 

1842 lambda x: ut.in_interval( 

1843 x, 

1844 limit=limits.working_distance_mm, 

1845 type=tbt.IntervalType.CLOSED, 

1846 ), 

1847 error=f"In step '{step_name}', requested working distance of '{settings['working_dist_mm']}' mm not within limits of {limits.working_distance_mm.min} mm and {limits.working_distance_mm.max} mm", 

1848 ), 

1849 }, 

1850 ignore_extra_keys=True, 

1851 ) 

1852 

1853 e_beam_schema = Schema( 

1854 { 

1855 "dynamic_focus": Or( 

1856 None, 

1857 bool, 

1858 error=f"In step '{step_name}' with 'electron' beam imaging, 'dynamic_focus' must be a boolean value but '{settings['dynamic_focus']}' of type {type(settings['dynamic_focus'])} was requested.", 

1859 ), 

1860 "tilt_correction": Or( 

1861 None, 

1862 bool, 

1863 error=f"In step '{step_name}' with 'electron' beam imaging, 'tilt_correction' must be a boolean value but '{settings['tilt_correction']}' of type {type(settings['tilt_correction'])} was requested.", 

1864 ), 

1865 }, 

1866 ignore_extra_keys=True, 

1867 ) 

1868 i_beam_schema = Schema( 

1869 { 

1870 "dynamic_focus": Or( 

1871 None, 

1872 False, 

1873 error=f"In step '{step_name}' with 'ion' beam imaging, 'dynamic_focus' must be 'False' or 'None' but '{settings['dynamic_focus']}' of type {type(settings['dynamic_focus'])} was requested.", 

1874 ), 

1875 "tilt_correction": Or( 

1876 None, 

1877 False, 

1878 error=f"In step '{step_name}' with 'ion' beam imaging, 'tilt_correction' must be 'False' or 'None' but '{settings['tilt_correction']}' of type {type(settings['tilt_correction'])} was requested.", 

1879 ), 

1880 }, 

1881 ignore_extra_keys=True, 

1882 ) 

1883 

1884 try: 

1885 schema.validate(settings) 

1886 if specified_beam.type == tbt.BeamType.ELECTRON: 

1887 e_beam_schema.validate(settings) 

1888 if specified_beam.type == tbt.BeamType.ION: 

1889 i_beam_schema.validate(settings) 

1890 except UnboundLocalError: 

1891 raise ValueError( 

1892 f"Error. Unsupported yml version {yml_format.version} provided." 

1893 ) 

1894 

1895 

1896def validate_detector_settings( 

1897 microscope: tbt.Microscope, 

1898 beam_type: tbt.BeamType, 

1899 settings: dict, 

1900 yml_format: tbt.YMLFormatVersion, 

1901 step_name: str, 

1902) -> bool: 

1903 """ 

1904 Perform schema checking for detector setting dictionary. 

1905 

1906 This function validates the detector settings dictionary based on the specified yml format and the detector capabilities of the microscope. 

1907 

1908 ## Parameters 

1909 

1910 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the detector settings. 

1911 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion). 

1912 - `settings` (`dict`): The dictionary containing the detector settings. 

1913 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

1914 - `step_name` (`str`): The name of the step in the .yml file. 

1915 

1916 ## Returns 

1917 

1918 - `bool`: True if the settings are valid, False otherwise. 

1919 

1920 ## Raises 

1921 

1922 - `KeyError`: If auto contrast/brightness settings conflict with fixed brightness/contrast values. 

1923 - `ValueError`: If the yml version is unsupported, or if the settings do not satisfy the specified schema, or if the detector type or mode is unsupported. 

1924 """ 

1925 

1926 # switch to top left quad, enable e-beam 

1927 devices.device_access(microscope=microscope) 

1928 

1929 # set specified beam to active imaging device 

1930 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings()) 

1931 img.set_beam_device( 

1932 microscope=microscope, 

1933 device=specified_beam.device, 

1934 ) 

1935 

1936 auto_cb_db = settings.get("auto_cb") 

1937 use_auto_cb = not ut.none_value_dictionary(settings.get("auto_cb")) 

1938 if use_auto_cb and ( 

1939 settings.get("brightness") is not None or settings.get("contrast") is not None 

1940 ): 

1941 raise KeyError( 

1942 f"Auto contrast/brightness settings of {auto_cb_db} provided while settings of fixed brightness of '{settings.get('brightness')}' and fixed contrast of '{settings.get('contrast')}' were also provided. Users may use either fixed brightness/contrast values or auto contrast/brightness settings, but not both." 

1943 ) 

1944 

1945 if yml_format.version >= 1.0: 1945 ↛ 1980line 1945 didn't jump to line 1980 because the condition on line 1945 was always true

1946 detector = settings.get("type") 

1947 mode = settings.get("mode") 

1948 cb_limit = tbt.Limit(min=0.0, max=1.0) 

1949 schema = Schema( 

1950 { 

1951 "brightness": Or( 

1952 None, 

1953 And( 

1954 float, 

1955 lambda x: ut.in_interval( 

1956 x, 

1957 limit=cb_limit, 

1958 type=tbt.IntervalType.LEFT_OPEN, 

1959 ), 

1960 ), 

1961 error=f"In step '{step_name}', requested fixed brightness of '{settings['brightness']}'. This is not within limits of '>{cb_limit.min}' and '<={cb_limit.max}'.", 

1962 ), 

1963 "contrast": Or( 

1964 None, 

1965 And( 

1966 float, 

1967 lambda x: ut.in_interval( 

1968 x, 

1969 limit=cb_limit, 

1970 type=tbt.IntervalType.LEFT_OPEN, 

1971 ), 

1972 ), 

1973 error=f"In step '{step_name}', requested fixed contrast of '{settings['contrast']}'. This is either not within limits of '>{cb_limit.min}' and '<={cb_limit.max}'.", 

1974 ), 

1975 }, 

1976 ignore_extra_keys=True, 

1977 ) 

1978 

1979 # check detector type 

1980 if not ut.valid_enum_entry(detector, tbt.DetectorType): 

1981 raise ValueError( 

1982 f"Unsupported detector type of '{detector}' on step '{step_name}'." 

1983 ) 

1984 detector_type = tbt.DetectorType(detector) 

1985 microscope_detector_types = available_detector_types(microscope=microscope) 

1986 if detector_type.value not in microscope_detector_types: 1986 ↛ 1987line 1986 didn't jump to line 1987 because the condition on line 1986 was never true

1987 raise ValueError( 

1988 f"Requested detector of {detector_type.value} is unavailable on this tool. Available detectors are: {microscope_detector_types}" 

1989 ) 

1990 # make sure to set this detector as the active one to access modes 

1991 img.detector_type( 

1992 microscope=microscope, 

1993 detector=detector_type, 

1994 ) 

1995 

1996 # check detector mode 

1997 if not ut.valid_enum_entry(mode, tbt.DetectorMode): 

1998 raise ValueError( 

1999 f'Unsupported detector mode of "{mode}" for "{detector}" detector' 

2000 ) 

2001 detector_mode = tbt.DetectorMode(mode) 

2002 microscope_detector_modes = available_detector_modes(microscope=microscope) 

2003 if detector_mode.value not in microscope_detector_modes: 2003 ↛ 2004line 2003 didn't jump to line 2004 because the condition on line 2003 was never true

2004 raise ValueError( 

2005 f"Requested mode of {detector_mode.value} for {detector_type.value} detector is invalid. Valid mode types are: {microscope_detector_modes}" 

2006 ) 

2007 

2008 # check detector settings 

2009 try: 

2010 schema.validate(settings) 

2011 except UnboundLocalError: 

2012 raise ValueError( 

2013 f"Error. Unsupported yml version {yml_format.version} provided." 

2014 ) 

2015 

2016 

2017def validate_EBSD_EDS_settings( 

2018 ebsd_oem: str, 

2019 eds_oem: str, 

2020) -> bool: 

2021 """ 

2022 Check EBSD and EDS OEM and connection for supported OEMs. 

2023 

2024 This function ensures that the specified EBSD and EDS OEMs are supported and that the connection settings are valid. 

2025 

2026 ## Parameters 

2027 

2028 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2029 - `connection_host` (`str`): The host for the microscope connection. 

2030 - `connection_port` (`str`): The port for the microscope connection. 

2031 - `ebsd_oem` (`str`): The OEM for the EBSD device. 

2032 - `eds_oem` (`str`): The OEM for the EDS device. 

2033 

2034 ## Returns 

2035 

2036 - `bool`: True if the settings are valid, False otherwise. 

2037 

2038 ## Raises 

2039 

2040 - `NotImplementedError`: If differing EBSD and EDS OEMs are requested. 

2041 - `ValueError`: If the EBSD or EDS OEM is unsupported. 

2042 - `SystemError`: If the Laser API is not accessible. 

2043 """ 

2044 # ensure same manufacturer for both EBSD and EDS 

2045 if (ebsd_oem is not None) and (eds_oem is not None) and (ebsd_oem != eds_oem): 

2046 raise NotImplementedError( 

2047 f"Differing EBSD and EDS OEMs are not supported. Requested EBSD OEM of '{ebsd_oem}' and EDS OEM of '{eds_oem}'." 

2048 ) 

2049 

2050 # Check EBSD OEM 

2051 if not ut.valid_enum_entry(ebsd_oem, tbt.ExternalDeviceOEM): 2051 ↛ 2052line 2051 didn't jump to line 2052 because the condition on line 2051 was never true

2052 raise ValueError( 2052 ↛ exit,   2052 ↛ exit2 missed branches: 1) line 2052 didn't jump to the function exit, 2) line 2052 didn't except from function 'validate_EBSD_EDS_settings' because the raise on line 2052 wasn't executed

2053 f"Unsupported EBSD OEM of '{ebsd_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}" 

2054 ) 

2055 ebsd_device = tbt.ExternalDeviceOEM(ebsd_oem) 

2056 # Check EDS OEM 

2057 if not ut.valid_enum_entry(eds_oem, tbt.ExternalDeviceOEM): 2057 ↛ 2058line 2057 didn't jump to line 2058 because the condition on line 2057 was never true

2058 raise ValueError( 2058 ↛ exit,   2058 ↛ exit2 missed branches: 1) line 2058 didn't jump to the function exit, 2) line 2058 didn't except from function 'validate_EBSD_EDS_settings' because the raise on line 2058 wasn't executed

2059 f"Unsupported EDS OEM of '{eds_oem}'. Supported OEM types are: {[i.value for i in tbt.ExternalDeviceOEM]}" 

2060 ) 

2061 eds_device = tbt.ExternalDeviceOEM(eds_oem) 

2062 

2063 # exit if both devices are none, no need for laser control 

2064 if ebsd_device == eds_device == tbt.ExternalDeviceOEM.NONE: 2064 ↛ 2068line 2064 didn't jump to line 2068 because the condition on line 2064 was always true

2065 return True 

2066 

2067 # check EBSD and EDS connection 

2068 try: 

2069 fs_laser.tfs_laser 

2070 except: 

2071 raise SystemError( 

2072 "EBSD and/or EDS control requested, but Laser API not accessible, so cannot use EBSD and EDS control. Please restart Laser API, or if not installed, change OEM to 'null', or leave blank in settings file." 

2073 ) 

2074 

2075 ###################### PROPOSED CHANGE ###################### 

2076 # Retracting devices here is unnecessary 

2077 # This causes devices to retract during validation 

2078 # Device retraction is needed only when startin/ending experiments 

2079 # Both of which are outside the scope of validation and already handled elsewhere 

2080 ###################### PROPOSED CHANGE ###################### 

2081 # microscope = tbt.Microscope() 

2082 # ut.connect_microscope( 

2083 # microscope=microscope, 

2084 # quiet_output=True, 

2085 # connection_host=connection_host, 

2086 # connection_port=connection_port, 

2087 # ) 

2088 # if tbt.ExternalDeviceOEM(eds_oem) != tbt.ExternalDeviceOEM.NONE: 

2089 # devices.retract_EDS(microscope=microscope) 

2090 # if tbt.ExternalDeviceOEM(ebsd_oem) != tbt.ExternalDeviceOEM.NONE: 

2091 # devices.retract_EBSD(microscope=microscope) 

2092 # ut.disconnect_microscope( 

2093 # microscope=microscope, 

2094 # quiet_output=True, 

2095 # ) 

2096 

2097 return True 

2098 

2099 

2100def validate_general_settings( 

2101 settings: dict, 

2102 yml_format: tbt.YMLFormatVersion, 

2103) -> bool: 

2104 """ 

2105 Perform schema checking for general setting dictionary. 

2106 

2107 This function validates the general settings dictionary based on the specified yml format. It checks the microscope connection and EBSD/EDS connection if valid OEMs are specified. 

2108 

2109 ## Parameters 

2110 

2111 - `settings` (`dict`): The dictionary containing the general settings. 

2112 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2113 

2114 ## Returns 

2115 

2116 - `bool`: True if the settings are valid, False otherwise. 

2117 

2118 ## Raises 

2119 

2120 - `ValueError`: If the general settings dictionary is empty, or if the settings do not satisfy the specified schema, or if the connection is invalid. 

2121 - `NotImplementedError`: If the sectioning axis is unsupported. 

2122 """ 

2123 

2124 if settings == {}: 2124 ↛ 2125line 2124 didn't jump to line 2125 because the condition on line 2124 was never true

2125 raise ValueError("General settings dictionary is empty.") 

2126 

2127 slice_thickness_limit_um = Constants.slice_thickness_limit_um 

2128 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_generic 

2129 

2130 if yml_format.version >= 1.0: 2130 ↛ 2141line 2130 didn't jump to line 2141 because the condition on line 2130 was always true

2131 sectioning_axis = settings.get("sectioning_axis") 

2132 connection_host = settings.get("connection_host") 

2133 connection_port = settings.get("connection_port") 

2134 ebsd_oem = settings.get("EBSD_OEM") 

2135 eds_oem = settings.get("EDS_OEM") 

2136 exp_dir = settings.get("exp_dir") 

2137 h5_log_name = settings.get("h5_log_name") 

2138 

2139 # Validate the non-numeric values 

2140 # Check sectioning axis 

2141 if not ut.valid_enum_entry(sectioning_axis, tbt.SectioningAxis): 2141 ↛ 2142line 2141 didn't jump to line 2142 because the condition on line 2141 was never true

2142 raise ValueError(f"Unsupported sectioning axis of {sectioning_axis}.") 

2143 # TODO 

2144 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z: 

2145 raise NotImplementedError("Currently only Z-axis sectioning is supported.") 

2146 if tbt.SectioningAxis(sectioning_axis) != tbt.SectioningAxis.Z: 

2147 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_non_Z_sectioning # overwrite 

2148 warnings.warn( 

2149 "Pre-tilt value must be zero (0.0) degrees when using a sectioning axis other than 'Z'" 

2150 ) 

2151 # Check connection host and port 

2152 if not ut.valid_microscope_connection( 2152 ↛ 2156line 2152 didn't jump to line 2156 because the condition on line 2152 was never true

2153 host=connection_host, 

2154 port=connection_port, 

2155 ): 

2156 raise ValueError( 

2157 f"Unsupported connection with host of {connection_host} and port of {connection_port}." 

2158 ) 

2159 

2160 # check EBSD and EDS 

2161 validate_EBSD_EDS_settings( 

2162 ebsd_oem=ebsd_oem, 

2163 eds_oem=eds_oem, 

2164 ) 

2165 

2166 # Check exp dir 

2167 try: 

2168 Path(exp_dir).mkdir( 

2169 parents=True, 

2170 exist_ok=True, 

2171 ) 

2172 except TypeError: 

2173 raise ValueError( 

2174 f'Requested experimental directory of "{exp_dir}", which is not a valid path.' 

2175 ) 

2176 # Check h5 log name 

2177 if not isinstance(h5_log_name, str): 2177 ↛ 2178line 2177 didn't jump to line 2178 because the condition on line 2177 was never true

2178 raise ValueError(f'Unsupported h5 log name of "{h5_log_name}"') 

2179 

2180 schema = Schema( 

2181 { 

2182 "slice_thickness_um": And( 

2183 float, 

2184 lambda x: ut.in_interval( 

2185 x, 

2186 limit=slice_thickness_limit_um, 

2187 type=tbt.IntervalType.CLOSED, 

2188 ), 

2189 error=f"Requested slice thickness of {settings['slice_thickness_um']} um must satisfy '{slice_thickness_limit_um.min} <= slice_thickness_um <= {slice_thickness_limit_um.max}'", 

2190 ), 

2191 "max_slice_num": And( 

2192 int, 

2193 lambda x: x > 0, 

2194 error=f"Requested max slice number of {settings['max_slice_num']} must satisfy '0 < max_slice_number'", 

2195 ), 

2196 "pre_tilt_deg": And( 

2197 float, 

2198 lambda x: ut.in_interval( 

2199 x, 

2200 limit=pre_tilt_limit_deg, 

2201 type=tbt.IntervalType.CLOSED, 

2202 ), 

2203 error=f"Requested pre tilt of {settings['pre_tilt_deg']} degrees must satisfy '{pre_tilt_limit_deg.min} <= pre_tilt_deg <= {pre_tilt_limit_deg.max}'", 

2204 ), 

2205 "stage_translational_tol_um": And( 

2206 float, 

2207 lambda x: x > 0, 

2208 error=f"Requested stage translational tolerance of {settings['stage_translational_tol_um']} um must be a positive float (greater than 0)", 

2209 ), 

2210 "stage_angular_tol_deg": And( 

2211 float, 

2212 lambda x: x > 0, 

2213 error=f"Requested stage angular tolerance of {settings['stage_angular_tol_deg']} degrees must be a positive float (greater than 0)", 

2214 ), 

2215 "step_count": And( 

2216 int, 

2217 lambda x: x > 0, 

2218 error=f"Requested step count of {settings['step_count']} must be a positive int (greater than 0)", 

2219 ), 

2220 }, 

2221 ignore_extra_keys=True, 

2222 ) 

2223 

2224 try: 

2225 schema.validate(settings) 

2226 except UnboundLocalError: 

2227 raise ValueError( 

2228 f"Error. Unsupported yml version {yml_format.version} provided." 

2229 ) 

2230 

2231 

2232def validate_scan_settings( 

2233 microscope: tbt.Microscope, 

2234 beam_type: tbt.BeamType, 

2235 settings: dict, 

2236 yml_format: tbt.YMLFormatVersion, 

2237 step_name: str, 

2238) -> bool: 

2239 """ 

2240 Perform schema checking for scan setting dictionary. 

2241 

2242 This function validates the scan settings dictionary based on the specified yml format and the scan limits of the microscope. 

2243 

2244 ## Parameters 

2245 

2246 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the scan settings. 

2247 - `beam_type` (`tbt.BeamType`): The type of the beam (electron or ion). 

2248 - `settings` (`dict`): The dictionary containing the scan settings. 

2249 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2250 - `step_name` (`str`): The name of the step in the .yml file. 

2251 

2252 ## Returns 

2253 

2254 - `bool`: True if the settings are valid, False otherwise. 

2255 

2256 ## Raises 

2257 

2258 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2259 """ 

2260 specified_beam = beam_object_type(beam_type)(settings=tbt.BeamSettings()) 

2261 selected_beam = ut.beam_type(specified_beam, microscope) 

2262 

2263 """Schema checking for scan setting dictionary, format specified by yml_format""" 

2264 if yml_format.version >= 1.0: 2264 ↛ 2271line 2264 didn't jump to line 2271 because the condition on line 2264 was always true

2265 limits = scan_limits(selected_beam) 

2266 resolution = settings["resolution"] 

2267 rotation_limit = limits.rotation_deg 

2268 dwell_limit = limits.dwell_us 

2269 

2270 # check resolution 

2271 res = string_to_res(resolution) 

2272 

2273 schema = Schema( 

2274 { 

2275 "resolution": And( 

2276 str, 

2277 lambda x: valid_string_resolution(x), 

2278 error=f"In '{step_name}', resolution provided was {settings['resolution']}, but must be an integer width and height in format '[width]x[height]' with each dimension satisfying '{Constants.scan_resolution_limit.min} <= [value] <= {Constants.scan_resolution_limit.max}", 

2279 ), 

2280 "rotation_deg": And( 

2281 float, 

2282 lambda x: ut.in_interval( 

2283 x, 

2284 limit=rotation_limit, 

2285 type=tbt.IntervalType.CLOSED, 

2286 ), 

2287 error=f"In '{step_name}', requested fixed rotation of '{settings['rotation_deg']}' degrees. This is not a float or not within limits of '>={rotation_limit.min}' and '<={rotation_limit.max}' degrees. Setting is of type {type(settings['rotation_deg'])}.", 

2288 ), 

2289 "dwell_time_us": And( 

2290 float, 

2291 lambda x: ut.in_interval( 

2292 x, 

2293 limit=dwell_limit, 

2294 type=tbt.IntervalType.CLOSED, 

2295 ), 

2296 error=f"In '{step_name}', requested fixed dwell_time of '{settings['dwell_time_us']}' microseconds. This is a float or not within limits of '>={dwell_limit.min}' and '<={dwell_limit.max}' microseconds. Settting is of type {type(settings['dwell_time_us'])}.", 

2297 ), 

2298 }, 

2299 ignore_extra_keys=True, 

2300 ) 

2301 

2302 try: 

2303 schema.validate(settings) 

2304 except UnboundLocalError: 

2305 raise ValueError( 

2306 f"Error. Unsupported yml version {yml_format.version} provided." 

2307 ) 

2308 

2309 

2310def stage_position_settings( 

2311 microscope: tbt.Microscope, 

2312 step_name: str, 

2313 general_settings: tbt.GeneralSettings, 

2314 step_stage_settings: dict, 

2315 yml_format: tbt.YMLFormatVersion, 

2316) -> tbt.StageSettings: 

2317 """ 

2318 Create a StagePositionUser object from settings, including validation. 

2319 

2320 This function creates a `StagePositionUser` object from the provided settings and performs validation. 

2321 

2322 ## Parameters 

2323 

2324 - `microscope` (`tbt.Microscope`): The microscope object for which to set the stage position. 

2325 - `step_name` (`str`): The name of the step in the .yml file. 

2326 - `general_settings` (`tbt.GeneralSettings`): The general settings object. 

2327 - `step_stage_settings` (`dict`): The dictionary containing the stage position settings for the step. 

2328 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2329 

2330 ## Returns 

2331 

2332 - `tbt.StageSettings`: The stage settings object. 

2333 

2334 ## Raises 

2335 

2336 - `NotImplementedError`: If the rotation side value is unsupported. 

2337 - `ValueError`: If the stage position settings do not satisfy the specified schema. 

2338 """ 

2339 

2340 if yml_format.version >= 1.0: 2340 ↛ 2349line 2340 didn't jump to line 2349 because the condition on line 2340 was always true

2341 pos_db = step_stage_settings.get("initial_position") 

2342 

2343 rotation_side = step_stage_settings["rotation_side"] 

2344 if not ut.valid_enum_entry(rotation_side, tbt.RotationSide): 

2345 raise NotImplementedError( 

2346 f"Unsupported rotation_side value of '{rotation_side}' of type '{type(rotation_side)}' in step '{step_name}', supported rotation_side values are: {[i.value for i in tbt.RotationSide]}." 

2347 ) 

2348 

2349 validate_stage_position( 

2350 microscope=microscope, 

2351 step_name=step_name, 

2352 settings=pos_db, 

2353 yml_format=yml_format, 

2354 ) 

2355 

2356 initial_position = tbt.StagePositionUser( 

2357 x_mm=pos_db["x_mm"], 

2358 y_mm=pos_db["y_mm"], 

2359 z_mm=pos_db["z_mm"], 

2360 r_deg=pos_db["r_deg"], 

2361 t_deg=pos_db["t_deg"], 

2362 ) 

2363 

2364 pretilt_angle_deg = general_settings.pre_tilt_deg 

2365 sectioning_axis = general_settings.sectioning_axis 

2366 

2367 stage_settings = tbt.StageSettings( 

2368 microscope=microscope, 

2369 initial_position=initial_position, 

2370 pretilt_angle_deg=pretilt_angle_deg, 

2371 sectioning_axis=sectioning_axis, 

2372 rotation_side=tbt.RotationSide(rotation_side), 

2373 # movement_mode=tbt.StageMovementMode.OUT_OF_PLANE, 

2374 ) 

2375 

2376 return stage_settings 

2377 

2378 

2379def validate_pulse_settings( 

2380 settings: dict, 

2381 yml_format: tbt.YMLFormatVersion, 

2382 step_name: str, 

2383) -> bool: 

2384 """ 

2385 Perform schema checking for pulse setting dictionary. 

2386 

2387 This function validates the pulse settings dictionary based on the specified yml format. 

2388 

2389 ## Parameters 

2390 

2391 - `settings` (`dict`): The dictionary containing the pulse settings. 

2392 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2393 - `step_name` (`str`): The name of the step in the .yml file. 

2394 

2395 ## Returns 

2396 

2397 - `bool`: True if the settings are valid, False otherwise. 

2398 

2399 ## Raises 

2400 

2401 - `NotImplementedError`: If the wavelength or polarization value is unsupported. 

2402 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2403 """ 

2404 if yml_format.version >= 1.0: 2404 ↛ 2431line 2404 didn't jump to line 2431 because the condition on line 2404 was always true

2405 wavelength_nm = settings.get("wavelength_nm") 

2406 if not ut.valid_enum_entry(wavelength_nm, tbt.LaserWavelength): 

2407 raise NotImplementedError( 

2408 f"In 'laser' step_type for step '{step_name}', unsupported wavelength of '{wavelength_nm}' nm (data type {type(wavelength_nm)}), supported wavelengths are: {[i.value for i in tbt.LaserWavelength]}." 

2409 ) 

2410 polarization = settings.get("polarization") 

2411 if not ut.valid_enum_entry(polarization, tbt.LaserPolarization): 

2412 raise NotImplementedError( 

2413 f"In 'laser' step_type for step '{step_name}', unsupported laser polarization of '{polarization}', supported values are: {[i.value for i in tbt.LaserPolarization]}." 

2414 ) 

2415 

2416 schema = Schema( 

2417 { 

2418 "divider": And( 

2419 int, 

2420 lambda x: x > 0, 

2421 error=f"In 'laser' step_type for step '{step_name}', 'divider' parameter must be a positive integer greater than 0 but '{settings['divider']}' was requested.", 

2422 ), 

2423 "energy_uj": And( 

2424 float, 

2425 lambda x: x > 0, 

2426 error=f"In 'laser' step_type for step '{step_name}', 'energy_uj' parameter must be a positive float greater than 0 but '{settings['energy_uj']}' was requested.", 

2427 ), 

2428 }, 

2429 ignore_extra_keys=True, 

2430 ) 

2431 try: 

2432 schema.validate(settings) 

2433 except UnboundLocalError: 

2434 raise ValueError( 

2435 f"Error. Unsupported yml version {yml_format.version} provided." 

2436 ) 

2437 

2438 

2439def validate_laser_optics_settings( 

2440 settings: dict, 

2441 yml_format: tbt.YMLFormatVersion, 

2442 step_name: str, 

2443) -> bool: 

2444 """ 

2445 Perform schema checking for laser optics setting dictionary. 

2446 

2447 This function validates the laser optics settings dictionary based on the specified yml format. 

2448 

2449 ## Parameters 

2450 

2451 - `settings` (`dict`): The dictionary containing the laser optics settings. 

2452 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2453 - `step_name` (`str`): The name of the step in the .yml file. 

2454 

2455 ## Returns 

2456 

2457 - `bool`: True if the settings are valid, False otherwise. 

2458 

2459 ## Raises 

2460 

2461 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2462 """ 

2463 if yml_format.version >= 1.0: 2463 ↛ 2486line 2463 didn't jump to line 2486 because the condition on line 2463 was always true

2464 schema = Schema( 

2465 { 

2466 "objective_position_mm": And( 

2467 float, 

2468 lambda x: ut.in_interval( 

2469 x, 

2470 limit=Constants.laser_objective_limit_mm, 

2471 type=tbt.IntervalType.CLOSED, 

2472 ), 

2473 error=f"In 'laser' step_type for step '{step_name}', 'objective_position_mm' parameter must be a float satisfying the following: {Constants.laser_objective_limit_mm.min} mm <= value <= {Constants.laser_objective_limit_mm.max} mm. '{settings['objective_position_mm']}' mm (of type {type(settings['objective_position_mm'])}) was requested.", 

2474 ), 

2475 "beam_shift_um_x": And( 

2476 float, 

2477 error=f"In 'laser' step_type for step '{step_name}', 'x' parameter for 'beam_shift_um' sub-dictioanry must be a float but '{settings['beam_shift_um_x']}' (of type {type(settings['beam_shift_um_x'])}) was requested.", 

2478 ), 

2479 "beam_shift_um_y": And( 

2480 float, 

2481 error=f"In 'laser' step_type for step '{step_name}', 'y' parameter for 'beam_shift_um' sub-dictioanry must be a float but '{settings['beam_shift_um_y']}' (of type {type(settings['beam_shift_um_y'])}) was requested.", 

2482 ), 

2483 }, 

2484 ignore_extra_keys=True, 

2485 ) 

2486 try: 

2487 schema.validate(settings) 

2488 except UnboundLocalError: 

2489 raise ValueError( 

2490 f"Error. Unsupported yml version {yml_format.version} provided." 

2491 ) 

2492 

2493 

2494def validate_laser_box_settings( 

2495 settings: dict, 

2496 yml_format: tbt.YMLFormatVersion, 

2497 step_name: str, 

2498) -> bool: 

2499 """ 

2500 Perform schema checking for laser box pattern setting dictionary. 

2501 

2502 This function validates the laser box pattern settings dictionary based on the specified yml format. 

2503 

2504 ## Parameters 

2505 

2506 - `settings` (`dict`): The dictionary containing the laser box pattern settings. 

2507 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2508 - `step_name` (`str`): The name of the step in the .yml file. 

2509 

2510 ## Returns 

2511 

2512 - `bool`: True if the settings are valid, False otherwise. 

2513 

2514 ## Raises 

2515 

2516 - `NotImplementedError`: If the scan type or coordinate reference value is unsupported. 

2517 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2518 """ 

2519 if yml_format.version >= 1.0: 2519 ↛ 2575line 2519 didn't jump to line 2575 because the condition on line 2519 was always true

2520 # scan type 

2521 scan_type = settings.get("scan_type") 

2522 scan_types = [ 

2523 tbt.LaserScanType.RASTER, 

2524 tbt.LaserScanType.SERPENTINE, 

2525 ] 

2526 scan_type_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported scan type of '{scan_type}' for box pattern, supported scan types are: {[i.value for i in scan_types]}." 

2527 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType): 

2528 raise NotImplementedError(scan_type_error_msg) 

2529 if tbt.LaserScanType(scan_type) not in scan_types: 

2530 raise NotImplementedError(scan_type_error_msg) 

2531 

2532 # coordinate reference 

2533 coordinate_ref = settings.get("coordinate_ref") 

2534 coord_refs = [ 

2535 tbt.CoordinateReference.CENTER, 

2536 tbt.CoordinateReference.UPPER_CENTER, 

2537 tbt.CoordinateReference.UPPER_LEFT, 

2538 ] 

2539 coord_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported coordinate reference of '{coordinate_ref}' for box pattern, supported coordinate references are: {[i.value for i in coord_refs]}." 

2540 if not ut.valid_enum_entry(coordinate_ref, tbt.CoordinateReference): 

2541 raise NotImplementedError(coord_error_msg) 

2542 if tbt.CoordinateReference(coordinate_ref) not in coord_refs: 

2543 raise NotImplementedError(coord_error_msg) 

2544 

2545 schema = Schema( 

2546 { 

2547 "passes": And( 

2548 int, 

2549 lambda x: x > 0, 

2550 error=f"In 'laser' step_type for step '{step_name}', 'passes' parameter in 'box' type pattern must be a positive integer. '{settings['passes']}' (of type {type(settings['passes'])}) was requested.", 

2551 ), 

2552 "size_x_um": And( 

2553 float, 

2554 lambda x: x > 0, 

2555 error=f"In 'laser' step_type for step '{step_name}', 'size_x_um' parameter in 'box' type pattern must be a positive float. '{settings['size_x_um']}' mm (of type {type(settings['size_x_um'])}) was requested.", 

2556 ), 

2557 "size_y_um": And( 

2558 float, 

2559 lambda x: x > 0, 

2560 error=f"In 'laser' step_type for step '{step_name}', 'size_y_um' parameter in 'box' type pattern must be a positive float. '{settings['size_y_um']}' mm (of type {type(settings['size_y_um'])}) was requested.", 

2561 ), 

2562 "pitch_x_um": And( 

2563 float, 

2564 lambda x: x > 0, 

2565 error=f"In 'laser' step_type for step '{step_name}', 'pitch_x_um' parameter in 'box' type pattern must be a positive float. '{settings['pitch_x_um']}' mm (of type {type(settings['pitch_x_um'])}) was requested.", 

2566 ), 

2567 "pitch_y_um": And( 

2568 float, 

2569 lambda x: x > 0, 

2570 error=f"In 'laser' step_type for step '{step_name}', 'pitch_y_um' parameter in 'box' type pattern must be a positive float. '{settings['pitch_y_um']}' mm (of type {type(settings['pitch_y_um'])}) was requested.", 

2571 ), 

2572 }, 

2573 ignore_extra_keys=True, 

2574 ) 

2575 try: 

2576 schema.validate(settings) 

2577 except UnboundLocalError: 

2578 raise ValueError( 

2579 f"Error. Unsupported yml version {yml_format.version} provided." 

2580 ) 

2581 

2582 

2583def validate_laser_line_settings( 

2584 settings: dict, 

2585 yml_format: tbt.YMLFormatVersion, 

2586 step_name: str, 

2587) -> bool: 

2588 """ 

2589 Perform schema checking for laser line pattern setting dictionary. 

2590 

2591 This function validates the laser line pattern settings dictionary based on the specified yml format. 

2592 

2593 ## Parameters 

2594 

2595 - `settings` (`dict`): The dictionary containing the laser line pattern settings. 

2596 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2597 - `step_name` (`str`): The name of the step in the .yml file. 

2598 

2599 ## Returns 

2600 

2601 - `bool`: True if the settings are valid, False otherwise. 

2602 

2603 ## Raises 

2604 

2605 - `NotImplementedError`: If the scan type value is unsupported. 

2606 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2607 """ 

2608 if yml_format.version >= 1.0: 

2609 # scan type 

2610 scan_type = settings.get("scan_type") 

2611 scan_types = [ 

2612 tbt.LaserScanType.SINGLE, 

2613 tbt.LaserScanType.LAP, 

2614 ] 

2615 scan_type_error_msg = f"In 'laser' step_type for step '{step_name}', unsupported scan type of '{scan_type}' for line pattern, supported scan types are: {[i.value for i in scan_types]}." 

2616 if not ut.valid_enum_entry(scan_type, tbt.LaserScanType): 

2617 raise NotImplementedError(scan_type_error_msg) 

2618 if tbt.LaserScanType(scan_type) not in scan_types: 

2619 raise NotImplementedError(scan_type_error_msg) 

2620 

2621 schema = Schema( 

2622 { 

2623 "passes": And( 

2624 int, 

2625 lambda x: x > 0, 

2626 error=f"In 'laser' step_type for step '{step_name}', 'passes' parameter in 'line' type pattern must be a positive integer. '{settings['passes']}' (of type {type(settings['passes'])}) was requested.", 

2627 ), 

2628 "size_um": And( 

2629 float, 

2630 lambda x: x > 0, 

2631 error=f"In 'laser' step_type for step '{step_name}', 'size_um' parameter in 'line' type pattern must be a positive float. '{settings['size_um']}' mm (of type {type(settings['size_um'])}) was requested.", 

2632 ), 

2633 "pitch_um": And( 

2634 float, 

2635 lambda x: x > 0, 

2636 error=f"In 'laser' step_type for step '{step_name}', 'pitch_um' parameter in 'line' type pattern must be a positive float. '{settings['pitch_um']}' mm (of type {type(settings['pitch_um'])}) was requested.", 

2637 ), 

2638 }, 

2639 ignore_extra_keys=True, 

2640 ) 

2641 try: 

2642 schema.validate(settings) 

2643 except UnboundLocalError: 

2644 raise ValueError( 

2645 f"Error. Unsupported yml version {yml_format.version} provided." 

2646 ) 

2647 

2648 

2649def validate_laser_mode_settings( 

2650 settings: dict, 

2651 yml_format: tbt.YMLFormatVersion, 

2652 step_name: str, 

2653) -> bool: 

2654 """ 

2655 Perform schema checking for laser mode setting dictionary. 

2656 

2657 This function validates the laser mode settings dictionary based on the specified yml format. 

2658 

2659 ## Parameters 

2660 

2661 - `settings` (`dict`): The dictionary containing the laser mode settings. 

2662 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2663 - `step_name` (`str`): The name of the step in the .yml file. 

2664 

2665 ## Returns 

2666 

2667 - `bool`: True if the settings are valid, False otherwise. 

2668 

2669 ## Raises 

2670 

2671 - `NotImplementedError`: If the laser pattern mode value is unsupported. 

2672 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2673 """ 

2674 if yml_format.version >= 1.0: 2674 ↛ 2722line 2674 didn't jump to line 2722 because the condition on line 2674 was always true

2675 mode = settings.get("mode") 

2676 if not ut.valid_enum_entry(mode, tbt.LaserPatternMode): 

2677 raise NotImplementedError( 

2678 f"In 'laser' step_type for step '{step_name}', unsupported laser pattern mode of '{mode}', supported values are: {[i.value for i in tbt.LaserPatternMode]}." 

2679 ) 

2680 

2681 schema = Schema( 

2682 { 

2683 "rotation_deg": And( 

2684 float, 

2685 error=f"In 'laser' step_type for step '{step_name}', 'rotation_deg' parameter must be a float. '{settings['rotation_deg']}' degrees (of type {type(settings['rotation_deg'])}) was requested.", 

2686 ), 

2687 }, 

2688 ignore_extra_keys=True, 

2689 ) 

2690 schema_fine = Schema( 

2691 { 

2692 "pixel_dwell_ms": Or( 

2693 None, 

2694 "null", 

2695 "None", 

2696 error=f"In 'laser' step_type for step '{step_name}', 'pixel_dwell_ms' parameter does not apply to the selected 'fine' milling mode. Set 'pixel_dwell_ms' to 'null' 'None' or leave the entry blank to continue.", 

2697 ), 

2698 "pulses_per_pixel": And( 

2699 int, 

2700 lambda x: x > 0, 

2701 error=f"In 'laser' step_type for step '{step_name}', 'pulses_per_pixel' parameter is required for pattern type of 'fine'. 'pulses_per_pixel' must be a positive integer greater than 0. '{settings['pulses_per_pixel']}' (of type {type(settings['pulses_per_pixel'])}) was requested.", 

2702 ), 

2703 }, 

2704 ignore_extra_keys=True, 

2705 ) 

2706 schema_coarse = Schema( 2706 ↛ exitline 2706 didn't jump to the function exit

2707 { 

2708 "pixel_dwell_ms": And( 

2709 float, 

2710 lambda x: x > 0.0, 

2711 error=f"In 'laser' step_type for step '{step_name}', 'pixel_dwell_ms' parameter is required for pattern type of 'coarse'. 'pixel_dwell_ms' must be a positive float greater than 0. '{settings['pixel_dwell_ms']}' (of type {type(settings['pixel_dwell_ms'])}) was requested.", 

2712 ), 

2713 "pulses_per_pixel": Or( 

2714 None, 

2715 "null", 

2716 "None", 

2717 error=f"In 'laser' step_type for step '{step_name}', 'pulses_per_pixel' parameter does not apply to the selected 'coarse' milling mode. Set 'pulses_per_pixel' to 'null' 'None' or leave the entry blank to continue.", 

2718 ), 

2719 }, 

2720 ignore_extra_keys=True, 

2721 ) 

2722 try: 

2723 schema.validate(settings) 

2724 if mode == "fine": 2724 ↛ 2726line 2724 didn't jump to line 2726 because the condition on line 2724 was always true

2725 schema_fine.validate(settings) 

2726 if mode == "coarse": 2726 ↛ 2727line 2726 didn't jump to line 2727 because the condition on line 2726 was never true

2727 schema_coarse.validate(settings) 

2728 except UnboundLocalError: 

2729 raise ValueError( 

2730 f"Error. Unsupported yml version {yml_format.version} provided." 

2731 ) 

2732 

2733 

2734def validate_laser_pattern_settings( 

2735 settings: dict, 

2736 yml_format: tbt.YMLFormatVersion, 

2737 step_name: str, 

2738) -> tbt.LaserPatternType: 

2739 """ 

2740 Perform schema checking for laser pattern setting dictionary. 

2741 

2742 This function validates the laser pattern settings dictionary based on the specified yml format and determines the type of pattern (box or line). 

2743 

2744 ## Parameters 

2745 

2746 - `settings` (`dict`): The dictionary containing the laser pattern settings. 

2747 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2748 - `step_name` (`str`): The name of the step in the .yml file. 

2749 

2750 ## Returns 

2751 

2752 - `tbt.LaserPatternType`: The type of the laser pattern (box or line). 

2753 

2754 ## Raises 

2755 

2756 - `KeyError`: If required settings are missing from the .yml file or if multiple pattern types are specified. 

2757 - `ValueError`: If the laser pattern settings are invalid. 

2758 """ 

2759 if yml_format.version >= 1.0: 2759 ↛ exitline 2759 didn't return from function 'validate_laser_pattern_settings' because the condition on line 2759 was always true

2760 # determine type of pattern (only one allowed) 

2761 type_set_db = settings.get("type") 

2762 if type_set_db is None: 2762 ↛ 2763line 2762 didn't jump to line 2763 because the condition on line 2762 was never true

2763 raise KeyError( 

2764 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'laser' step_type for step '{step_name}'." 

2765 ) 

2766 

2767 box_settings = type_set_db.get("box") 

2768 line_settings = type_set_db.get("line") 

2769 if box_settings is None: 2769 ↛ 2770line 2769 didn't jump to line 2770 because the condition on line 2769 was never true

2770 box_pattern = False 

2771 else: 

2772 box_pattern = not ut.none_value_dictionary(box_settings) 

2773 if line_settings is None: 2773 ↛ 2774line 2773 didn't jump to line 2774 because the condition on line 2773 was never true

2774 line_pattern = False 

2775 else: 

2776 line_pattern = not ut.none_value_dictionary(line_settings) 

2777 if box_pattern == line_pattern == False: 2777 ↛ 2778line 2777 didn't jump to line 2778 because the condition on line 2777 was never true

2778 raise KeyError( 

2779 f"Invalid .yml file in 'laser' step_type for step '{step_name}'. No pattern type settings found." 

2780 ) 

2781 if box_pattern == line_pattern == True: 2781 ↛ 2782line 2781 didn't jump to line 2782 because the condition on line 2781 was never true

2782 raise KeyError( 

2783 f"Invalid .yml file in 'laser' step_type for step '{step_name}'. Pattern settings for one and only one type are allowed. Type settings found for both 'box' and 'line' type. Please leave one set of type settings completely blank, enter 'null' for each parameter, or remove the unused subdictionary completely from the .yml file." 

2784 ) 

2785 validate_laser_mode_settings( 

2786 settings=settings, 

2787 yml_format=yml_format, 

2788 step_name=step_name, 

2789 ) 

2790 

2791 if box_pattern: 2791 ↛ 2798line 2791 didn't jump to line 2798 because the condition on line 2791 was always true

2792 validate_laser_box_settings( 

2793 settings=box_settings, 

2794 yml_format=yml_format, 

2795 step_name=step_name, 

2796 ) 

2797 return tbt.LaserPatternType.BOX 

2798 elif line_pattern: 

2799 validate_laser_line_settings( 

2800 settings=line_settings, 

2801 yml_format=yml_format, 

2802 step_name=step_name, 

2803 ) 

2804 return tbt.LaserPatternType.LINE 

2805 else: 

2806 raise ValueError( 

2807 f"Invalid laser pattern settings for step {step_name}. Supported types are {[i.value for i in tbt.LaserPatternType]}" 

2808 ) 

2809 

2810 

2811def validate_fib_pattern_settings( 

2812 microscope: tbt.Microscope, 

2813 settings: dict, 

2814 yml_format: tbt.YMLFormatVersion, 

2815 step_name: str, 

2816) -> Union[ 

2817 tbt.FIBRectanglePattern, 

2818 tbt.FIBRegularCrossSection, 

2819 tbt.FIBCleaningCrossSection, 

2820 tbt.FIBStreamPattern, 

2821]: 

2822 """ 

2823 Perform schema checking for FIB pattern setting dictionary. 

2824 

2825 This function validates the FIB pattern settings dictionary based on the specified yml format and determines the type of pattern (rectangle, regular cross section, cleaning cross section, or selected area). 

2826 

2827 ## Parameters 

2828 

2829 - `microscope` (`tbt.Microscope`): The microscope object for which to validate the FIB pattern settings. 

2830 - `settings` (`dict`): The dictionary containing the FIB pattern settings. 

2831 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

2832 - `step_name` (`str`): The name of the step in the .yml file. 

2833 

2834 ## Returns 

2835 

2836 - `Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern]`: The validated FIB pattern object. 

2837 

2838 ## Raises 

2839 

2840 - `KeyError`: If required settings are missing from the .yml file or if multiple pattern types are specified. 

2841 - `ValueError`: If the application file is unsupported or invalid for the specified pattern type. 

2842 """ 

2843 

2844 if yml_format.version >= 1.0: 2844 ↛ 2904line 2844 didn't jump to line 2904 because the condition on line 2844 was always true

2845 application_file = settings.get("application_file") 

2846 # determine type of pattern (only one allowed) 

2847 type_set_db = settings.get("type") 

2848 if type_set_db is None: 2848 ↛ 2849line 2848 didn't jump to line 2849 because the condition on line 2848 was never true

2849 raise KeyError( 

2850 f"Invalid .yml file, no 'type' settings sub-dictionary found in 'pattern' settings in 'fib' step_type for step '{step_name}'." 

2851 ) 

2852 rectangle_settings = type_set_db.get("rectangle") 

2853 regular_cross_section_settings = type_set_db.get("regular_cross_section") 

2854 cleaning_cross_section_settings = type_set_db.get("cleaning_cross_section") 

2855 selected_area_settings = type_set_db.get("selected_area") 

2856 

2857 pattern_settings = [ 

2858 rectangle_settings, 

2859 regular_cross_section_settings, 

2860 cleaning_cross_section_settings, 

2861 selected_area_settings, 

2862 ] 

2863 ( 

2864 rectangle_pattern, 

2865 regular_cross_section_pattern, 

2866 cleaning_cross_section_pattern, 

2867 selected_area_pattern, 

2868 ) = (None, None, None, None) 

2869 # pattern_names = [val.value for val in tbt.FIBPatternType] # remembers order 

2870 # assert pattern_names == [ 

2871 # "rectangle", 

2872 # "regular_cross_section", 

2873 # "cleaning_cross_section", 

2874 # "selected_area", 

2875 # ] 

2876 pattern_names = [ 

2877 "rectangle", 

2878 "regular_cross_section", 

2879 "cleaning_cross_section", 

2880 "selected_area", 

2881 ] 

2882 pattern_types = [ 

2883 rectangle_pattern, 

2884 regular_cross_section_pattern, 

2885 cleaning_cross_section_pattern, 

2886 selected_area_pattern, 

2887 ] 

2888 

2889 for type in range(len(pattern_settings)): 

2890 db = pattern_settings[type] 

2891 if db is None: 2891 ↛ 2892line 2891 didn't jump to line 2892 because the condition on line 2891 was never true

2892 pattern_types[type] = False 

2893 else: 

2894 pattern_types[type] = not ut.none_value_dictionary(db) 

2895 

2896 # one and only one type of pattern can have settings 

2897 if sum(pattern_types) != 1: 2897 ↛ 2898line 2897 didn't jump to line 2898 because the condition on line 2897 was never true

2898 raise KeyError( 2898 ↛ exit,   2898 ↛ exit2 missed branches: 1) line 2898 didn't jump to the function exit, 2) line 2898 didn't except from function 'validate_fib_pattern_settings' because the raise on line 2898 wasn't executed

2899 f"Invalid .yml file in 'fib' step_type for step '{step_name}'. Pattern settings for one and only one type are allowed. Please provide settings for only one of the supported pattern types: {[name for name in pattern_names]}. For unused pattern types, leave the type settings completely blank, enter 'null' for each parameter, or remove the unused subdictionary completely from the .yml file." 

2900 ) 

2901 pattern_type = tbt.FIBPatternType(pattern_names[pattern_types.index(True)]) 

2902 

2903 # check application file 

2904 valid_applications = application_files(microscope=microscope) 

2905 # TODO make this print out pretty 

2906 # display_applications = ",".join(valid_applications) 

2907 # display_applications = ut.tabular_list(data=valid_applications) 

2908 if application_file not in valid_applications: 2908 ↛ 2909line 2908 didn't jump to line 2909 because the condition on line 2908 was never true

2909 raise ValueError( 

2910 f"Unsupported FIB application file of '{application_file}' on step '{step_name}. Supported applications on this microscope are: \n{valid_applications}'" 

2911 ) 

2912 

2913 if pattern_type == tbt.FIBPatternType.RECTANGLE: 2913 ↛ 2956line 2913 didn't jump to line 2956 because the condition on line 2913 was always true

2914 validate_fib_box_settings( 

2915 settings=rectangle_settings, 

2916 yml_format=yml_format, 

2917 step_name=step_name, 

2918 pattern_type=pattern_type, 

2919 ) 

2920 

2921 pattern = tbt.FIBPattern( 

2922 application=application_file, 

2923 type=pattern_type, 

2924 geometry=tbt.FIBRectanglePattern( 

2925 center_um=tbt.Point( 

2926 x=rectangle_settings.get("center").get("x_um"), 

2927 y=rectangle_settings.get("center").get("y_um"), 

2928 ), 

2929 width_um=rectangle_settings.get("width_um"), 

2930 height_um=rectangle_settings.get("height_um"), 

2931 depth_um=rectangle_settings.get("depth_um"), 

2932 scan_direction=tbt.FIBPatternScanDirection( 

2933 rectangle_settings.get("scan_direction") 

2934 ), 

2935 scan_type=tbt.FIBPatternScanType(rectangle_settings.get("scan_type")), 

2936 ), 

2937 ) 

2938 # make sure application file is valid for this pattern type: 

2939 try: 

2940 geometry = pattern.geometry 

2941 microscope.patterning.set_default_application_file(pattern.application) 

2942 microscope.patterning.create_rectangle( 

2943 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

2944 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

2945 width=geometry.width_um * Conversions.UM_TO_M, 

2946 height=geometry.height_um * Conversions.UM_TO_M, 

2947 depth=geometry.depth_um * Conversions.UM_TO_M, 

2948 ) 

2949 microscope.patterning.clear_patterns() 

2950 except: 

2951 raise ValueError( 

2952 f'Invalid application file of "{pattern.application}" for Rectangle pattern type. Please select or create an appropriate application file.' 

2953 ) 

2954 

2955 return pattern 

2956 elif pattern_type == tbt.FIBPatternType.REGULAR_CROSS_SECTION: 

2957 validate_fib_box_settings( 

2958 settings=regular_cross_section_settings, 

2959 yml_format=yml_format, 

2960 step_name=step_name, 

2961 pattern_type=pattern_type, 

2962 ) 

2963 pattern = tbt.FIBPattern( 

2964 application=application_file, 

2965 type=pattern_type, 

2966 geometry=tbt.FIBRegularCrossSection( 

2967 center_um=tbt.Point( 

2968 x=regular_cross_section_settings.get("center").get("x_um"), 

2969 y=regular_cross_section_settings.get("center").get("y_um"), 

2970 ), 

2971 width_um=regular_cross_section_settings.get("width_um"), 

2972 height_um=regular_cross_section_settings.get("height_um"), 

2973 depth_um=regular_cross_section_settings.get("depth_um"), 

2974 scan_direction=tbt.FIBPatternScanDirection( 

2975 regular_cross_section_settings.get("scan_direction") 

2976 ), 

2977 scan_type=tbt.FIBPatternScanType( 

2978 regular_cross_section_settings.get("scan_type") 

2979 ), 

2980 ), 

2981 ) 

2982 # make sure application file is valid for this pattern type: 

2983 try: 

2984 geometry = pattern.geometry 

2985 microscope.patterning.set_default_application_file(pattern.application) 

2986 microscope.patterning.create_regular_cross_section( 

2987 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

2988 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

2989 width=geometry.width_um * Conversions.UM_TO_M, 

2990 height=geometry.height_um * Conversions.UM_TO_M, 

2991 depth=geometry.depth_um * Conversions.UM_TO_M, 

2992 ) 

2993 microscope.patterning.clear_patterns() 

2994 except: 

2995 raise ValueError( 

2996 f'Invalid application file of "{pattern.application}" for Regular Cross Section pattern type. Please select or create an appropriate application file.' 

2997 ) 

2998 return pattern 

2999 elif pattern_type == tbt.FIBPatternType.CLEANING_CROSS_SECTION: 

3000 validate_fib_box_settings( 

3001 settings=cleaning_cross_section_settings, 

3002 yml_format=yml_format, 

3003 step_name=step_name, 

3004 pattern_type=pattern_type, 

3005 ) 

3006 pattern = tbt.FIBPattern( 

3007 application=application_file, 

3008 type=pattern_type, 

3009 geometry=tbt.FIBCleaningCrossSection( 

3010 center_um=tbt.Point( 

3011 x=cleaning_cross_section_settings.get("center").get("x_um"), 

3012 y=cleaning_cross_section_settings.get("center").get("y_um"), 

3013 ), 

3014 width_um=cleaning_cross_section_settings.get("width_um"), 

3015 height_um=cleaning_cross_section_settings.get("height_um"), 

3016 depth_um=cleaning_cross_section_settings.get("depth_um"), 

3017 scan_direction=tbt.FIBPatternScanDirection( 

3018 cleaning_cross_section_settings.get("scan_direction") 

3019 ), 

3020 scan_type=tbt.FIBPatternScanType( 

3021 cleaning_cross_section_settings.get("scan_type") 

3022 ), 

3023 ), 

3024 ) 

3025 # make sure application file is valid for this pattern type: 

3026 try: 

3027 geometry = pattern.geometry 

3028 microscope.patterning.set_default_application_file(pattern.application) 

3029 microscope.patterning.create_cleaning_cross_section( 

3030 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

3031 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

3032 width=geometry.width_um * Conversions.UM_TO_M, 

3033 height=geometry.height_um * Conversions.UM_TO_M, 

3034 depth=geometry.depth_um * Conversions.UM_TO_M, 

3035 ) 

3036 microscope.patterning.clear_patterns() 

3037 except: 

3038 raise ValueError( 

3039 f'Invalid application file of "{pattern.application}" for Cleaning Cross Section pattern type. Please select or create an appropriate application file.' 

3040 ) 

3041 return pattern 

3042 elif pattern_type == tbt.FIBPatternType.SELECTED_AREA: 

3043 validate_fib_selected_area_settings( 

3044 settings=selected_area_settings, 

3045 yml_format=yml_format, 

3046 step_name=step_name, 

3047 pattern_type=pattern_type, 

3048 ) 

3049 pattern = tbt.FIBPattern( 

3050 application=application_file, 

3051 type=pattern_type, 

3052 geometry=tbt.FIBStreamPattern( 

3053 dwell_us=selected_area_settings.get("dwell_us"), 

3054 repeats=selected_area_settings.get("repeats"), 

3055 recipe=Path(selected_area_settings.get("recipe_file")), 

3056 mask=Path(selected_area_settings.get("mask_file")), 

3057 ), 

3058 ) 

3059 

3060 # make sure application file is valid for this pattern type: 

3061 try: 

3062 microscope.patterning.set_default_application_file(pattern.application) 

3063 microscope.patterning.create_rectangle( 

3064 center_x=0.0, 

3065 center_y=0.0, 

3066 width=10.0e-6, 

3067 height=10.0e-6, 

3068 depth=1.0e-6, 

3069 ) 

3070 microscope.patterning.clear_patterns() 

3071 except: 

3072 raise ValueError( 

3073 f'Invalid application file of "{pattern.application}" for Selected Area pattern. Please use an application file for Rectangle milling.' 

3074 ) 

3075 

3076 return pattern 

3077 else: 

3078 raise KeyError( 

3079 f"Invalid pattern type of {pattern_type}. Supported pattern types are: {[i.value for i in tbt.FIBPatternType]}" 

3080 ) 

3081 

3082 

3083def validate_fib_box_settings( 

3084 settings: dict, 

3085 yml_format: tbt.YMLFormatVersion, 

3086 step_name: str, 

3087 pattern_type: tbt.FIBPatternType, 

3088) -> bool: 

3089 """ 

3090 Perform schema checking for FIB box pattern setting dictionary. 

3091 

3092 This function validates the FIB box pattern settings dictionary based on the specified yml format. 

3093 

3094 ## Parameters 

3095 

3096 - `settings` (`dict`): The dictionary containing the FIB box pattern settings. 

3097 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

3098 - `step_name` (`str`): The name of the step in the .yml file. 

3099 - `pattern_type` (`tbt.FIBPatternType`): The type of the FIB pattern. 

3100 

3101 ## Returns 

3102 

3103 - `bool`: True if the settings are valid, False otherwise. 

3104 

3105 ## Raises 

3106 

3107 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

3108 """ 

3109 if yml_format.version >= 1.0: 3109 ↛ 3150line 3109 didn't jump to line 3150 because the condition on line 3109 was always true

3110 # flattens nested dictionary, adding "_" separator 

3111 flat_settings = ut._flatten(settings) 

3112 schema = Schema( 

3113 { 

3114 "center_x_um": And( 

3115 float, 

3116 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'x_um' parameter for pattern 'center' sub-dictionary must be a float. '{flat_settings['center_x_um']}' of type '{type(flat_settings['center_x_um'])}' was requested.", 

3117 ), 

3118 "center_y_um": And( 

3119 float, 

3120 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'y_um' parameter for pattern 'center' sub-dictionary must be a float. '{flat_settings['center_y_um']}' of type '{type(flat_settings['center_y_um'])}' was requested.", 

3121 ), 

3122 "width_um": And( 

3123 float, 

3124 lambda x: x > 0, 

3125 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'width_um' parameter must be a float. '{flat_settings['width_um']}' of type '{type(flat_settings['width_um'])}' was requested.", 

3126 ), 

3127 "height_um": And( 

3128 float, 

3129 lambda x: x > 0, 

3130 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'height_um' parameter must be a float. '{flat_settings['height_um']}' of type '{type(flat_settings['height_um'])}' was requested.", 

3131 ), 

3132 "depth_um": And( 

3133 float, 

3134 lambda x: x > 0, 

3135 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'depth_um' parameter must be a float. '{flat_settings['depth_um']}' of type '{type(flat_settings['depth_um'])}' was requested.", 

3136 ), 

3137 "scan_direction": And( 

3138 str, 

3139 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanDirection), 

3140 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'scan_direction' parameter must be a valid scan direction type. '{flat_settings['scan_direction']}' was requested but supported values are: {[i.value for i in tbt.FIBPatternScanDirection]}", 

3141 ), 

3142 "scan_type": And( 

3143 str, 

3144 lambda x: ut.valid_enum_entry(x, tbt.FIBPatternScanType), 

3145 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'scan_type' parameter must be a valid scan type type. '{flat_settings['scan_type']}' was requested but supported values are: {[i.value for i in tbt.FIBPatternScanType]}", 

3146 ), 

3147 }, 

3148 ignore_extra_keys=True, 

3149 ) 

3150 try: 

3151 schema.validate(flat_settings) 

3152 except UnboundLocalError: 

3153 raise ValueError( 

3154 f"Error. Unsupported yml version {yml_format.version} provided." 

3155 ) 

3156 return True 

3157 

3158 

3159def validate_fib_selected_area_settings( 

3160 settings: dict, 

3161 yml_format: tbt.YMLFormatVersion, 

3162 step_name: str, 

3163 pattern_type: tbt.FIBPatternType, 

3164) -> bool: 

3165 """ 

3166 Perform schema checking for FIB selected area pattern setting dictionary. 

3167 

3168 This function validates the FIB selected area pattern settings dictionary based on the specified yml format. 

3169 

3170 ## Parameters 

3171 

3172 - `settings` (`dict`): The dictionary containing the FIB selected area pattern settings. 

3173 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

3174 - `step_name` (`str`): The name of the step in the .yml file. 

3175 - `pattern_type` (`tbt.FIBPatternType`): The type of the FIB pattern. 

3176 

3177 ## Returns 

3178 

3179 - `bool`: True if the settings are valid, False otherwise. 

3180 

3181 ## Raises 

3182 

3183 - `ValueError`: If the yml version is unsupported or if the settings do not satisfy the specified schema. 

3184 """ 

3185 if yml_format.version >= 1.0: 

3186 schema = Schema( 

3187 { 

3188 "dwell_us": And( 

3189 float, 

3190 lambda x: x > 0, 

3191 # check all float error versions of modulus 

3192 Or( 

3193 lambda x: math.isclose( 

3194 x % Constants.stream_pattern_base_dwell_us, 

3195 0, 

3196 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3197 ), 

3198 lambda x: math.isclose( 

3199 x % Constants.stream_pattern_base_dwell_us, 

3200 Constants.stream_pattern_base_dwell_us, 

3201 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3202 ), 

3203 lambda x: math.isclose( 

3204 x % Constants.stream_pattern_base_dwell_us, 

3205 -Constants.stream_pattern_base_dwell_us, 

3206 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3207 ), 

3208 ), 

3209 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'dwell_us' parameter must be a positive float and an integer multiple of the base dwell time, {Constants.stream_pattern_base_dwell_us * Conversions.US_TO_NS} ns. '{settings['dwell_us']}' us of type '{type(settings['dwell_us'])}' was requested.", 

3210 ), 

3211 "repeats": And( 

3212 int, 

3213 lambda x: x > 0, 

3214 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'repeats' parameter must be a positive integer. '{settings['repeats']}' of type '{type(settings['repeats'])}' was requested.", 

3215 ), 

3216 "recipe_file": And( 

3217 str, 

3218 lambda x: Path(x).is_file(), 

3219 lambda x: Path(x).suffix == ".py", 

3220 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'recipe_file' parameter must be a valid file path string with the extension '.py' and must already exist. The recipe file '{settings['recipe_file']}' was requested.", 

3221 ), 

3222 "mask_file": And( 

3223 str, 

3224 lambda x: Path(x).suffix == ".tif", 

3225 error=f"In 'fib' step_type for step '{step_name}' and pattern type '{pattern_type.value}', 'mask' parameter must be a file path string with a file extension of '.tif'. '{settings['mask_file']}' of type '{type(settings['mask_file'])}' was requested.", 

3226 ), 

3227 }, 

3228 ignore_extra_keys=True, 

3229 ) 

3230 try: 

3231 schema.validate(settings) 

3232 except UnboundLocalError: 

3233 raise ValueError( 

3234 f"Error. Unsupported yml version {yml_format.version} provided." 

3235 ) 

3236 return True 

3237 

3238 

3239def step( 

3240 microscope: tbt.Microscope, 

3241 # slice_number: str, 

3242 step_name: str, 

3243 step_settings: dict, 

3244 general_settings: tbt.GeneralSettings, 

3245 yml_format: tbt.YMLFormatVersion, 

3246) -> tbt.Step: 

3247 """ 

3248 Create a step object for different step types, including validation. 

3249 

3250 This function creates a `Step` object for the specified step type and performs validation. 

3251 

3252 ## Parameters 

3253 

3254 - `microscope` (`tbt.Microscope`): The microscope object for which to create the step. 

3255 - `step_name` (`str`): The name of the step in the .yml file. 

3256 - `step_settings` (`dict`): The dictionary containing the step settings. 

3257 - `general_settings` (`tbt.GeneralSettings`): The general settings object. 

3258 - `yml_format` (`tbt.YMLFormatVersion`): The format specified by the version of the .yml file. 

3259 

3260 ## Returns 

3261 

3262 - `tbt.Step`: The step object. 

3263 

3264 ## Raises 

3265 

3266 - `NotImplementedError`: If the step type is unsupported. 

3267 - `KeyError`: If required settings are missing or invalid. 

3268 """ 

3269 

3270 # parsing settings 

3271 step_type_value = step_settings[yml_format.step_general_key][ 

3272 yml_format.step_type_key 

3273 ] 

3274 if not ut.valid_enum_entry(step_type_value, tbt.StepType): 

3275 raise NotImplementedError( 

3276 f"Unsupported step type of '{step_type_value}', for step name '{step_name}' supported types are: {[i.value for i in tbt.StepType]}." 

3277 ) 

3278 step_type = tbt.StepType(step_type_value) 

3279 

3280 step_number = step_settings[yml_format.step_general_key][yml_format.step_number_key] 

3281 if not isinstance(step_number, int) or (step_number < 1): 3281 ↛ 3282line 3281 didn't jump to line 3282 because the condition on line 3281 was never true

3282 raise KeyError( 

3283 f"Invalid step number of '{step_number}', for step name '{step_name}'. Must be a positive integer greater than 0." 

3284 ) 

3285 step_frequency = step_settings[yml_format.step_general_key][ 

3286 yml_format.step_frequency_key 

3287 ] 

3288 if not isinstance(step_frequency, int) or (step_frequency < 1): 3288 ↛ 3289line 3288 didn't jump to line 3289 because the condition on line 3288 was never true

3289 raise KeyError( 

3290 f"Invalid step frequency of '{step_frequency}', for step name '{step_name}'. Must be a positive integer greater than 0." 

3291 ) 

3292 stage_db = step_settings[yml_format.step_general_key][ 

3293 yml_format.step_stage_settings_key 

3294 ] 

3295 

3296 # check and validate stage 

3297 stage_settings = stage_position_settings( 

3298 microscope=microscope, 

3299 step_name=step_name, 

3300 general_settings=general_settings, 

3301 step_stage_settings=stage_db, 

3302 yml_format=yml_format, 

3303 ) 

3304 

3305 # TODO 

3306 # operation_settings, could use match statement in python >= 3.10 

3307 if step_type == tbt.StepType.EBSD: 

3308 operation_settings = ebsd( 

3309 microscope=microscope, 

3310 step_settings=step_settings, 

3311 step_name=step_name, 

3312 yml_format=yml_format, 

3313 ) 

3314 if step_type == tbt.StepType.EDS: 

3315 operation_settings = eds( 

3316 microscope=microscope, 

3317 step_settings=step_settings, 

3318 step_name=step_name, 

3319 yml_format=yml_format, 

3320 ) 

3321 if step_type == tbt.StepType.IMAGE: 

3322 operation_settings = image( 

3323 microscope=microscope, 

3324 step_settings=step_settings, 

3325 step_name=step_name, 

3326 yml_format=yml_format, 

3327 ) 

3328 if step_type == tbt.StepType.LASER: 

3329 operation_settings = laser( 

3330 microscope=microscope, 

3331 step_settings=step_settings, 

3332 step_name=step_name, 

3333 yml_format=yml_format, 

3334 ) 

3335 if step_type == tbt.StepType.CUSTOM: 

3336 operation_settings = custom( 

3337 microscope=microscope, 

3338 step_settings=step_settings, 

3339 step_name=step_name, 

3340 yml_format=yml_format, 

3341 ) 

3342 if step_type == tbt.StepType.FIB: 

3343 operation_settings = fib( 

3344 microscope=microscope, 

3345 step_settings=step_settings, 

3346 step_name=step_name, 

3347 yml_format=yml_format, 

3348 ) 

3349 

3350 step_object = tbt.Step( 

3351 type=step_type, 

3352 name=step_name, 

3353 number=step_number, 

3354 frequency=step_frequency, 

3355 stage=stage_settings, 

3356 operation_settings=operation_settings, 

3357 ) 

3358 

3359 return step_object