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

670 statements  

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

1#!/usr/bin/python3 

2""" 

3Factory Module 

4============== 

5 

6This module contains functions for creating and validating various settings and objects used in the microscope operations. The functions are organized to handle different step types, including EBSD, EDS, IMAGE, LASER, CUSTOM, and FIB. 

7 

8Functions 

9--------- 

10active_fib_applications(microscope: tbt.Microscope) -> list 

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

12 

13active_beam_with_settings(microscope: tbt.Microscope) -> tbt.Beam 

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

15 

16active_detector_settings(microscope: tbt.Microscope) -> tbt.Detector 

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

18 

19active_image_settings(microscope: tbt.Microscope) -> tbt.ImageSettings 

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

21 

22active_imaging_device(microscope: tbt.Microscope) -> tbt.Beam 

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

24 

25active_scan_settings(microscope: tbt.Microscope) -> tbt.Scan 

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

27 

28active_stage_position_settings(microscope: tbt.Microscope) -> tbt.StagePositionUser 

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

30 

31active_laser_state() -> tbt.LaserState 

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

33 

34active_laser_settings(microscope: tbt.Microscope) -> tbt.LaserSettings 

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

36 

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

38 Retrieve the available detector types on the current microscope. 

39 

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

41 Retrieve the available detector modes on the current microscope. 

42 

43beam_object_type(type: tbt.BeamType) -> tbt.Beam 

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

45 

46stage_limits(microscope: tbt.Microscope) -> tbt.StageLimits 

47 Retrieve the stage limits from the current microscope connection. 

48 

49beam_limits(selected_beam: property, beam_type: tbt.BeamType) -> tbt.BeamLimits 

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

51 

52general(general_db: dict, yml_format: tbt.YMLFormatVersion) -> tbt.GeneralSettings 

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

54 

55laser_box_pattern(settings: dict) -> tbt.LaserBoxPattern 

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

57 

58laser_line_pattern(settings: dict) -> tbt.LaserLinePattern 

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

60 

61laser(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.LaserSettings 

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

63 

64image(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.ImageSettings 

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

66 

67fib(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.FIBSettings 

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

69 

70enforce_beam_type(beam_type, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> bool 

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

72 

73ebsd(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.EBSDSettings 

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

75 

76eds(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.EDSSettings 

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

78 

79custom(microscope: tbt.Microscope, step_settings: dict, step_name: str, yml_format: tbt.YMLFormatVersion) -> tbt.CustomSettings 

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

81 

82scan_limits(selected_beam: property) -> tbt.ScanLimits 

83 Retrieve the scan settings limits for the selected beam. 

84 

85string_to_res(input: str) -> tbt.Resolution 

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

87 

88valid_string_resolution(string_resolution: str) -> bool 

89 Validate a string resolution. 

90 

91validate_auto_cb_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

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

93 

94validate_stage_position(microscope: tbt.Microscope, step_name: str, settings: dict, yml_format: tbt.YMLFormatVersion) -> bool 

95 Perform schema checking for stage position dictionary. 

96 

97validate_beam_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

98 Perform schema checking for beam setting dictionary. 

99 

100validate_detector_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

101 Perform schema checking for detector setting dictionary. 

102 

103validate_EBSD_EDS_settings(ebsd_oem: str, eds_oem: str) -> bool 

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

105 

106validate_general_settings(settings: dict, yml_format: tbt.YMLFormatVersion) -> bool 

107 Perform schema checking for general setting dictionary. 

108 

109validate_scan_settings(microscope: tbt.Microscope, beam_type: tbt.BeamType, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

110 Perform schema checking for scan setting dictionary. 

111 

112stage_position_settings(microscope: tbt.Microscope, step_name: str, general_settings: tbt.GeneralSettings, step_stage_settings: dict, yml_format: tbt.YMLFormatVersion) -> tbt.StageSettings 

113 Create a StagePositionUser object from settings, including validation. 

114 

115validate_pulse_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

116 Perform schema checking for pulse setting dictionary. 

117 

118validate_laser_optics_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

119 Perform schema checking for laser optics setting dictionary. 

120 

121validate_laser_box_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

122 Perform schema checking for laser box pattern setting dictionary. 

123 

124validate_laser_line_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

125 Perform schema checking for laser line pattern setting dictionary. 

126 

127validate_laser_mode_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> bool 

128 Perform schema checking for laser mode setting dictionary. 

129 

130validate_laser_pattern_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> tbt.LaserPatternType 

131 Perform schema checking for laser pattern setting dictionary. 

132 

133validate_fib_pattern_settings(microscope: tbt.Microscope, settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str) -> Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern] 

134 Perform schema checking for FIB pattern setting dictionary. 

135 

136validate_fib_box_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str, pattern_type: tbt.FIBPatternType) -> bool 

137 Perform schema checking for FIB box pattern setting dictionary. 

138 

139validate_fib_selected_area_settings(settings: dict, yml_format: tbt.YMLFormatVersion, step_name: str, pattern_type: tbt.FIBPatternType) -> bool 

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

141 

142step(microscope: tbt.Microscope, step_name: str, step_settings: dict, general_settings: tbt.GeneralSettings, yml_format: tbt.YMLFormatVersion) -> tbt.Step 

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

144""" 

145 

146## python standard libraries 

147from pathlib import Path 

148from typing import List, Union 

149import warnings 

150from functools import singledispatch 

151import math 

152 

153 

154# 3rd party libraries 

155from schema import And, Or, Schema 

156 

157# Local 

158import pytribeam.insertable_devices as devices 

159import pytribeam.image as img 

160import pytribeam.utilities as ut 

161import pytribeam.stage as stage 

162from pytribeam.constants import Conversions, Constants 

163from pytribeam.fib import application_files 

164 

165try: 

166 import pytribeam.laser as fs_laser 

167except: 

168 pass 

169import pytribeam.types as tbt 

170 

171 

172def active_fib_applications( 

173 microscope: tbt.Microscope, 

174) -> list: 

175 """ 

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

177 

178 Parameters 

179 ---------- 

180 microscope : tbt.Microscope 

181 The microscope object from which to retrieve the application files. 

182 

183 Returns 

184 ------- 

185 list 

186 A list of active FIB patterning application files. 

187 """ 

188 return microscope.patterning.list_all_application_files() 

189 

190 

191def active_beam_with_settings( 

192 microscope: tbt.Microscope, 

193) -> tbt.Beam: 

194 """ 

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

196 

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

198 

199 Parameters 

200 ---------- 

201 microscope : tbt.Microscope 

202 The microscope object from which to retrieve the active beam and its settings. 

203 

204 Returns 

205 ------- 

206 tbt.Beam 

207 The active beam object with its settings. 

208 """ 

209 selected_beam = active_imaging_device(microscope=microscope) 

210 beam = ut.beam_type(selected_beam, microscope) 

211 

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

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

214 

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

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

217 

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

219 

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

221 

222 active_settings = tbt.BeamSettings( 

223 voltage_kv=voltage_kv, 

224 current_na=current_na, 

225 hfw_mm=hfw_mm, 

226 working_dist_mm=working_dist_mm, 

227 voltage_tol_kv=voltage_tol_kv, 

228 current_tol_na=current_tol_na, 

229 ) 

230 

231 return type(selected_beam)(settings=active_settings) 

232 

233 

234def active_detector_settings( 

235 microscope: tbt.Microscope, 

236) -> tbt.Detector: 

237 """ 

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

239 

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

241 

242 Parameters 

243 ---------- 

244 microscope : tbt.Microscope 

245 The microscope object from which to retrieve the active detector settings. 

246 

247 Returns 

248 ------- 

249 tbt.Detector 

250 The active detector object with its settings. 

251 """ 

252 

253 detector_type = microscope.detector.type.value 

254 detector_mode = microscope.detector.mode.value 

255 brightness = microscope.detector.brightness.value 

256 contrast = microscope.detector.contrast.value 

257 auto_cb_settings = tbt.ScanArea( 

258 left=None, 

259 top=None, 

260 width=None, 

261 height=None, 

262 ) 

263 custom_settings = None 

264 

265 active_detector = tbt.Detector( 

266 type=detector_type, 

267 mode=detector_mode, 

268 brightness=brightness, 

269 contrast=contrast, 

270 auto_cb_settings=auto_cb_settings, 

271 custom_settings=custom_settings, 

272 ) 

273 

274 return active_detector 

275 

276 

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

278 """ 

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

280 

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

282 

283 Parameters 

284 ---------- 

285 microscope : tbt.Microscope 

286 The microscope object from which to retrieve the active image settings. 

287 

288 Returns 

289 ------- 

290 tbt.ImageSettings 

291 The active image settings object. 

292 """ 

293 beam = active_beam_with_settings(microscope=microscope) 

294 detector = active_detector_settings(microscope=microscope) 

295 scan = active_scan_settings(microscope=microscope) 

296 bit_depth = Constants.default_color_depth 

297 

298 active_image_settings = tbt.ImageSettings( 

299 microscope=microscope, 

300 beam=beam, 

301 detector=detector, 

302 scan=scan, 

303 bit_depth=bit_depth, 

304 ) 

305 

306 return active_image_settings 

307 

308 

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

310 """ 

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

312 

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

314 

315 Parameters 

316 ---------- 

317 microscope : tbt.Microscope 

318 The microscope object from which to determine the active imaging device. 

319 

320 Returns 

321 ------- 

322 tbt.Beam 

323 The active beam object with null beam settings. 

324 

325 Raises 

326 ------ 

327 ValueError 

328 If the currently selected device is neither an electron beam nor an ion beam. 

329 """ 

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

331 if curr_device == tbt.Device.ELECTRON_BEAM: 

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

333 settings=tbt.BeamSettings() 

334 ) 

335 elif curr_device == tbt.Device.ION_BEAM: 

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

337 settings=tbt.BeamSettings() 

338 ) 

339 else: 

340 raise ValueError( 

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

342 ) 

343 return selected_beam 

344 

345 

346def active_scan_settings( 

347 microscope: tbt.Microscope, 

348) -> tbt.Scan: 

349 """ 

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

351 

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

353 

354 Parameters 

355 ---------- 

356 microscope : tbt.Microscope 

357 The microscope object from which to retrieve the active scan settings. 

358 

359 Returns 

360 ------- 

361 tbt.Scan 

362 The active scan object with its settings. 

363 """ 

364 selected_beam = active_imaging_device(microscope=microscope) 

365 beam = ut.beam_type(selected_beam, microscope) 

366 

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

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

369 current_res = beam.scanning.resolution.value 

370 res = string_to_res(current_res) 

371 resolution = tbt.PresetResolution(res) 

372 

373 active_scan = tbt.Scan( 

374 rotation_deg=rotation_deg, 

375 dwell_time_us=dwell_time_us, 

376 resolution=resolution, 

377 # mode=mode, 

378 ) 

379 

380 return active_scan 

381 

382 

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

384 """ 

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

386 

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

388 

389 Parameters 

390 ---------- 

391 microscope : tbt.Microscope 

392 The microscope object from which to retrieve the current stage position. 

393 

394 Returns 

395 ------- 

396 tbt.StagePositionUser 

397 The current stage position in user units [mm, deg]. 

398 """ 

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

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

401 direct_encoder_pos = microscope.specimen.stage.current_position 

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

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

404 coord_system_str = direct_encoder_pos.coordinate_system 

405 

406 encoder_pos = tbt.StagePositionEncoder( 

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

408 ) 

409 user_pos = stage.encoder_to_user_position(encoder_pos) 

410 

411 # ensure r-axis is kept in axis limit 

412 # ------------------------------------------------ 

413 # THIS IS NOT NEEDED, AUTOSCRIPT DOES THIS ALREADY 

414 # ------------------------------------------------ 

415 # if not ut.in_interval( 

416 # val=user_pos.r_deg, 

417 # limit=Constants.rotation_axis_limit_deg, 

418 # type=tbt.IntervalType.RIGHT_OPEN, 

419 # ): 

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

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

422 # new_r_deg = user_pos.r_deg - 360.0 

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

424 # new_r_deg = user_pos.r_deg + 360.0 

425 # new_r_deg = round(new_r_deg, 6) 

426 # user_pos = tbt.StagePositionUser( 

427 # x_mm=user_pos.x_mm, 

428 # y_mm=user_pos.y_mm, 

429 # z_mm=user_pos.z_mm, 

430 # r_deg=new_r_deg, 

431 # t_deg=user_pos.t_deg, 

432 # ) 

433 

434 return user_pos 

435 

436 

437def active_laser_state() -> tbt.LaserState: 

438 """ 

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

440 

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

442 

443 Returns 

444 ------- 

445 tbt.LaserState 

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

447 

448 Raises 

449 ------ 

450 KeyError 

451 If an unsupported LaserPatternType is encountered. 

452 """ 

453 vals = fs_laser.tfs_laser.Laser_ReadValues() 

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

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

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

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

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

459 vals["expected_pattern_duration_s"] = ( 

460 fs_laser.tfs_laser.Patterning_GetExpectedDuration() 

461 ) 

462 

463 pattern_db = vals["pattern"] 

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

465 if not ut.valid_enum_entry(pattern_type, tbt.LaserPatternType): 

466 raise KeyError( 

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

468 ) 

469 pattern_type = tbt.LaserPatternType(pattern_type) 

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

471 if mode == tbt.LaserPatternMode.COARSE: 

472 pixel_dwell_ms = pattern_db["dwellTime"] 

473 pulses_per_pixel = None 

474 elif mode == tbt.LaserPatternMode.FINE: 

475 pixel_dwell_ms = None 

476 pulses_per_pixel = pattern_db["pulsesPerPixel"] 

477 rotation_deg = pattern_db["patternRotation_deg"] 

478 

479 if pattern_type == tbt.LaserPatternType.BOX: 

480 geometry = tbt.LaserBoxPattern( 

481 passes=pattern_db["passes"], 

482 size_x_um=pattern_db["xSize_um"], 

483 size_y_um=pattern_db["ySize_um"], 

484 pitch_x_um=pattern_db["xPitch_um"], 

485 pitch_y_um=pattern_db["yPitch_um"], 

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

487 coordinate_ref=tbt.CoordinateReference( 

488 pattern_db["coordReference"].lower() 

489 ), 

490 ) 

491 if pattern_type == tbt.LaserPatternType.LINE: 

492 geometry = tbt.LaserLinePattern( 

493 passes=pattern_db["passes"], 

494 size_um=pattern_db["xSize_um"], 

495 pitch_um=pattern_db["xPitch_um"], 

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

497 ) 

498 

499 pattern = tbt.LaserPattern( 

500 mode=mode, 

501 rotation_deg=rotation_deg, 

502 geometry=geometry, 

503 pulses_per_pixel=pulses_per_pixel, 

504 pixel_dwell_ms=pixel_dwell_ms, 

505 ) 

506 

507 state = tbt.LaserState( 

508 wavelength_nm=vals["wavelength_nm"], 

509 frequency_khz=vals["frequency_kHz"], 

510 pulse_divider=vals["pulse_divider"], 

511 pulse_energy_uj=vals["pulse_energy_uJ"], 

512 objective_position_mm=vals["objective_position_mm"], 

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

514 pattern=pattern, 

515 expected_pattern_duration_s=vals["expected_pattern_duration_s"], 

516 ) 

517 

518 return state 

519 

520 

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

522 """ 

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

524 

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

526 

527 Parameters 

528 ---------- 

529 microscope : tbt.Microscope 

530 The microscope object from which to retrieve the active laser settings. 

531 

532 Returns 

533 ------- 

534 tbt.LaserSettings 

535 The active laser settings object. 

536 """ 

537 state = active_laser_state() 

538 

539 settings = tbt.LaserSettings( 

540 microscope=microscope, 

541 pulse=tbt.LaserPulse( 

542 wavelength_nm=state.wavelength_nm, 

543 divider=state.pulse_divider, 

544 energy_uj=state.pulse_energy_uj, 

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

546 ), 

547 objective_position_mm=state.objective_position_mm, 

548 beam_shift_um=state.beam_shift_um, 

549 pattern=state.pattern, 

550 ) 

551 

552 return settings 

553 

554 

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

556 """ 

557 Retrieve the available detector types on the current microscope. 

558 

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

560 

561 Parameters 

562 ---------- 

563 microscope : tbt.Microscope 

564 The microscope object from which to retrieve the available detector types. 

565 

566 Returns 

567 ------- 

568 List[str] 

569 A list of available detector types. 

570 """ 

571 detectors = microscope.detector.type.available_values 

572 return detectors 

573 

574 

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

576 """ 

577 Retrieve the available detector modes on the current microscope. 

578 

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

580 

581 Parameters 

582 ---------- 

583 microscope : tbt.Microscope 

584 The microscope object from which to retrieve the available detector modes. 

585 

586 Returns 

587 ------- 

588 List[str] 

589 A list of available detector modes. 

590 """ 

591 modes = microscope.detector.mode.available_values 

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

593 return modes 

594 

595 

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

597 """ 

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

599 

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

601 

602 Parameters 

603 ---------- 

604 type : tbt.BeamType 

605 The type of the beam (electron or ion). 

606 

607 Returns 

608 ------- 

609 tbt.Beam 

610 The corresponding beam object type. 

611 

612 Raises 

613 ------ 

614 NotImplementedError 

615 If the provided beam type is unsupported. 

616 """ 

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

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

619 if type.value == "electron": 

620 return tbt.ElectronBeam 

621 if type.value == "ion": 

622 return tbt.IonBeam 

623 

624 

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

626 """ 

627 Retrieve the stage limits from the current microscope connection. 

628 

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

630 

631 Parameters 

632 ---------- 

633 microscope : tbt.Microscope 

634 The microscope object from which to retrieve the stage limits. 

635 

636 Returns 

637 ------- 

638 tbt.StageLimits 

639 The stage limits for the X, Y, Z, R, and T axes in user units (mm and degrees). 

640 """ 

641 stage.coordinate_system(microscope=microscope) 

642 # x position 

643 min_x_mm = ( 

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

645 * Conversions.M_TO_MM 

646 ) 

647 max_x_mm = ( 

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

649 * Conversions.M_TO_MM 

650 ) 

651 # y position 

652 min_y_mm = ( 

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

654 * Conversions.M_TO_MM 

655 ) 

656 max_y_mm = ( 

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

658 * Conversions.M_TO_MM 

659 ) 

660 # z position 

661 min_z_mm = ( 

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

663 * Conversions.M_TO_MM 

664 ) 

665 max_z_mm = ( 

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

667 * Conversions.M_TO_MM 

668 ) 

669 # r position 

670 min_r_deg = Constants.rotation_axis_limit_deg.min 

671 max_r_deg = Constants.rotation_axis_limit_deg.max 

672 # t position 

673 min_t_deg = ( 

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

675 * Conversions.RAD_TO_DEG 

676 ) 

677 max_t_deg = ( 

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

679 * Conversions.RAD_TO_DEG 

680 ) 

681 

682 return tbt.StageLimits( 

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

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

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

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

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

688 ) 

689 

690 

691def beam_limits( 

692 selected_beam: property, 

693 beam_type: tbt.BeamType, 

694) -> tbt.BeamLimits: 

695 """ 

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

697 

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

699 

700 Parameters 

701 ---------- 

702 selected_beam : property 

703 The selected beam property from which to retrieve the limits. 

704 beam_type : tbt.BeamType 

705 The type of the beam (electron or ion). 

706 

707 Returns 

708 ------- 

709 tbt.BeamLimits 

710 The beam limits for voltage, current, HFW, and working distance in user units (kV, nA, mm). 

711 

712 Raises 

713 ------ 

714 ValueError 

715 If the beam type is unsupported. 

716 """ 

717 # voltage range 

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

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

720 

721 # current range 

722 if beam_type == tbt.BeamType.ELECTRON: 

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

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

725 if beam_type == tbt.BeamType.ION: 

726 available_currents = selected_beam.beam_current.available_values 

727 min_na = min(available_currents) * Conversions.A_TO_NA 

728 max_na = max(available_currents) * Conversions.A_TO_NA 

729 

730 # hfw range 

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

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

733 

734 # working_dist range 

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

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

737 

738 return tbt.BeamLimits( 

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

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

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

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

743 ) 

744 

745 

746def general( 

747 general_db: dict, 

748 yml_format: tbt.YMLFormatVersion, 

749) -> tbt.GeneralSettings: 

750 """ 

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

752 

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

754 

755 Parameters 

756 ---------- 

757 general_db : dict 

758 The general settings dictionary from the .yml file. 

759 yml_format : tbt.YMLFormatVersion 

760 The format specified by the version of the .yml file. 

761 

762 Returns 

763 ------- 

764 tbt.GeneralSettings 

765 The general settings object. 

766 

767 Raises 

768 ------ 

769 NotImplementedError 

770 If the provided yml format is unsupported. 

771 """ 

772 

773 if not yml_format in tbt.YMLFormatVersion: 

774 raise NotImplementedError( 

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

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

777 ) 

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

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

780 

781 validate_general_settings( 

782 settings=general_db, 

783 yml_format=yml_format, 

784 ) 

785 

786 if yml_format.version >= 1.0: 

787 # slice thickness 

788 slice_thickness_um = general_db["slice_thickness_um"] 

789 # max slice number 

790 max_slice_number = general_db["max_slice_num"] 

791 # pre tilt 

792 pre_tilt_deg = general_db["pre_tilt_deg"] 

793 # sectioning axis 

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

795 # stage tolerance 

796 stage_tolerance = tbt.StageTolerance( 

797 translational_um=general_db["stage_translational_tol_um"], 

798 angular_deg=general_db["stage_angular_tol_deg"], 

799 ) 

800 # connection 

801 connection = tbt.MicroscopeConnection( 

802 general_db["connection_host"], general_db["connection_port"] 

803 ) 

804 # EBSD OEM 

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

806 # EDS OEM 

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

808 # exp dir 

809 exp_dir = general_db["exp_dir"] 

810 # h5 log name 

811 h5_log_name = general_db["h5_log_name"] 

812 # remove log file extension if the user provided it 

813 log_extension = Constants.logfile_extension 

814 if h5_log_name.endswith(log_extension): 

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

816 # step count 

817 step_count = general_db["step_count"] 

818 yml_version = 1.0 

819 

820 general_settings = tbt.GeneralSettings( 

821 yml_version=yml_version, 

822 slice_thickness_um=slice_thickness_um, 

823 max_slice_number=max_slice_number, 

824 pre_tilt_deg=pre_tilt_deg, 

825 sectioning_axis=sectioning_axis, 

826 stage_tolerance=stage_tolerance, 

827 connection=connection, 

828 EBSD_OEM=ebsd_oem, 

829 EDS_OEM=eds_oem, 

830 exp_dir=Path(exp_dir), 

831 h5_log_name=h5_log_name, 

832 step_count=step_count, 

833 ) 

834 

835 return general_settings 

836 

837 

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

839 """ 

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

841 

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

843 

844 Parameters 

845 ---------- 

846 settings : dict 

847 The dictionary containing laser box pattern settings. 

848 

849 Returns 

850 ------- 

851 tbt.LaserBoxPattern 

852 The laser box pattern object. 

853 """ 

854 return tbt.LaserBoxPattern( 

855 passes=settings["passes"], 

856 size_x_um=settings["size_x_um"], 

857 size_y_um=settings["size_y_um"], 

858 pitch_x_um=settings["pitch_x_um"], 

859 pitch_y_um=settings["pitch_y_um"], 

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

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

862 ) 

863 

864 

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

866 """ 

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

868 

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

870 

871 Parameters 

872 ---------- 

873 settings : dict 

874 The dictionary containing laser line pattern settings. 

875 

876 Returns 

877 ------- 

878 tbt.LaserLinePattern 

879 The laser line pattern object. 

880 """ 

881 return tbt.LaserLinePattern( 

882 passes=settings["passes"], 

883 size_um=settings["size_um"], 

884 pitch_um=settings["pitch_um"], 

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

886 ) 

887 

888 

889def laser( 

890 microscope: tbt.Microscope, 

891 step_settings: dict, 

892 step_name: str, 

893 yml_format: tbt.YMLFormatVersion, 

894) -> tbt.LaserSettings: 

895 """ 

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

897 

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

899 

900 Parameters 

901 ---------- 

902 microscope : tbt.Microscope 

903 The microscope object for which to set the laser settings. 

904 step_settings : dict 

905 The dictionary containing the laser step settings from the .yml file. 

906 step_name : str 

907 The name of the step in the .yml file. 

908 yml_format : tbt.YMLFormatVersion 

909 The format specified by the version of the .yml file. 

910 

911 Returns 

912 ------- 

913 tbt.LaserSettings 

914 The laser settings object. 

915 

916 Raises 

917 ------ 

918 KeyError 

919 If required settings are missing from the .yml file. 

920 """ 

921 if yml_format.version >= 1.0: 

922 # pulse settings 

923 pulse_set_db = step_settings.get("pulse") 

924 if pulse_set_db is None: 

925 raise KeyError( 

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

927 ) 

928 # laser optics settings 

929 optics_set_db = { 

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

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

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

933 } 

934 # pattern settings 

935 pattern_set_db = step_settings.get("pattern") 

936 if pattern_set_db is None: 

937 raise KeyError( 

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

939 ) 

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

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

942 

943 validate_pulse_settings( 

944 settings=pulse_set_db, 

945 yml_format=yml_format, 

946 step_name=step_name, 

947 ) 

948 pulse = tbt.LaserPulse( 

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

950 divider=pulse_set_db["divider"], 

951 energy_uj=pulse_set_db["energy_uj"], 

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

953 ) 

954 

955 validate_laser_optics_settings( 

956 settings=optics_set_db, 

957 yml_format=yml_format, 

958 step_name=step_name, 

959 ) 

960 

961 pattern_type = validate_laser_pattern_settings( 

962 settings=pattern_set_db, 

963 yml_format=yml_format, 

964 step_name=step_name, 

965 ) 

966 if pattern_type == tbt.LaserPatternType.BOX: 

967 geometry = laser_box_pattern(box_pattern_db) 

968 if pattern_type == tbt.LaserPatternType.LINE: 

969 geometry = laser_line_pattern(line_pattern_db) 

970 pattern = tbt.LaserPattern( 

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

972 rotation_deg=pattern_set_db["rotation_deg"], 

973 pulses_per_pixel=pattern_set_db["pulses_per_pixel"], 

974 pixel_dwell_ms=pattern_set_db["pixel_dwell_ms"], 

975 geometry=geometry, 

976 ) 

977 

978 laser_settings = tbt.LaserSettings( 

979 microscope=microscope, 

980 pulse=pulse, 

981 objective_position_mm=optics_set_db["objective_position_mm"], 

982 beam_shift_um=tbt.Point( 

983 x=optics_set_db["beam_shift_um_x"], 

984 y=optics_set_db["beam_shift_um_y"], 

985 ), 

986 pattern=pattern, 

987 ) 

988 

989 return laser_settings 

990 

991 

992def image( 

993 microscope: tbt.Microscope, 

994 step_settings: dict, 

995 step_name: str, 

996 yml_format: tbt.YMLFormatVersion, 

997) -> tbt.ImageSettings: 

998 """ 

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

1000 

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

1002 

1003 Parameters 

1004 ---------- 

1005 microscope : tbt.Microscope 

1006 The microscope object for which to set the image settings. 

1007 step_settings : dict 

1008 The dictionary containing the image step settings from the .yml file. 

1009 step_name : str 

1010 The name of the step in the .yml file. 

1011 yml_format : tbt.YMLFormatVersion 

1012 The format specified by the version of the .yml file. 

1013 

1014 Returns 

1015 ------- 

1016 tbt.ImageSettings 

1017 The image settings object. 

1018 

1019 Raises 

1020 ------ 

1021 KeyError 

1022 If required settings are missing from the .yml file. 

1023 NotImplementedError 

1024 If the provided beam type is unsupported. 

1025 ValueError 

1026 If invalid scan rotation is requested with dynamic focus or tilt correction, or if the bit depth is unsupported. 

1027 """ 

1028 if yml_format.version >= 1.0: 

1029 step_general = step_settings.get(yml_format.step_general_key) 

1030 if step_general is None: 

1031 raise KeyError( 

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

1033 ) 

1034 step_type = step_general.get(yml_format.step_type_key) 

1035 if step_type is None: 

1036 raise KeyError( 

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

1038 ) 

1039 # beam settings 

1040 beam_set_db = step_settings.get("beam") 

1041 if beam_set_db is None: 

1042 raise KeyError( 

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

1044 ) 

1045 beam_type_value = beam_set_db.get("type") 

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

1047 raise NotImplementedError( 

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

1049 ) 

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

1051 

1052 # detector settings 

1053 detector_set_db = step_settings.get("detector") 

1054 if detector_set_db is None: 

1055 raise KeyError( 

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

1057 ) 

1058 auto_cb_set_db = detector_set_db.get("auto_cb") 

1059 

1060 # scan settings 

1061 scan_set_db = step_settings.get("scan") 

1062 if scan_set_db is None: 

1063 raise KeyError( 

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

1065 ) 

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

1067 

1068 # misc settings 

1069 bit_depth = step_settings.get("bit_depth") 

1070 

1071 # TODO incorporate tile settings 

1072 if yml_format.version >= 1.1: 

1073 # tile settings 

1074 tile_set_db = step_settings.get("tile_settings") 

1075 

1076 validate_beam_settings( 

1077 microscope=microscope, 

1078 beam_type=beam_type, 

1079 settings=beam_set_db, 

1080 yml_format=yml_format, 

1081 step_name=step_name, 

1082 ) 

1083 beam_settings = tbt.BeamSettings( 

1084 voltage_kv=beam_set_db["voltage_kv"], 

1085 voltage_tol_kv=beam_set_db["voltage_tol_kv"], 

1086 current_na=beam_set_db["current_na"], 

1087 current_tol_na=beam_set_db["current_tol_na"], 

1088 hfw_mm=beam_set_db["hfw_mm"], 

1089 working_dist_mm=beam_set_db["working_dist_mm"], 

1090 dynamic_focus=beam_set_db["dynamic_focus"], 

1091 tilt_correction=beam_set_db["tilt_correction"], 

1092 ) 

1093 

1094 validate_auto_cb_settings( 

1095 settings=auto_cb_set_db, 

1096 yml_format=yml_format, 

1097 step_name=step_name, 

1098 ) 

1099 auto_cb_settings = tbt.ScanArea( 

1100 left=auto_cb_set_db["left"], 

1101 top=auto_cb_set_db["top"], 

1102 width=auto_cb_set_db["width"], 

1103 height=auto_cb_set_db["height"], 

1104 ) 

1105 

1106 validate_detector_settings( 

1107 microscope=microscope, 

1108 beam_type=beam_type, 

1109 settings=detector_set_db, 

1110 yml_format=yml_format, 

1111 step_name=step_name, 

1112 ) 

1113 detector_settings = tbt.Detector( 

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

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

1116 brightness=detector_set_db["brightness"], 

1117 contrast=detector_set_db["contrast"], 

1118 auto_cb_settings=auto_cb_settings, 

1119 # custom_settings=None, 

1120 ) 

1121 

1122 validate_scan_settings( 

1123 microscope=microscope, 

1124 beam_type=beam_type, 

1125 settings=scan_set_db, 

1126 yml_format=yml_format, 

1127 step_name=step_name, 

1128 ) 

1129 

1130 # cast resolution to preset if applicable 

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

1132 scan_res = tbt.PresetResolution(scan_res) 

1133 

1134 scan_settings = tbt.Scan( 

1135 rotation_deg=scan_set_db["rotation_deg"], 

1136 dwell_time_us=scan_set_db["dwell_time_us"], 

1137 resolution=scan_res, 

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

1139 ) 

1140 

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

1142 if beam_settings.dynamic_focus or beam_settings.tilt_correction: 

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

1144 raise ValueError( 

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

1146 ) 

1147 

1148 # validate bit_depth 

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

1150 valid_bit_depths = [i.value for i in tbt.ColorDepth] 

1151 raise ValueError( 

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

1153 ) 

1154 

1155 image_settings = tbt.ImageSettings( 

1156 microscope=microscope, 

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

1158 detector=detector_settings, 

1159 scan=scan_settings, 

1160 bit_depth=bit_depth, 

1161 ) 

1162 

1163 return image_settings 

1164 

1165 

1166def fib( 

1167 microscope: tbt.Microscope, 

1168 step_settings: dict, 

1169 step_name: str, 

1170 yml_format: tbt.YMLFormatVersion, 

1171) -> tbt.FIBSettings: 

1172 """ 

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

1174 

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

1176 

1177 Parameters 

1178 ---------- 

1179 microscope : tbt.Microscope 

1180 The microscope object for which to set the FIB settings. 

1181 step_settings : dict 

1182 The dictionary containing the FIB step settings from the .yml file. 

1183 step_name : str 

1184 The name of the step in the .yml file. 

1185 yml_format : tbt.YMLFormatVersion 

1186 The format specified by the version of the .yml file. 

1187 

1188 Returns 

1189 ------- 

1190 tbt.FIBSettings 

1191 The FIB settings object. 

1192 

1193 Raises 

1194 ------ 

1195 KeyError 

1196 If required settings are missing from the .yml file. 

1197 ValueError 

1198 If invalid beam type is requested. 

1199 """ 

1200 ## create image_step_settings from this 

1201 if yml_format.version >= 1.0: 

1202 image_step_settings = step_settings.get("image") 

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

1204 

1205 mill_step_settings = step_settings.get("mill") 

1206 mill_beam_db = mill_step_settings.get("beam") 

1207 mill_pattern_db = mill_step_settings.get("pattern") 

1208 

1209 # ensure image is with an ion beam 

1210 enforce_beam_type( 

1211 tbt.IonBeam(settings=None), 

1212 step_settings=image_step_settings, 

1213 step_name=step_name, 

1214 yml_format=yml_format, 

1215 ) 

1216 image_settings = image( 

1217 microscope=microscope, 

1218 step_settings=image_step_settings, 

1219 step_name=step_name, 

1220 yml_format=yml_format, 

1221 ) 

1222 

1223 ## fib mill settings 

1224 # ensure milling is with an ion beam 

1225 enforce_beam_type( 

1226 tbt.IonBeam(settings=None), 

1227 step_settings=mill_step_settings, 

1228 step_name=step_name, 

1229 yml_format=yml_format, 

1230 ) 

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

1232 # mill beam 

1233 validate_beam_settings( 

1234 microscope=microscope, 

1235 beam_type=beam_type, 

1236 settings=mill_beam_db, 

1237 yml_format=yml_format, 

1238 step_name=step_name, 

1239 ) 

1240 mill_beam = tbt.IonBeam( 

1241 settings=tbt.BeamSettings( 

1242 voltage_kv=mill_beam_db["voltage_kv"], 

1243 voltage_tol_kv=mill_beam_db["voltage_tol_kv"], 

1244 current_na=mill_beam_db["current_na"], 

1245 current_tol_na=mill_beam_db["current_tol_na"], 

1246 hfw_mm=mill_beam_db["hfw_mm"], 

1247 working_dist_mm=mill_beam_db["working_dist_mm"], 

1248 dynamic_focus=mill_beam_db["dynamic_focus"], 

1249 tilt_correction=mill_beam_db["tilt_correction"], 

1250 ) 

1251 ) 

1252 

1253 # fib pattern settings 

1254 pattern = validate_fib_pattern_settings( 

1255 microscope=microscope, 

1256 settings=mill_pattern_db, 

1257 yml_format=yml_format, 

1258 step_name=step_name, 

1259 ) 

1260 

1261 fib_settings = tbt.FIBSettings( 

1262 microscope=microscope, 

1263 image=image_settings, 

1264 mill_beam=mill_beam, 

1265 pattern=pattern, 

1266 ) 

1267 return fib_settings 

1268 

1269 

1270@singledispatch 

1271def enforce_beam_type( 

1272 beam_type, 

1273 step_settings: dict, 

1274 step_name: str, 

1275 yml_format: tbt.YMLFormatVersion, 

1276) -> bool: 

1277 """ 

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

1279 

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

1281 

1282 Parameters 

1283 ---------- 

1284 beam_type : Any 

1285 The beam type to enforce. 

1286 step_settings : dict 

1287 The dictionary containing the step settings. 

1288 step_name : str 

1289 The name of the step in the .yml file. 

1290 yml_format : tbt.YMLFormatVersion 

1291 The format specified by the version of the .yml file. 

1292 

1293 Returns 

1294 ------- 

1295 bool 

1296 True if the beam type is enforced successfully. 

1297 

1298 Raises 

1299 ------ 

1300 NotImplementedError 

1301 If no handler is available for the provided type. 

1302 """ 

1303 _ = beam_type 

1304 __ = step_settings 

1305 ___ = step_name 

1306 ____ = yml_format 

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

1308 

1309 

1310@enforce_beam_type.register 

1311def _( 

1312 beam_type: tbt.ElectronBeam, 

1313 step_settings: dict, 

1314 step_name: str, 

1315 yml_format: tbt.YMLFormatVersion, 

1316) -> bool: 

1317 """ 

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

1319 

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

1321 

1322 Parameters 

1323 ---------- 

1324 beam_type : tbt.ElectronBeam 

1325 The electron beam type to enforce. 

1326 step_settings : dict 

1327 The dictionary containing the step settings. 

1328 step_name : str 

1329 The name of the step in the .yml file. 

1330 yml_format : tbt.YMLFormatVersion 

1331 The format specified by the version of the .yml file. 

1332 

1333 Returns 

1334 ------- 

1335 bool 

1336 True if the electron beam type is enforced successfully. 

1337 

1338 Raises 

1339 ------ 

1340 KeyError 

1341 If the 'beam' settings are missing from the .yml file. 

1342 NotImplementedError 

1343 If the beam type is unsupported or not an electron beam. 

1344 """ 

1345 # beam must be electron 

1346 if yml_format.version >= 1.0: 

1347 beam_set_db = step_settings.get("beam") 

1348 if beam_set_db is None: 

1349 raise KeyError( 

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

1351 ) 

1352 beam_type_value = beam_set_db.get("type") 

1353 

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

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

1356 raise NotImplementedError(electron_beam_error_message) 

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

1358 raise NotImplementedError(electron_beam_error_message) 

1359 

1360 

1361@enforce_beam_type.register 

1362def _( 

1363 beam_type: tbt.IonBeam, 

1364 step_settings: dict, 

1365 step_name: str, 

1366 yml_format: tbt.YMLFormatVersion, 

1367) -> bool: 

1368 """ 

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

1370 

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

1372 

1373 Parameters 

1374 ---------- 

1375 beam_type : tbt.IonBeam 

1376 The ion beam type to enforce. 

1377 step_settings : dict 

1378 The dictionary containing the step settings. 

1379 step_name : str 

1380 The name of the step in the .yml file. 

1381 yml_format : tbt.YMLFormatVersion 

1382 The format specified by the version of the .yml file. 

1383 

1384 Returns 

1385 ------- 

1386 bool 

1387 True if the ion beam type is enforced successfully. 

1388 

1389 Raises 

1390 ------ 

1391 KeyError 

1392 If the 'beam' settings are missing from the .yml file. 

1393 NotImplementedError 

1394 If the beam type is unsupported or not an ion beam. 

1395 """ 

1396 # beam must be ion 

1397 if yml_format.version >= 1.0: 

1398 beam_set_db = step_settings.get("beam") 

1399 if beam_set_db is None: 

1400 raise KeyError( 

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

1402 ) 

1403 beam_type_value = beam_set_db.get("type") 

1404 

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

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

1407 raise NotImplementedError(ion_beam_error_message) 

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

1409 raise NotImplementedError(ion_beam_error_message) 

1410 

1411 

1412def ebsd( 

1413 microscope: tbt.Microscope, 

1414 step_settings: dict, 

1415 step_name: str, 

1416 yml_format: tbt.YMLFormatVersion, 

1417) -> tbt.EBSDSettings: 

1418 """ 

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

1420 

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

1422 

1423 Parameters 

1424 ---------- 

1425 microscope : tbt.Microscope 

1426 The microscope object for which to set the EBSD settings. 

1427 step_settings : dict 

1428 The dictionary containing the EBSD step settings from the .yml file. 

1429 step_name : str 

1430 The name of the step in the .yml file. 

1431 yml_format : tbt.YMLFormatVersion 

1432 The format specified by the version of the .yml file. 

1433 

1434 Returns 

1435 ------- 

1436 tbt.EBSDSettings 

1437 The EBSD settings object. 

1438 

1439 Raises 

1440 ------ 

1441 KeyError 

1442 If required settings are missing from the .yml file or if the 'concurrent_EDS' key is invalid. 

1443 """ 

1444 enforce_beam_type( 

1445 tbt.ElectronBeam(settings=None), 

1446 step_settings=step_settings, 

1447 step_name=step_name, 

1448 yml_format=yml_format, 

1449 ) 

1450 image_settings = image( 

1451 microscope=microscope, 

1452 step_settings=step_settings, 

1453 step_name=step_name, 

1454 yml_format=yml_format, 

1455 ) 

1456 concurrent_EDS = step_settings.get("concurrent_EDS") 

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

1458 enable_eds = False 

1459 elif concurrent_EDS == True: 

1460 enable_eds = True 

1461 else: 

1462 raise KeyError( 

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

1464 ) 

1465 

1466 ebsd_settings = tbt.EBSDSettings( 

1467 image=image_settings, 

1468 enable_eds=enable_eds, 

1469 enable_ebsd=True, 

1470 ) 

1471 return ebsd_settings 

1472 

1473 

1474def eds( 

1475 microscope: tbt.Microscope, 

1476 step_settings: dict, 

1477 step_name: str, 

1478 yml_format: tbt.YMLFormatVersion, 

1479) -> tbt.EDSSettings: 

1480 """ 

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

1482 

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

1484 

1485 Parameters 

1486 ---------- 

1487 microscope : tbt.Microscope 

1488 The microscope object for which to set the EDS settings. 

1489 step_settings : dict 

1490 The dictionary containing the EDS step settings from the .yml file. 

1491 step_name : str 

1492 The name of the step in the .yml file. 

1493 yml_format : tbt.YMLFormatVersion 

1494 The format specified by the version of the .yml file. 

1495 

1496 Returns 

1497 ------- 

1498 tbt.EDSSettings 

1499 The EDS settings object. 

1500 """ 

1501 enforce_beam_type( 

1502 tbt.ElectronBeam(settings=None), 

1503 step_settings=step_settings, 

1504 step_name=step_name, 

1505 yml_format=yml_format, 

1506 ) 

1507 image_settings = image( 

1508 microscope=microscope, 

1509 step_settings=step_settings, 

1510 step_name=step_name, 

1511 yml_format=yml_format, 

1512 ) 

1513 eds_settings = tbt.EDSSettings( 

1514 image=image_settings, 

1515 enable_eds=True, 

1516 ) 

1517 return eds_settings 

1518 

1519 

1520def custom( 

1521 microscope: tbt.Microscope, 

1522 step_settings: dict, 

1523 step_name: str, 

1524 yml_format: tbt.YMLFormatVersion, 

1525) -> tbt.CustomSettings: 

1526 """ 

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

1528 

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

1530 

1531 Parameters 

1532 ---------- 

1533 microscope : tbt.Microscope 

1534 The microscope object for which to set the custom settings. 

1535 step_settings : dict 

1536 The dictionary containing the custom step settings from the .yml file. 

1537 step_name : str 

1538 The name of the step in the .yml file. 

1539 yml_format : tbt.YMLFormatVersion 

1540 The format specified by the version of the .yml file. 

1541 

1542 Returns 

1543 ------- 

1544 tbt.CustomSettings 

1545 The custom settings object. 

1546 

1547 Raises 

1548 ------ 

1549 KeyError 

1550 If required settings are missing from the .yml file. 

1551 ValueError 

1552 If the specified script or executable path does not exist. 

1553 """ 

1554 if yml_format.version >= 1.0: 

1555 script_path = step_settings.get("script_path") 

1556 if script_path is None: 

1557 raise KeyError( 

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

1559 ) 

1560 if not Path(script_path).is_file(): 

1561 raise ValueError( 

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

1563 ) 

1564 

1565 executable_path = step_settings.get("executable_path") 

1566 if executable_path is None: 

1567 raise KeyError( 

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

1569 ) 

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

1571 raise ValueError( 

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

1573 ) 

1574 

1575 custom_settings = tbt.CustomSettings( 

1576 script_path=Path(script_path), 

1577 executable_path=Path(executable_path), 

1578 ) 

1579 return custom_settings 

1580 

1581 

1582# def fib_pattern_type( 

1583# settings: tbt.FIBSettings, 

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

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

1586 

1587 

1588def scan_limits( 

1589 selected_beam: property, 

1590) -> tbt.ScanLimits: 

1591 """ 

1592 Retrieve the scan settings limits for the selected beam. 

1593 

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

1595 

1596 Parameters 

1597 ---------- 

1598 selected_beam : property 

1599 The selected beam property from which to retrieve the scan limits. 

1600 

1601 Returns 

1602 ------- 

1603 tbt.ScanLimits 

1604 The scan limits for rotation (degrees) and dwell time (microseconds). 

1605 """ 

1606 # rotation 

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

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

1609 # dwell_time 

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

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

1612 

1613 return tbt.ScanLimits( 

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

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

1616 ) 

1617 

1618 

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

1620 """ 

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

1622 

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

1624 

1625 Parameters 

1626 ---------- 

1627 input : str 

1628 The string representing the resolution in the format "WIDTHxHEIGHT". 

1629 

1630 Returns 

1631 ------- 

1632 tbt.Resolution 

1633 The resolution object. 

1634 

1635 Raises 

1636 ------ 

1637 ValueError 

1638 If the input string is not in the expected format. 

1639 """ 

1640 try: 

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

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

1643 except: 

1644 raise ValueError( 

1645 f"""Invalid string format,  

1646 expected string format of "WIDTHxHEIGHT",  

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

1648 ) 

1649 

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

1651 

1652 

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

1654 """ 

1655 Validate a string resolution. 

1656 

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

1658 

1659 Parameters 

1660 ---------- 

1661 string_resolution : str 

1662 The string representing the resolution in the format "WIDTHxHEIGHT". 

1663 

1664 Returns 

1665 ------- 

1666 bool 

1667 True if the resolution is valid, False otherwise. 

1668 """ 

1669 res = string_to_res(string_resolution) 

1670 width, height = res.width, res.height 

1671 return ( 

1672 ut.in_interval( 

1673 width, 

1674 limit=Constants.scan_resolution_limit, 

1675 type=tbt.IntervalType.CLOSED, 

1676 ) 

1677 ) and ( 

1678 ut.in_interval( 

1679 height, 

1680 limit=Constants.scan_resolution_limit, 

1681 type=tbt.IntervalType.CLOSED, 

1682 ) 

1683 ) 

1684 

1685 

1686def validate_auto_cb_settings( 

1687 settings: dict, 

1688 yml_format: tbt.YMLFormatVersion, 

1689 step_name: str, 

1690) -> bool: 

1691 """ 

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

1693 

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

1695 

1696 Parameters 

1697 ---------- 

1698 settings : dict 

1699 The dictionary containing the auto contrast/brightness settings. 

1700 yml_format : tbt.YMLFormatVersion 

1701 The format specified by the version of the .yml file. 

1702 step_name : str 

1703 The name of the step in the .yml file. 

1704 

1705 Returns 

1706 ------- 

1707 bool 

1708 True if the settings are valid, False otherwise. 

1709 

1710 Raises 

1711 ------ 

1712 KeyError 

1713 If required keys are missing from the settings dictionary. 

1714 ValueError 

1715 If the settings do not satisfy the specified schema. 

1716 """ 

1717 

1718 if ut.none_value_dictionary(settings): 

1719 return True 

1720 

1721 if settings.get("left") is None or settings.get("top") is None: 

1722 if settings.get("left") is None: 

1723 missing_key = "left" 

1724 else: 

1725 missing_key = "top" 

1726 raise KeyError( 

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

1728 ) 

1729 

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

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

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

1733 

1734 if yml_format.version >= 1.0: 

1735 schema = Schema( 

1736 { 

1737 "left": And( 

1738 float, 

1739 lambda x: ut.in_interval( 

1740 x, 

1741 limit=origin_limit, 

1742 type=tbt.IntervalType.RIGHT_OPEN, 

1743 ), 

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

1745 ), 

1746 "top": And( 

1747 float, 

1748 lambda x: ut.in_interval( 

1749 x, 

1750 limit=origin_limit, 

1751 type=tbt.IntervalType.RIGHT_OPEN, 

1752 ), 

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

1754 ), 

1755 "width": And( 

1756 float, 

1757 lambda x: ut.in_interval( 

1758 x, 

1759 limit=width_limit, 

1760 type=tbt.IntervalType.LEFT_OPEN, 

1761 ), 

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

1763 ), 

1764 "height": And( 

1765 float, 

1766 lambda x: ut.in_interval( 

1767 x, 

1768 limit=height_limit, 

1769 type=tbt.IntervalType.LEFT_OPEN, 

1770 ), 

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

1772 ), 

1773 }, 

1774 ignore_extra_keys=True, 

1775 ) 

1776 

1777 try: 

1778 schema.validate(settings) 

1779 except UnboundLocalError: 

1780 raise ValueError( 

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

1782 ) 

1783 return True 

1784 

1785 

1786def validate_stage_position( 

1787 microscope: tbt.Microscope, 

1788 step_name: str, 

1789 settings: dict, 

1790 yml_format: tbt.YMLFormatVersion, 

1791) -> bool: 

1792 """ 

1793 Perform schema checking for stage position dictionary. 

1794 

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

1796 

1797 Parameters 

1798 ---------- 

1799 microscope : tbt.Microscope 

1800 The microscope object for which to validate the stage position settings. 

1801 step_name : str 

1802 The name of the step in the .yml file. 

1803 settings : dict 

1804 The dictionary containing the stage position settings. 

1805 yml_format : tbt.YMLFormatVersion 

1806 The format specified by the version of the .yml file. 

1807 

1808 Returns 

1809 ------- 

1810 bool 

1811 True if the settings are valid, False otherwise. 

1812 

1813 Raises 

1814 ------ 

1815 ValueError 

1816 If the yml version is unsupported. 

1817 """ 

1818 limits = stage_limits(microscope=microscope) 

1819 

1820 if yml_format.version >= 1.0: 

1821 schema = Schema( 

1822 { 

1823 "x_mm": And( 

1824 float, 

1825 lambda x: ut.in_interval( 

1826 x, 

1827 limit=limits.x_mm, 

1828 type=tbt.IntervalType.CLOSED, 

1829 ), 

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

1831 ), 

1832 "y_mm": And( 

1833 float, 

1834 lambda x: ut.in_interval( 

1835 x, 

1836 limit=limits.y_mm, 

1837 type=tbt.IntervalType.CLOSED, 

1838 ), 

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

1840 ), 

1841 "z_mm": And( 

1842 float, 

1843 lambda x: ut.in_interval( 

1844 x, 

1845 limit=limits.z_mm, 

1846 type=tbt.IntervalType.CLOSED, 

1847 ), 

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

1849 ), 

1850 "r_deg": And( 

1851 float, 

1852 lambda x: ut.in_interval( 

1853 x, 

1854 limit=limits.r_deg, 

1855 type=tbt.IntervalType.RIGHT_OPEN, 

1856 ), 

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

1858 ), 

1859 "t_deg": And( 

1860 float, 

1861 lambda x: ut.in_interval( 

1862 x, 

1863 limit=limits.t_deg, 

1864 type=tbt.IntervalType.CLOSED, 

1865 ), 

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

1867 ), 

1868 }, 

1869 ignore_extra_keys=True, 

1870 ) 

1871 

1872 try: 

1873 schema.validate(settings) 

1874 except UnboundLocalError: 

1875 raise ValueError( 

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

1877 ) 

1878 

1879 

1880def validate_beam_settings( 

1881 microscope: tbt.Microscope, 

1882 beam_type: tbt.BeamType, 

1883 settings: dict, 

1884 yml_format: tbt.YMLFormatVersion, 

1885 step_name: str, 

1886) -> bool: 

1887 """ 

1888 Perform schema checking for beam setting dictionary. 

1889 

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

1891 

1892 Parameters 

1893 ---------- 

1894 microscope : tbt.Microscope 

1895 The microscope object for which to validate the beam settings. 

1896 beam_type : tbt.BeamType 

1897 The type of the beam (electron or ion). 

1898 settings : dict 

1899 The dictionary containing the beam settings. 

1900 yml_format : tbt.YMLFormatVersion 

1901 The format specified by the version of the .yml file. 

1902 step_name : str 

1903 The name of the step in the .yml file. 

1904 

1905 Returns 

1906 ------- 

1907 bool 

1908 True if the settings are valid, False otherwise. 

1909 

1910 Raises 

1911 ------ 

1912 ValueError 

1913 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

1914 """ 

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

1916 selected_beam = ut.beam_type(specified_beam, microscope) 

1917 

1918 limits = beam_limits(selected_beam, beam_type) 

1919 

1920 if yml_format.version >= 1.0: 

1921 schema = Schema( 

1922 { 

1923 "voltage_kv": And( 

1924 float, 

1925 lambda x: ut.in_interval( 

1926 x, 

1927 limit=limits.voltage_kv, 

1928 type=tbt.IntervalType.CLOSED, 

1929 ), 

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

1931 ), 

1932 "voltage_tol_kv": And( 

1933 float, 

1934 lambda x: x > 0, 

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

1936 ), 

1937 "current_na": And( 

1938 float, 

1939 lambda x: ut.in_interval( 

1940 x, 

1941 limit=limits.current_na, 

1942 type=tbt.IntervalType.CLOSED, 

1943 ), 

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

1945 ), 

1946 "current_tol_na": And( 

1947 float, 

1948 lambda x: x > 0, 

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

1950 ), 

1951 "hfw_mm": And( 

1952 float, 

1953 lambda x: ut.in_interval( 

1954 x, 

1955 limits.hfw_mm, 

1956 type=tbt.IntervalType.CLOSED, 

1957 ), 

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

1959 ), 

1960 "working_dist_mm": And( 

1961 float, 

1962 lambda x: ut.in_interval( 

1963 x, 

1964 limit=limits.working_distance_mm, 

1965 type=tbt.IntervalType.CLOSED, 

1966 ), 

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

1968 ), 

1969 }, 

1970 ignore_extra_keys=True, 

1971 ) 

1972 

1973 e_beam_schema = Schema( 

1974 { 

1975 "dynamic_focus": Or( 

1976 None, 

1977 bool, 

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

1979 ), 

1980 "tilt_correction": Or( 

1981 None, 

1982 bool, 

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

1984 ), 

1985 }, 

1986 ignore_extra_keys=True, 

1987 ) 

1988 i_beam_schema = Schema( 

1989 { 

1990 "dynamic_focus": Or( 

1991 None, 

1992 False, 

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

1994 ), 

1995 "tilt_correction": Or( 

1996 None, 

1997 False, 

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

1999 ), 

2000 }, 

2001 ignore_extra_keys=True, 

2002 ) 

2003 

2004 try: 

2005 schema.validate(settings) 

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

2007 e_beam_schema.validate(settings) 

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

2009 i_beam_schema.validate(settings) 

2010 except UnboundLocalError: 

2011 raise ValueError( 

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

2013 ) 

2014 

2015 

2016def validate_detector_settings( 

2017 microscope: tbt.Microscope, 

2018 beam_type: tbt.BeamType, 

2019 settings: dict, 

2020 yml_format: tbt.YMLFormatVersion, 

2021 step_name: str, 

2022) -> bool: 

2023 """ 

2024 Perform schema checking for detector setting dictionary. 

2025 

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

2027 

2028 Parameters 

2029 ---------- 

2030 microscope : tbt.Microscope 

2031 The microscope object for which to validate the detector settings. 

2032 beam_type : tbt.BeamType 

2033 The type of the beam (electron or ion). 

2034 settings : dict 

2035 The dictionary containing the detector settings. 

2036 yml_format : tbt.YMLFormatVersion 

2037 The format specified by the version of the .yml file. 

2038 step_name : str 

2039 The name of the step in the .yml file. 

2040 

2041 Returns 

2042 ------- 

2043 bool 

2044 True if the settings are valid, False otherwise. 

2045 

2046 Raises 

2047 ------ 

2048 KeyError 

2049 If auto contrast/brightness settings conflict with fixed brightness/contrast values. 

2050 ValueError 

2051 If the yml version is unsupported, or if the settings do not satisfy the specified schema, or if the detector type or mode is unsupported. 

2052 """ 

2053 

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

2055 devices.device_access(microscope=microscope) 

2056 

2057 # set specified beam to active imaging device 

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

2059 img.set_beam_device( 

2060 microscope=microscope, 

2061 device=specified_beam.device, 

2062 ) 

2063 

2064 auto_cb_db = settings.get("auto_cb") 

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

2066 if use_auto_cb and ( 

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

2068 ): 

2069 raise KeyError( 

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

2071 ) 

2072 

2073 if yml_format.version >= 1.0: 

2074 detector = settings.get("type") 

2075 mode = settings.get("mode") 

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

2077 schema = Schema( 

2078 { 

2079 "brightness": Or( 

2080 None, 

2081 And( 

2082 float, 

2083 lambda x: ut.in_interval( 

2084 x, 

2085 limit=cb_limit, 

2086 type=tbt.IntervalType.LEFT_OPEN, 

2087 ), 

2088 ), 

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

2090 ), 

2091 "contrast": Or( 

2092 None, 

2093 And( 

2094 float, 

2095 lambda x: ut.in_interval( 

2096 x, 

2097 limit=cb_limit, 

2098 type=tbt.IntervalType.LEFT_OPEN, 

2099 ), 

2100 ), 

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

2102 ), 

2103 }, 

2104 ignore_extra_keys=True, 

2105 ) 

2106 

2107 # check detector type 

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

2109 raise ValueError( 

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

2111 ) 

2112 detector_type = tbt.DetectorType(detector) 

2113 microscope_detector_types = available_detector_types(microscope=microscope) 

2114 if detector_type.value not in microscope_detector_types: 

2115 raise ValueError( 

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

2117 ) 

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

2119 img.detector_type( 

2120 microscope=microscope, 

2121 detector=detector_type, 

2122 ) 

2123 

2124 # check detector mode 

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

2126 raise ValueError( 

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

2128 ) 

2129 detector_mode = tbt.DetectorMode(mode) 

2130 microscope_detector_modes = available_detector_modes(microscope=microscope) 

2131 if detector_mode.value not in microscope_detector_modes: 

2132 raise ValueError( 

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

2134 ) 

2135 

2136 # check detector settings 

2137 try: 

2138 schema.validate(settings) 

2139 except UnboundLocalError: 

2140 raise ValueError( 

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

2142 ) 

2143 

2144 

2145def validate_EBSD_EDS_settings( 

2146 ebsd_oem: str, 

2147 eds_oem: str, 

2148) -> bool: 

2149 """ 

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

2151 

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

2153 

2154 Parameters 

2155 ---------- 

2156 yml_format : tbt.YMLFormatVersion 

2157 The format specified by the version of the .yml file. 

2158 connection_host : str 

2159 The host for the microscope connection. 

2160 connection_port : str 

2161 The port for the microscope connection. 

2162 ebsd_oem : str 

2163 The OEM for the EBSD device. 

2164 eds_oem : str 

2165 The OEM for the EDS device. 

2166 

2167 Returns 

2168 ------- 

2169 bool 

2170 True if the settings are valid, False otherwise. 

2171 

2172 Raises 

2173 ------ 

2174 NotImplementedError 

2175 If differing EBSD and EDS OEMs are requested. 

2176 ValueError 

2177 If the EBSD or EDS OEM is unsupported. 

2178 SystemError 

2179 If the Laser API is not accessible. 

2180 """ 

2181 # ensure same manufacturer for both EBSD and EDS 

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

2183 raise NotImplementedError( 

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

2185 ) 

2186 

2187 # Check EBSD OEM 

2188 if not ut.valid_enum_entry(ebsd_oem, tbt.ExternalDeviceOEM): 

2189 raise ValueError( 

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

2191 ) 

2192 ebsd_device = tbt.ExternalDeviceOEM(ebsd_oem) 

2193 # Check EDS OEM 

2194 if not ut.valid_enum_entry(eds_oem, tbt.ExternalDeviceOEM): 

2195 raise ValueError( 

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

2197 ) 

2198 eds_device = tbt.ExternalDeviceOEM(eds_oem) 

2199 

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

2201 if ebsd_device == eds_device == tbt.ExternalDeviceOEM.NONE: 

2202 return True 

2203 

2204 # check EBSD and EDS connection 

2205 try: 

2206 fs_laser.tfs_laser 

2207 except: 

2208 raise SystemError( 

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

2210 ) 

2211 

2212 ###################### PROPOSED CHANGE ###################### 

2213 # Retracting devices here is unnecessary 

2214 # This causes devices to retract during validation 

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

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

2217 ###################### PROPOSED CHANGE ###################### 

2218 # microscope = tbt.Microscope() 

2219 # ut.connect_microscope( 

2220 # microscope=microscope, 

2221 # quiet_output=True, 

2222 # connection_host=connection_host, 

2223 # connection_port=connection_port, 

2224 # ) 

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

2226 # devices.retract_EDS(microscope=microscope) 

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

2228 # devices.retract_EBSD(microscope=microscope) 

2229 # ut.disconnect_microscope( 

2230 # microscope=microscope, 

2231 # quiet_output=True, 

2232 # ) 

2233 

2234 return True 

2235 

2236 

2237def validate_general_settings( 

2238 settings: dict, 

2239 yml_format: tbt.YMLFormatVersion, 

2240) -> bool: 

2241 """ 

2242 Perform schema checking for general setting dictionary. 

2243 

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

2245 

2246 Parameters 

2247 ---------- 

2248 settings : dict 

2249 The dictionary containing the general settings. 

2250 yml_format : tbt.YMLFormatVersion 

2251 The format specified by the version of the .yml file. 

2252 

2253 Returns 

2254 ------- 

2255 bool 

2256 True if the settings are valid, False otherwise. 

2257 

2258 Raises 

2259 ------ 

2260 ValueError 

2261 If the general settings dictionary is empty, or if the settings do not satisfy the specified schema, or if the connection is invalid. 

2262 NotImplementedError 

2263 If the sectioning axis is unsupported. 

2264 """ 

2265 

2266 if settings == {}: 

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

2268 

2269 slice_thickness_limit_um = Constants.slice_thickness_limit_um 

2270 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_generic 

2271 

2272 if yml_format.version >= 1.0: 

2273 sectioning_axis = settings.get("sectioning_axis") 

2274 connection_host = settings.get("connection_host") 

2275 connection_port = settings.get("connection_port") 

2276 ebsd_oem = settings.get("EBSD_OEM") 

2277 eds_oem = settings.get("EDS_OEM") 

2278 exp_dir = settings.get("exp_dir") 

2279 h5_log_name = settings.get("h5_log_name") 

2280 

2281 # Validate the non-numeric values 

2282 # Check sectioning axis 

2283 if not ut.valid_enum_entry(sectioning_axis, tbt.SectioningAxis): 

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

2285 # TODO 

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

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

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

2289 pre_tilt_limit_deg = Constants.pre_tilt_limit_deg_non_Z_sectioning # overwrite 

2290 warnings.warn( 

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

2292 ) 

2293 # Check connection host and port 

2294 if not ut.valid_microscope_connection( 

2295 host=connection_host, 

2296 port=connection_port, 

2297 ): 

2298 raise ValueError( 

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

2300 ) 

2301 

2302 # check EBSD and EDS 

2303 validate_EBSD_EDS_settings( 

2304 ebsd_oem=ebsd_oem, 

2305 eds_oem=eds_oem, 

2306 ) 

2307 

2308 # Check exp dir 

2309 try: 

2310 Path(exp_dir).mkdir( 

2311 parents=True, 

2312 exist_ok=True, 

2313 ) 

2314 except TypeError: 

2315 raise ValueError( 

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

2317 ) 

2318 # Check h5 log name 

2319 if not isinstance(h5_log_name, str): 

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

2321 

2322 schema = Schema( 

2323 { 

2324 "slice_thickness_um": And( 

2325 float, 

2326 lambda x: ut.in_interval( 

2327 x, 

2328 limit=slice_thickness_limit_um, 

2329 type=tbt.IntervalType.CLOSED, 

2330 ), 

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

2332 ), 

2333 "max_slice_num": And( 

2334 int, 

2335 lambda x: x > 0, 

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

2337 ), 

2338 "pre_tilt_deg": And( 

2339 float, 

2340 lambda x: ut.in_interval( 

2341 x, 

2342 limit=pre_tilt_limit_deg, 

2343 type=tbt.IntervalType.CLOSED, 

2344 ), 

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

2346 ), 

2347 "stage_translational_tol_um": And( 

2348 float, 

2349 lambda x: x > 0, 

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

2351 ), 

2352 "stage_angular_tol_deg": And( 

2353 float, 

2354 lambda x: x > 0, 

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

2356 ), 

2357 "step_count": And( 

2358 int, 

2359 lambda x: x > 0, 

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

2361 ), 

2362 }, 

2363 ignore_extra_keys=True, 

2364 ) 

2365 

2366 try: 

2367 schema.validate(settings) 

2368 except UnboundLocalError: 

2369 raise ValueError( 

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

2371 ) 

2372 

2373 

2374def validate_scan_settings( 

2375 microscope: tbt.Microscope, 

2376 beam_type: tbt.BeamType, 

2377 settings: dict, 

2378 yml_format: tbt.YMLFormatVersion, 

2379 step_name: str, 

2380) -> bool: 

2381 """ 

2382 Perform schema checking for scan setting dictionary. 

2383 

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

2385 

2386 Parameters 

2387 ---------- 

2388 microscope : tbt.Microscope 

2389 The microscope object for which to validate the scan settings. 

2390 beam_type : tbt.BeamType 

2391 The type of the beam (electron or ion). 

2392 settings : dict 

2393 The dictionary containing the scan settings. 

2394 yml_format : tbt.YMLFormatVersion 

2395 The format specified by the version of the .yml file. 

2396 step_name : str 

2397 The name of the step in the .yml file. 

2398 

2399 Returns 

2400 ------- 

2401 bool 

2402 True if the settings are valid, False otherwise. 

2403 

2404 Raises 

2405 ------ 

2406 ValueError 

2407 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2408 """ 

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

2410 selected_beam = ut.beam_type(specified_beam, microscope) 

2411 

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

2413 if yml_format.version >= 1.0: 

2414 limits = scan_limits(selected_beam) 

2415 resolution = settings["resolution"] 

2416 rotation_limit = limits.rotation_deg 

2417 dwell_limit = limits.dwell_us 

2418 

2419 # check resolution 

2420 res = string_to_res(resolution) 

2421 

2422 schema = Schema( 

2423 { 

2424 "resolution": And( 

2425 str, 

2426 lambda x: valid_string_resolution(x), 

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

2428 ), 

2429 "rotation_deg": And( 

2430 float, 

2431 lambda x: ut.in_interval( 

2432 x, 

2433 limit=rotation_limit, 

2434 type=tbt.IntervalType.CLOSED, 

2435 ), 

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

2437 ), 

2438 "dwell_time_us": And( 

2439 float, 

2440 lambda x: ut.in_interval( 

2441 x, 

2442 limit=dwell_limit, 

2443 type=tbt.IntervalType.CLOSED, 

2444 ), 

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

2446 ), 

2447 }, 

2448 ignore_extra_keys=True, 

2449 ) 

2450 

2451 try: 

2452 schema.validate(settings) 

2453 except UnboundLocalError: 

2454 raise ValueError( 

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

2456 ) 

2457 

2458 

2459def stage_position_settings( 

2460 microscope: tbt.Microscope, 

2461 step_name: str, 

2462 general_settings: tbt.GeneralSettings, 

2463 step_stage_settings: dict, 

2464 yml_format: tbt.YMLFormatVersion, 

2465) -> tbt.StageSettings: 

2466 """ 

2467 Create a StagePositionUser object from settings, including validation. 

2468 

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

2470 

2471 Parameters 

2472 ---------- 

2473 microscope : tbt.Microscope 

2474 The microscope object for which to set the stage position. 

2475 step_name : str 

2476 The name of the step in the .yml file. 

2477 general_settings : tbt.GeneralSettings 

2478 The general settings object. 

2479 step_stage_settings : dict 

2480 The dictionary containing the stage position settings for the step. 

2481 yml_format : tbt.YMLFormatVersion 

2482 The format specified by the version of the .yml file. 

2483 

2484 Returns 

2485 ------- 

2486 tbt.StageSettings 

2487 The stage settings object. 

2488 

2489 Raises 

2490 ------ 

2491 NotImplementedError 

2492 If the rotation side value is unsupported. 

2493 ValueError 

2494 If the stage position settings do not satisfy the specified schema. 

2495 """ 

2496 

2497 if yml_format.version >= 1.0: 

2498 pos_db = step_stage_settings.get("initial_position") 

2499 

2500 rotation_side = step_stage_settings["rotation_side"] 

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

2502 raise NotImplementedError( 

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

2504 ) 

2505 

2506 validate_stage_position( 

2507 microscope=microscope, 

2508 step_name=step_name, 

2509 settings=pos_db, 

2510 yml_format=yml_format, 

2511 ) 

2512 

2513 initial_position = tbt.StagePositionUser( 

2514 x_mm=pos_db["x_mm"], 

2515 y_mm=pos_db["y_mm"], 

2516 z_mm=pos_db["z_mm"], 

2517 r_deg=pos_db["r_deg"], 

2518 t_deg=pos_db["t_deg"], 

2519 ) 

2520 

2521 pretilt_angle_deg = general_settings.pre_tilt_deg 

2522 sectioning_axis = general_settings.sectioning_axis 

2523 

2524 stage_settings = tbt.StageSettings( 

2525 microscope=microscope, 

2526 initial_position=initial_position, 

2527 pretilt_angle_deg=pretilt_angle_deg, 

2528 sectioning_axis=sectioning_axis, 

2529 rotation_side=tbt.RotationSide(rotation_side), 

2530 # movement_mode=tbt.StageMovementMode.OUT_OF_PLANE, 

2531 ) 

2532 

2533 return stage_settings 

2534 

2535 

2536def validate_pulse_settings( 

2537 settings: dict, 

2538 yml_format: tbt.YMLFormatVersion, 

2539 step_name: str, 

2540) -> bool: 

2541 """ 

2542 Perform schema checking for pulse setting dictionary. 

2543 

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

2545 

2546 Parameters 

2547 ---------- 

2548 settings : dict 

2549 The dictionary containing the pulse settings. 

2550 yml_format : tbt.YMLFormatVersion 

2551 The format specified by the version of the .yml file. 

2552 step_name : str 

2553 The name of the step in the .yml file. 

2554 

2555 Returns 

2556 ------- 

2557 bool 

2558 True if the settings are valid, False otherwise. 

2559 

2560 Raises 

2561 ------ 

2562 NotImplementedError 

2563 If the wavelength or polarization value is unsupported. 

2564 ValueError 

2565 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2566 """ 

2567 if yml_format.version >= 1.0: 

2568 wavelength_nm = settings.get("wavelength_nm") 

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

2570 raise NotImplementedError( 

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

2572 ) 

2573 polarization = settings.get("polarization") 

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

2575 raise NotImplementedError( 

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

2577 ) 

2578 

2579 schema = Schema( 

2580 { 

2581 "divider": And( 

2582 int, 

2583 lambda x: x > 0, 

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

2585 ), 

2586 "energy_uj": And( 

2587 float, 

2588 lambda x: x > 0, 

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

2590 ), 

2591 }, 

2592 ignore_extra_keys=True, 

2593 ) 

2594 try: 

2595 schema.validate(settings) 

2596 except UnboundLocalError: 

2597 raise ValueError( 

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

2599 ) 

2600 

2601 

2602def validate_laser_optics_settings( 

2603 settings: dict, 

2604 yml_format: tbt.YMLFormatVersion, 

2605 step_name: str, 

2606) -> bool: 

2607 """ 

2608 Perform schema checking for laser optics setting dictionary. 

2609 

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

2611 

2612 Parameters 

2613 ---------- 

2614 settings : dict 

2615 The dictionary containing the laser optics settings. 

2616 yml_format : tbt.YMLFormatVersion 

2617 The format specified by the version of the .yml file. 

2618 step_name : str 

2619 The name of the step in the .yml file. 

2620 

2621 Returns 

2622 ------- 

2623 bool 

2624 True if the settings are valid, False otherwise. 

2625 

2626 Raises 

2627 ------ 

2628 ValueError 

2629 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2630 """ 

2631 if yml_format.version >= 1.0: 

2632 schema = Schema( 

2633 { 

2634 "objective_position_mm": And( 

2635 float, 

2636 lambda x: ut.in_interval( 

2637 x, 

2638 limit=Constants.laser_objective_limit_mm, 

2639 type=tbt.IntervalType.CLOSED, 

2640 ), 

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

2642 ), 

2643 "beam_shift_um_x": And( 

2644 float, 

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

2646 ), 

2647 "beam_shift_um_y": And( 

2648 float, 

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

2650 ), 

2651 }, 

2652 ignore_extra_keys=True, 

2653 ) 

2654 try: 

2655 schema.validate(settings) 

2656 except UnboundLocalError: 

2657 raise ValueError( 

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

2659 ) 

2660 

2661 

2662def validate_laser_box_settings( 

2663 settings: dict, 

2664 yml_format: tbt.YMLFormatVersion, 

2665 step_name: str, 

2666) -> bool: 

2667 """ 

2668 Perform schema checking for laser box pattern setting dictionary. 

2669 

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

2671 

2672 Parameters 

2673 ---------- 

2674 settings : dict 

2675 The dictionary containing the laser box pattern settings. 

2676 yml_format : tbt.YMLFormatVersion 

2677 The format specified by the version of the .yml file. 

2678 step_name : str 

2679 The name of the step in the .yml file. 

2680 

2681 Returns 

2682 ------- 

2683 bool 

2684 True if the settings are valid, False otherwise. 

2685 

2686 Raises 

2687 ------ 

2688 NotImplementedError 

2689 If the scan type or coordinate reference value is unsupported. 

2690 ValueError 

2691 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2692 """ 

2693 if yml_format.version >= 1.0: 

2694 # scan type 

2695 scan_type = settings.get("scan_type") 

2696 scan_types = [ 

2697 tbt.LaserScanType.RASTER, 

2698 tbt.LaserScanType.SERPENTINE, 

2699 ] 

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

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

2702 raise NotImplementedError(scan_type_error_msg) 

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

2704 raise NotImplementedError(scan_type_error_msg) 

2705 

2706 # coordinate reference 

2707 coordinate_ref = settings.get("coordinate_ref") 

2708 coord_refs = [ 

2709 tbt.CoordinateReference.CENTER, 

2710 tbt.CoordinateReference.UPPER_CENTER, 

2711 tbt.CoordinateReference.UPPER_LEFT, 

2712 ] 

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

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

2715 raise NotImplementedError(coord_error_msg) 

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

2717 raise NotImplementedError(coord_error_msg) 

2718 

2719 schema = Schema( 

2720 { 

2721 "passes": And( 

2722 int, 

2723 lambda x: x > 0, 

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

2725 ), 

2726 "size_x_um": And( 

2727 float, 

2728 lambda x: x > 0, 

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

2730 ), 

2731 "size_y_um": And( 

2732 float, 

2733 lambda x: x > 0, 

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

2735 ), 

2736 "pitch_x_um": And( 

2737 float, 

2738 lambda x: x > 0, 

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

2740 ), 

2741 "pitch_y_um": And( 

2742 float, 

2743 lambda x: x > 0, 

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

2745 ), 

2746 }, 

2747 ignore_extra_keys=True, 

2748 ) 

2749 try: 

2750 schema.validate(settings) 

2751 except UnboundLocalError: 

2752 raise ValueError( 

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

2754 ) 

2755 

2756 

2757def validate_laser_line_settings( 

2758 settings: dict, 

2759 yml_format: tbt.YMLFormatVersion, 

2760 step_name: str, 

2761) -> bool: 

2762 """ 

2763 Perform schema checking for laser line pattern setting dictionary. 

2764 

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

2766 

2767 Parameters 

2768 ---------- 

2769 settings : dict 

2770 The dictionary containing the laser line pattern settings. 

2771 yml_format : tbt.YMLFormatVersion 

2772 The format specified by the version of the .yml file. 

2773 step_name : str 

2774 The name of the step in the .yml file. 

2775 

2776 Returns 

2777 ------- 

2778 bool 

2779 True if the settings are valid, False otherwise. 

2780 

2781 Raises 

2782 ------ 

2783 NotImplementedError 

2784 If the scan type value is unsupported. 

2785 ValueError 

2786 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2787 """ 

2788 if yml_format.version >= 1.0: 

2789 # scan type 

2790 scan_type = settings.get("scan_type") 

2791 scan_types = [ 

2792 tbt.LaserScanType.SINGLE, 

2793 tbt.LaserScanType.LAP, 

2794 ] 

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

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

2797 raise NotImplementedError(scan_type_error_msg) 

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

2799 raise NotImplementedError(scan_type_error_msg) 

2800 

2801 schema = Schema( 

2802 { 

2803 "passes": And( 

2804 int, 

2805 lambda x: x > 0, 

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

2807 ), 

2808 "size_um": And( 

2809 float, 

2810 lambda x: x > 0, 

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

2812 ), 

2813 "pitch_um": And( 

2814 float, 

2815 lambda x: x > 0, 

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

2817 ), 

2818 }, 

2819 ignore_extra_keys=True, 

2820 ) 

2821 try: 

2822 schema.validate(settings) 

2823 except UnboundLocalError: 

2824 raise ValueError( 

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

2826 ) 

2827 

2828 

2829def validate_laser_mode_settings( 

2830 settings: dict, 

2831 yml_format: tbt.YMLFormatVersion, 

2832 step_name: str, 

2833) -> bool: 

2834 """ 

2835 Perform schema checking for laser mode setting dictionary. 

2836 

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

2838 

2839 Parameters 

2840 ---------- 

2841 settings : dict 

2842 The dictionary containing the laser mode settings. 

2843 yml_format : tbt.YMLFormatVersion 

2844 The format specified by the version of the .yml file. 

2845 step_name : str 

2846 The name of the step in the .yml file. 

2847 

2848 Returns 

2849 ------- 

2850 bool 

2851 True if the settings are valid, False otherwise. 

2852 

2853 Raises 

2854 ------ 

2855 NotImplementedError 

2856 If the laser pattern mode value is unsupported. 

2857 ValueError 

2858 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

2859 """ 

2860 if yml_format.version >= 1.0: 

2861 mode = settings.get("mode") 

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

2863 raise NotImplementedError( 

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

2865 ) 

2866 

2867 schema = Schema( 

2868 { 

2869 "rotation_deg": And( 

2870 float, 

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

2872 ), 

2873 }, 

2874 ignore_extra_keys=True, 

2875 ) 

2876 schema_fine = Schema( 

2877 { 

2878 "pixel_dwell_ms": Or( 

2879 None, 

2880 "null", 

2881 "None", 

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

2883 ), 

2884 "pulses_per_pixel": And( 

2885 int, 

2886 lambda x: x > 0, 

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

2888 ), 

2889 }, 

2890 ignore_extra_keys=True, 

2891 ) 

2892 schema_coarse = Schema( 

2893 { 

2894 "pixel_dwell_ms": And( 

2895 float, 

2896 lambda x: x > 0.0, 

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

2898 ), 

2899 "pulses_per_pixel": Or( 

2900 None, 

2901 "null", 

2902 "None", 

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

2904 ), 

2905 }, 

2906 ignore_extra_keys=True, 

2907 ) 

2908 try: 

2909 schema.validate(settings) 

2910 if mode == "fine": 

2911 schema_fine.validate(settings) 

2912 if mode == "coarse": 

2913 schema_coarse.validate(settings) 

2914 except UnboundLocalError: 

2915 raise ValueError( 

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

2917 ) 

2918 

2919 

2920def validate_laser_pattern_settings( 

2921 settings: dict, 

2922 yml_format: tbt.YMLFormatVersion, 

2923 step_name: str, 

2924) -> tbt.LaserPatternType: 

2925 """ 

2926 Perform schema checking for laser pattern setting dictionary. 

2927 

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

2929 

2930 Parameters 

2931 ---------- 

2932 settings : dict 

2933 The dictionary containing the laser pattern settings. 

2934 yml_format : tbt.YMLFormatVersion 

2935 The format specified by the version of the .yml file. 

2936 step_name : str 

2937 The name of the step in the .yml file. 

2938 

2939 Returns 

2940 ------- 

2941 tbt.LaserPatternType 

2942 The type of the laser pattern (box or line). 

2943 

2944 Raises 

2945 ------ 

2946 KeyError 

2947 If required settings are missing from the .yml file or if multiple pattern types are specified. 

2948 ValueError 

2949 If the laser pattern settings are invalid. 

2950 """ 

2951 if yml_format.version >= 1.0: 

2952 # determine type of pattern (only one allowed) 

2953 type_set_db = settings.get("type") 

2954 if type_set_db is None: 

2955 raise KeyError( 

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

2957 ) 

2958 

2959 box_settings = type_set_db.get("box") 

2960 line_settings = type_set_db.get("line") 

2961 if box_settings is None: 

2962 box_pattern = False 

2963 else: 

2964 box_pattern = not ut.none_value_dictionary(box_settings) 

2965 if line_settings is None: 

2966 line_pattern = False 

2967 else: 

2968 line_pattern = not ut.none_value_dictionary(line_settings) 

2969 if box_pattern == line_pattern == False: 

2970 raise KeyError( 

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

2972 ) 

2973 if box_pattern == line_pattern == True: 

2974 raise KeyError( 

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

2976 ) 

2977 validate_laser_mode_settings( 

2978 settings=settings, 

2979 yml_format=yml_format, 

2980 step_name=step_name, 

2981 ) 

2982 

2983 if box_pattern: 

2984 validate_laser_box_settings( 

2985 settings=box_settings, 

2986 yml_format=yml_format, 

2987 step_name=step_name, 

2988 ) 

2989 return tbt.LaserPatternType.BOX 

2990 elif line_pattern: 

2991 validate_laser_line_settings( 

2992 settings=line_settings, 

2993 yml_format=yml_format, 

2994 step_name=step_name, 

2995 ) 

2996 return tbt.LaserPatternType.LINE 

2997 else: 

2998 raise ValueError( 

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

3000 ) 

3001 

3002 

3003def validate_fib_pattern_settings( 

3004 microscope: tbt.Microscope, 

3005 settings: dict, 

3006 yml_format: tbt.YMLFormatVersion, 

3007 step_name: str, 

3008) -> Union[ 

3009 tbt.FIBRectanglePattern, 

3010 tbt.FIBRegularCrossSection, 

3011 tbt.FIBCleaningCrossSection, 

3012 tbt.FIBStreamPattern, 

3013]: 

3014 """ 

3015 Perform schema checking for FIB pattern setting dictionary. 

3016 

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

3018 

3019 Parameters 

3020 ---------- 

3021 microscope : tbt.Microscope 

3022 The microscope object for which to validate the FIB pattern settings. 

3023 settings : dict 

3024 The dictionary containing the FIB pattern settings. 

3025 yml_format : tbt.YMLFormatVersion 

3026 The format specified by the version of the .yml file. 

3027 step_name : str 

3028 The name of the step in the .yml file. 

3029 

3030 Returns 

3031 ------- 

3032 Union[tbt.FIBRectanglePattern, tbt.FIBRegularCrossSection, tbt.FIBCleaningCrossSection, tbt.FIBStreamPattern] 

3033 The validated FIB pattern object. 

3034 

3035 Raises 

3036 ------ 

3037 KeyError 

3038 If required settings are missing from the .yml file or if multiple pattern types are specified. 

3039 ValueError 

3040 If the application file is unsupported or invalid for the specified pattern type. 

3041 """ 

3042 

3043 if yml_format.version >= 1.0: 

3044 application_file = settings.get("application_file") 

3045 # determine type of pattern (only one allowed) 

3046 type_set_db = settings.get("type") 

3047 if type_set_db is None: 

3048 raise KeyError( 

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

3050 ) 

3051 rectangle_settings = type_set_db.get("rectangle") 

3052 regular_cross_section_settings = type_set_db.get("regular_cross_section") 

3053 cleaning_cross_section_settings = type_set_db.get("cleaning_cross_section") 

3054 selected_area_settings = type_set_db.get("selected_area") 

3055 

3056 pattern_settings = [ 

3057 rectangle_settings, 

3058 regular_cross_section_settings, 

3059 cleaning_cross_section_settings, 

3060 selected_area_settings, 

3061 ] 

3062 ( 

3063 rectangle_pattern, 

3064 regular_cross_section_pattern, 

3065 cleaning_cross_section_pattern, 

3066 selected_area_pattern, 

3067 ) = (None, None, None, None) 

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

3069 # assert pattern_names == [ 

3070 # "rectangle", 

3071 # "regular_cross_section", 

3072 # "cleaning_cross_section", 

3073 # "selected_area", 

3074 # ] 

3075 pattern_names = [ 

3076 "rectangle", 

3077 "regular_cross_section", 

3078 "cleaning_cross_section", 

3079 "selected_area", 

3080 ] 

3081 pattern_types = [ 

3082 rectangle_pattern, 

3083 regular_cross_section_pattern, 

3084 cleaning_cross_section_pattern, 

3085 selected_area_pattern, 

3086 ] 

3087 

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

3089 db = pattern_settings[type] 

3090 if db is None: 

3091 pattern_types[type] = False 

3092 else: 

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

3094 

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

3096 if sum(pattern_types) != 1: 

3097 raise KeyError( 

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

3099 ) 

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

3101 

3102 # check application file 

3103 valid_applications = application_files(microscope=microscope) 

3104 # TODO make this print out pretty 

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

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

3107 if application_file not in valid_applications: 

3108 raise ValueError( 

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

3110 ) 

3111 

3112 if pattern_type == tbt.FIBPatternType.RECTANGLE: 

3113 validate_fib_box_settings( 

3114 settings=rectangle_settings, 

3115 yml_format=yml_format, 

3116 step_name=step_name, 

3117 pattern_type=pattern_type, 

3118 ) 

3119 

3120 pattern = tbt.FIBPattern( 

3121 application=application_file, 

3122 type=pattern_type, 

3123 geometry=tbt.FIBRectanglePattern( 

3124 center_um=tbt.Point( 

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

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

3127 ), 

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

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

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

3131 scan_direction=tbt.FIBPatternScanDirection( 

3132 rectangle_settings.get("scan_direction") 

3133 ), 

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

3135 ), 

3136 ) 

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

3138 try: 

3139 geometry = pattern.geometry 

3140 microscope.patterning.set_default_application_file(pattern.application) 

3141 microscope.patterning.create_rectangle( 

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

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

3144 width=geometry.width_um * Conversions.UM_TO_M, 

3145 height=geometry.height_um * Conversions.UM_TO_M, 

3146 depth=geometry.depth_um * Conversions.UM_TO_M, 

3147 ) 

3148 microscope.patterning.clear_patterns() 

3149 except: 

3150 raise ValueError( 

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

3152 ) 

3153 

3154 return pattern 

3155 elif pattern_type == tbt.FIBPatternType.REGULAR_CROSS_SECTION: 

3156 validate_fib_box_settings( 

3157 settings=regular_cross_section_settings, 

3158 yml_format=yml_format, 

3159 step_name=step_name, 

3160 pattern_type=pattern_type, 

3161 ) 

3162 pattern = tbt.FIBPattern( 

3163 application=application_file, 

3164 type=pattern_type, 

3165 geometry=tbt.FIBRegularCrossSection( 

3166 center_um=tbt.Point( 

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

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

3169 ), 

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

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

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

3173 scan_direction=tbt.FIBPatternScanDirection( 

3174 regular_cross_section_settings.get("scan_direction") 

3175 ), 

3176 scan_type=tbt.FIBPatternScanType( 

3177 regular_cross_section_settings.get("scan_type") 

3178 ), 

3179 ), 

3180 ) 

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

3182 try: 

3183 geometry = pattern.geometry 

3184 microscope.patterning.set_default_application_file(pattern.application) 

3185 microscope.patterning.create_regular_cross_section( 

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

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

3188 width=geometry.width_um * Conversions.UM_TO_M, 

3189 height=geometry.height_um * Conversions.UM_TO_M, 

3190 depth=geometry.depth_um * Conversions.UM_TO_M, 

3191 ) 

3192 microscope.patterning.clear_patterns() 

3193 except: 

3194 raise ValueError( 

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

3196 ) 

3197 return pattern 

3198 elif pattern_type == tbt.FIBPatternType.CLEANING_CROSS_SECTION: 

3199 validate_fib_box_settings( 

3200 settings=cleaning_cross_section_settings, 

3201 yml_format=yml_format, 

3202 step_name=step_name, 

3203 pattern_type=pattern_type, 

3204 ) 

3205 pattern = tbt.FIBPattern( 

3206 application=application_file, 

3207 type=pattern_type, 

3208 geometry=tbt.FIBCleaningCrossSection( 

3209 center_um=tbt.Point( 

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

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

3212 ), 

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

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

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

3216 scan_direction=tbt.FIBPatternScanDirection( 

3217 cleaning_cross_section_settings.get("scan_direction") 

3218 ), 

3219 scan_type=tbt.FIBPatternScanType( 

3220 cleaning_cross_section_settings.get("scan_type") 

3221 ), 

3222 ), 

3223 ) 

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

3225 try: 

3226 geometry = pattern.geometry 

3227 microscope.patterning.set_default_application_file(pattern.application) 

3228 microscope.patterning.create_cleaning_cross_section( 

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

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

3231 width=geometry.width_um * Conversions.UM_TO_M, 

3232 height=geometry.height_um * Conversions.UM_TO_M, 

3233 depth=geometry.depth_um * Conversions.UM_TO_M, 

3234 ) 

3235 microscope.patterning.clear_patterns() 

3236 except: 

3237 raise ValueError( 

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

3239 ) 

3240 return pattern 

3241 elif pattern_type == tbt.FIBPatternType.SELECTED_AREA: 

3242 validate_fib_selected_area_settings( 

3243 settings=selected_area_settings, 

3244 yml_format=yml_format, 

3245 step_name=step_name, 

3246 pattern_type=pattern_type, 

3247 ) 

3248 pattern = tbt.FIBPattern( 

3249 application=application_file, 

3250 type=pattern_type, 

3251 geometry=tbt.FIBStreamPattern( 

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

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

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

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

3256 ), 

3257 ) 

3258 

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

3260 try: 

3261 microscope.patterning.set_default_application_file(pattern.application) 

3262 microscope.patterning.create_rectangle( 

3263 center_x=0.0, 

3264 center_y=0.0, 

3265 width=10.0e-6, 

3266 height=10.0e-6, 

3267 depth=1.0e-6, 

3268 ) 

3269 microscope.patterning.clear_patterns() 

3270 except: 

3271 raise ValueError( 

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

3273 ) 

3274 

3275 return pattern 

3276 else: 

3277 raise KeyError( 

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

3279 ) 

3280 

3281 

3282def validate_fib_box_settings( 

3283 settings: dict, 

3284 yml_format: tbt.YMLFormatVersion, 

3285 step_name: str, 

3286 pattern_type: tbt.FIBPatternType, 

3287) -> bool: 

3288 """ 

3289 Perform schema checking for FIB box pattern setting dictionary. 

3290 

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

3292 

3293 Parameters 

3294 ---------- 

3295 settings : dict 

3296 The dictionary containing the FIB box pattern settings. 

3297 yml_format : tbt.YMLFormatVersion 

3298 The format specified by the version of the .yml file. 

3299 step_name : str 

3300 The name of the step in the .yml file. 

3301 pattern_type : tbt.FIBPatternType 

3302 The type of the FIB pattern. 

3303 

3304 Returns 

3305 ------- 

3306 bool 

3307 True if the settings are valid, False otherwise. 

3308 

3309 Raises 

3310 ------ 

3311 ValueError 

3312 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

3313 """ 

3314 if yml_format.version >= 1.0: 

3315 # flattens nested dictionary, adding "_" separator 

3316 flat_settings = ut._flatten(settings) 

3317 schema = Schema( 

3318 { 

3319 "center_x_um": And( 

3320 float, 

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

3322 ), 

3323 "center_y_um": And( 

3324 float, 

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

3326 ), 

3327 "width_um": And( 

3328 float, 

3329 lambda x: x > 0, 

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

3331 ), 

3332 "height_um": And( 

3333 float, 

3334 lambda x: x > 0, 

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

3336 ), 

3337 "depth_um": And( 

3338 float, 

3339 lambda x: x > 0, 

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

3341 ), 

3342 "scan_direction": And( 

3343 str, 

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

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

3346 ), 

3347 "scan_type": And( 

3348 str, 

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

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

3351 ), 

3352 }, 

3353 ignore_extra_keys=True, 

3354 ) 

3355 try: 

3356 schema.validate(flat_settings) 

3357 except UnboundLocalError: 

3358 raise ValueError( 

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

3360 ) 

3361 return True 

3362 

3363 

3364def validate_fib_selected_area_settings( 

3365 settings: dict, 

3366 yml_format: tbt.YMLFormatVersion, 

3367 step_name: str, 

3368 pattern_type: tbt.FIBPatternType, 

3369) -> bool: 

3370 """ 

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

3372 

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

3374 

3375 Parameters 

3376 ---------- 

3377 settings : dict 

3378 The dictionary containing the FIB selected area pattern settings. 

3379 yml_format : tbt.YMLFormatVersion 

3380 The format specified by the version of the .yml file. 

3381 step_name : str 

3382 The name of the step in the .yml file. 

3383 pattern_type : tbt.FIBPatternType 

3384 The type of the FIB pattern. 

3385 

3386 Returns 

3387 ------- 

3388 bool 

3389 True if the settings are valid, False otherwise. 

3390 

3391 Raises 

3392 ------ 

3393 ValueError 

3394 If the yml version is unsupported or if the settings do not satisfy the specified schema. 

3395 """ 

3396 if yml_format.version >= 1.0: 

3397 schema = Schema( 

3398 { 

3399 "dwell_us": And( 

3400 float, 

3401 lambda x: x > 0, 

3402 # check all float error versions of modulus 

3403 Or( 

3404 lambda x: math.isclose( 

3405 x % Constants.stream_pattern_base_dwell_us, 

3406 0, 

3407 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3408 ), 

3409 lambda x: math.isclose( 

3410 x % Constants.stream_pattern_base_dwell_us, 

3411 Constants.stream_pattern_base_dwell_us, 

3412 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3413 ), 

3414 lambda x: math.isclose( 

3415 x % Constants.stream_pattern_base_dwell_us, 

3416 -Constants.stream_pattern_base_dwell_us, 

3417 abs_tol=Constants.stream_pattern_base_dwell_us / 1e5, 

3418 ), 

3419 ), 

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

3421 ), 

3422 "repeats": And( 

3423 int, 

3424 lambda x: x > 0, 

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

3426 ), 

3427 "recipe_file": And( 

3428 str, 

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

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

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

3432 ), 

3433 "mask_file": And( 

3434 str, 

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

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

3437 ), 

3438 }, 

3439 ignore_extra_keys=True, 

3440 ) 

3441 try: 

3442 schema.validate(settings) 

3443 except UnboundLocalError: 

3444 raise ValueError( 

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

3446 ) 

3447 return True 

3448 

3449 

3450def step( 

3451 microscope: tbt.Microscope, 

3452 # slice_number: str, 

3453 step_name: str, 

3454 step_settings: dict, 

3455 general_settings: tbt.GeneralSettings, 

3456 yml_format: tbt.YMLFormatVersion, 

3457) -> tbt.Step: 

3458 """ 

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

3460 

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

3462 

3463 Parameters 

3464 ---------- 

3465 microscope : tbt.Microscope 

3466 The microscope object for which to create the step. 

3467 step_name : str 

3468 The name of the step in the .yml file. 

3469 step_settings : dict 

3470 The dictionary containing the step settings. 

3471 general_settings : tbt.GeneralSettings 

3472 The general settings object. 

3473 yml_format : tbt.YMLFormatVersion 

3474 The format specified by the version of the .yml file. 

3475 

3476 Returns 

3477 ------- 

3478 tbt.Step 

3479 The step object. 

3480 

3481 Raises 

3482 ------ 

3483 NotImplementedError 

3484 If the step type is unsupported. 

3485 KeyError 

3486 If required settings are missing or invalid. 

3487 """ 

3488 

3489 # parsing settings 

3490 step_type_value = step_settings[yml_format.step_general_key][ 

3491 yml_format.step_type_key 

3492 ] 

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

3494 raise NotImplementedError( 

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

3496 ) 

3497 step_type = tbt.StepType(step_type_value) 

3498 

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

3500 if not isinstance(step_number, int) or (step_number < 1): 

3501 raise KeyError( 

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

3503 ) 

3504 step_frequency = step_settings[yml_format.step_general_key][ 

3505 yml_format.step_frequency_key 

3506 ] 

3507 if not isinstance(step_frequency, int) or (step_frequency < 1): 

3508 raise KeyError( 

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

3510 ) 

3511 stage_db = step_settings[yml_format.step_general_key][ 

3512 yml_format.step_stage_settings_key 

3513 ] 

3514 

3515 # check and validate stage 

3516 stage_settings = stage_position_settings( 

3517 microscope=microscope, 

3518 step_name=step_name, 

3519 general_settings=general_settings, 

3520 step_stage_settings=stage_db, 

3521 yml_format=yml_format, 

3522 ) 

3523 

3524 # TODO 

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

3526 if step_type == tbt.StepType.EBSD: 

3527 operation_settings = ebsd( 

3528 microscope=microscope, 

3529 step_settings=step_settings, 

3530 step_name=step_name, 

3531 yml_format=yml_format, 

3532 ) 

3533 if step_type == tbt.StepType.EDS: 

3534 operation_settings = eds( 

3535 microscope=microscope, 

3536 step_settings=step_settings, 

3537 step_name=step_name, 

3538 yml_format=yml_format, 

3539 ) 

3540 if step_type == tbt.StepType.IMAGE: 

3541 operation_settings = image( 

3542 microscope=microscope, 

3543 step_settings=step_settings, 

3544 step_name=step_name, 

3545 yml_format=yml_format, 

3546 ) 

3547 if step_type == tbt.StepType.LASER: 

3548 operation_settings = laser( 

3549 microscope=microscope, 

3550 step_settings=step_settings, 

3551 step_name=step_name, 

3552 yml_format=yml_format, 

3553 ) 

3554 if step_type == tbt.StepType.CUSTOM: 

3555 operation_settings = custom( 

3556 microscope=microscope, 

3557 step_settings=step_settings, 

3558 step_name=step_name, 

3559 yml_format=yml_format, 

3560 ) 

3561 if step_type == tbt.StepType.FIB: 

3562 operation_settings = fib( 

3563 microscope=microscope, 

3564 step_settings=step_settings, 

3565 step_name=step_name, 

3566 yml_format=yml_format, 

3567 ) 

3568 

3569 step_object = tbt.Step( 

3570 type=step_type, 

3571 name=step_name, 

3572 number=step_number, 

3573 frequency=step_frequency, 

3574 stage=stage_settings, 

3575 operation_settings=operation_settings, 

3576 ) 

3577 

3578 return step_object