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

246 statements  

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

1#!/usr/bin/python3 

2""" 

3Laser Module 

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

5 

6This module contains functions for managing and controlling the laser in the microscope, including setting laser parameters, checking laser connections, and performing laser operations. 

7 

8Functions 

9--------- 

10laser_state_to_db(state: tbt.LaserState) -> dict 

11 Convert a laser state object into a flattened dictionary. 

12 

13laser_connected() -> bool 

14 Check if the laser is connected. 

15 

16_device_connections() -> tbt.DeviceStatus 

17 Check the connection status of the laser and associated external devices. 

18 

19pattern_mode(mode: tbt.LaserPatternMode) -> bool 

20 Set the laser pattern mode. 

21 

22pulse_energy_uj(energy_uj: float, energy_tol_uj: float = Constants.laser_energy_tol_uj, delay_s: float = 3.0) -> bool 

23 Set the pulse energy on the laser. 

24 

25pulse_divider(divider: int, delay_s: float = Constants.laser_delay_s) -> bool 

26 Set the pulse divider on the laser. 

27 

28set_wavelength(wavelength: tbt.LaserWavelength, frequency_khz: float = 60, timeout_s: int = 20, num_attempts: int = 2, delay_s: int = 5) -> bool 

29 Set the wavelength and frequency of the laser. 

30 

31read_power(delay_s: float = Constants.laser_delay_s) -> float 

32 Measure the laser power in watts. 

33 

34insert_shutter(microscope: tbt.Microscope) -> bool 

35 Insert the laser shutter. 

36 

37retract_shutter(microscope: tbt.Microscope) -> bool 

38 Retract the laser shutter. 

39 

40pulse_polarization(polarization: tbt.LaserPolarization, wavelength: tbt.LaserWavelength) -> bool 

41 Configure the polarization of the laser light. 

42 

43pulse_settings(pulse: tbt.LaserPulse) -> bool 

44 Apply the pulse settings to the laser. 

45 

46retract_laser_objective() -> bool 

47 Retract the laser objective to a safe position. 

48 

49objective_position(position_mm: float, tolerance_mm=Constants.laser_objective_tolerance_mm) -> bool 

50 Move the laser objective to the requested position. 

51 

52beam_shift(shift_um: tbt.Point, shift_tolerance_um: float = Constants.laser_beam_shift_tolerance_um) -> bool 

53 Adjust the laser beam shift to the specified values. 

54 

55create_pattern(pattern: tbt.LaserPattern) -> bool 

56 Create a laser pattern and check that it is set correctly. 

57 

58apply_laser_settings(image_beam: tbt.Beam, settings: tbt.LaserSettings) -> bool 

59 Apply the laser settings to the current patterning. 

60 

61execute_patterning() -> bool 

62 Execute the laser patterning. 

63 

64mill_region(settings: tbt.LaserSettings) -> bool 

65 Perform laser milling on a specified region. 

66 

67laser_operation(step: tbt.Step, general_settings: tbt.GeneralSettings, slice_number: int) -> bool 

68 Perform a laser operation based on the specified step and settings. 

69 

70map_ebsd() -> bool 

71 Start an EBSD map and ensure it takes the minimum expected time. 

72 

73map_eds() -> bool 

74 Start an EDS map and ensure it takes the minimum expected time. 

75""" 

76 

77# Default python modules 

78import time 

79import contextlib, io 

80import math 

81 

82try: 

83 import Laser.PythonControl as tfs_laser 

84 

85 print("Laser PythonControl API imported.") 

86except: 

87 print("WARNING: Laser API not imported!") 

88 print("\tLaser control, as well as EBSD and EDS control are unavailable.") 

89 

90# 3rd party .whl modules 

91 

92# Local scripts 

93from pytribeam.constants import Constants 

94import pytribeam.factory as factory 

95import pytribeam.types as tbt 

96import pytribeam.utilities as ut 

97import pytribeam.insertable_devices as devices 

98import pytribeam.image as img 

99import pytribeam.log as log 

100 

101 

102def laser_state_to_db(state: tbt.LaserState) -> dict: 

103 """ 

104 This function converts a `LaserState` object into a flattened dictionary representation. 

105 

106 Parameters 

107 ---------- 

108 state : tbt.LaserState 

109 The laser state object to convert. 

110 

111 Returns 

112 ------- 

113 dict 

114 A flattened dictionary representation of the laser state. 

115 """ 

116 db = {} 

117 

118 db["wavelength_nm"] = state.wavelength_nm 

119 db["frequency_khz"] = state.frequency_khz 

120 db["pulse_divider"] = state.pulse_divider 

121 db["pulse_energy_uj"] = state.pulse_energy_uj 

122 db["objective_position_mm"] = state.objective_position_mm 

123 db["expected_pattern_duration_s"] = state.expected_pattern_duration_s 

124 

125 beam_shift = state.beam_shift_um 

126 db["beam_shift_um_x"] = beam_shift.x 

127 db["beam_shift_um_y"] = beam_shift.y 

128 

129 # we can name these differently depending on the needs of the GUI 

130 pattern = state.pattern 

131 db["laser_pattern_mode"] = pattern.mode.value 

132 db["laser_pattern_rotation_deg"] = pattern.rotation_deg 

133 db["laser_pattern_pulses_per_pixel"] = pattern.pulses_per_pixel 

134 db["laser_pattern_pixel_dwell_ms"] = pattern.pixel_dwell_ms 

135 

136 geometry = pattern.geometry 

137 db["passes"] = geometry.passes 

138 db["laser_scan_type"] = geometry.scan_type.value 

139 db["geometry_type"] = geometry.type.value 

140 

141 if geometry.type == tbt.LaserPatternType.BOX: 

142 db["size_x_um"] = geometry.size_x_um 

143 db["size_y_um"] = geometry.size_y_um 

144 db["pitch_x_um"] = geometry.pitch_x_um 

145 db["pitch_y_um"] = geometry.pitch_y_um 

146 db["coordinate_ref"] = geometry.coordinate_ref 

147 

148 if geometry.type == tbt.LaserPatternType.LINE: 

149 db["size_um"] = geometry.size_um 

150 db["pitch_um"] = geometry.pitch_um 

151 

152 return db 

153 

154 

155def laser_connected() -> bool: 

156 """ 

157 Check if the laser is connected. 

158 

159 This function tests the connection to the laser and returns True if the connection is successful. 

160 

161 Returns 

162 ------- 

163 bool 

164 True if the laser is connected, False otherwise. 

165 """ 

166 connect_msg = "Connection test successful.\n" 

167 laser_status = io.StringIO() 

168 try: 

169 with contextlib.redirect_stdout(laser_status): 

170 tfs_laser.TestConnection() 

171 except: 

172 return False 

173 else: 

174 if laser_status.getvalue() == connect_msg: 

175 return True 

176 return False 

177 

178 

179def _device_connections() -> tbt.DeviceStatus: 

180 """ 

181 Check the connection status of the laser and associated external devices. 

182 

183 This function checks the connection status of the laser, EBSD, and EDS devices. It is meant to be a quick tool for the GUI and does not provide additional information for troubleshooting. 

184 

185 Returns 

186 ------- 

187 tbt.DeviceStatus 

188 The connection status of the laser, EBSD, and EDS devices. 

189 """ 

190 # laser must be connected to connect with other devices: 

191 if not laser_connected(): 

192 laser = tbt.RetractableDeviceState.ERROR 

193 ebsd = tbt.RetractableDeviceState.ERROR 

194 eds = tbt.RetractableDeviceState.ERROR 

195 else: 

196 laser = tbt.RetractableDeviceState.CONNECTED 

197 ebsd = devices.connect_EBSD() # retractable device state 

198 eds = devices.connect_EDS() # retractable device state 

199 

200 return tbt.DeviceStatus( 

201 laser=laser, 

202 ebsd=ebsd, 

203 eds=eds, 

204 ) 

205 

206 

207def pattern_mode(mode: tbt.LaserPatternMode) -> bool: 

208 """ 

209 Set the laser pattern mode. 

210 

211 This function sets the laser pattern mode and verifies that it has been set correctly. 

212 

213 Parameters 

214 ---------- 

215 mode : tbt.LaserPatternMode 

216 The laser pattern mode to set. 

217 

218 Returns 

219 ------- 

220 bool 

221 True if the pattern mode is set correctly. 

222 

223 Raises 

224 ------ 

225 SystemError 

226 If the pattern mode cannot be set correctly. 

227 """ 

228 tfs_laser.Patterning_Mode(mode.value) 

229 laser_state = factory.active_laser_state() 

230 if laser_state.pattern.mode != mode: 

231 raise SystemError("Unable to correctly set pattern mode.") 

232 return True 

233 

234 

235def pulse_energy_uj( 

236 energy_uj: float, 

237 energy_tol_uj: float = Constants.laser_energy_tol_uj, 

238 delay_s: float = 3.0, 

239) -> bool: 

240 """ 

241 Set the pulse energy on the laser. 

242 

243 This function sets the pulse energy on the laser and verifies that it has been set correctly. It should be done after setting the pulse divider. 

244 

245 Parameters 

246 ---------- 

247 energy_uj : float 

248 The pulse energy to set in microjoules. 

249 energy_tol_uj : float, optional 

250 The tolerance for the pulse energy in microjoules (default is Constants.laser_energy_tol_uj). 

251 delay_s : float, optional 

252 The delay in seconds after setting the pulse energy (default is 3.0 seconds). 

253 

254 Returns 

255 ------- 

256 bool 

257 True if the pulse energy is set correctly. 

258 

259 Raises 

260 ------ 

261 ValueError 

262 If the pulse energy cannot be set correctly. 

263 """ 

264 tfs_laser.Laser_SetPulseEnergy_MicroJoules(energy_uj) 

265 time.sleep(delay_s) 

266 laser_state = factory.active_laser_state() 

267 if not ut.in_interval( 

268 val=laser_state.pulse_energy_uj, 

269 limit=tbt.Limit( 

270 min=energy_uj - energy_tol_uj, 

271 max=energy_uj + energy_tol_uj, 

272 ), 

273 type=tbt.IntervalType.CLOSED, 

274 ): 

275 raise ValueError( 

276 f"Could not properly set pulse energy, requested '{energy_uj}' uJ", 

277 f"Current settings is {round(laser_state.pulse_energy_uj, 3)} uJ", 

278 ) 

279 return True 

280 

281 

282def pulse_divider( 

283 divider: int, 

284 delay_s: float = Constants.laser_delay_s, 

285) -> bool: 

286 """ 

287 Set the pulse divider on the laser. 

288 

289 This function sets the pulse divider on the laser and verifies that it has been set correctly. 

290 

291 Parameters 

292 ---------- 

293 divider : int 

294 The pulse divider to set. 

295 delay_s : float, optional 

296 The delay in seconds after setting the pulse divider (default is Constants.laser_delay_s). 

297 

298 Returns 

299 ------- 

300 bool 

301 True if the pulse divider is set correctly. 

302 

303 Raises 

304 ------ 

305 ValueError 

306 If the pulse divider cannot be set correctly. 

307 """ 

308 tfs_laser.Laser_PulseDivider(divider) 

309 time.sleep(delay_s) 

310 laser_state = factory.active_laser_state() 

311 if laser_state.pulse_divider != divider: 

312 raise ValueError( 

313 f"Could not properly set pulse divider, requested '{divider}'", 

314 f"Current settings have a divider of {laser_state.pulse_divider}.", 

315 ) 

316 return True 

317 

318 

319def set_wavelength( 

320 wavelength: tbt.LaserWavelength, 

321 frequency_khz: float = 60, # make constnat 

322 timeout_s: int = 20, # 120, # make constant 

323 num_attempts: int = 2, # TODO make a constant 

324 delay_s: int = 5, # make a constant 

325) -> bool: 

326 """ 

327 Set the wavelength and frequency of the laser. 

328 

329 This function sets the wavelength and frequency of the laser and verifies that they have been set correctly. 

330 

331 Parameters 

332 ---------- 

333 wavelength : tbt.LaserWavelength 

334 The wavelength to set. 

335 frequency_khz : float, optional 

336 The frequency to set in kHz (default is 60 kHz). 

337 timeout_s : int, optional 

338 The timeout in seconds for each attempt (default is 20 seconds). 

339 num_attempts : int, optional 

340 The number of attempts to set the wavelength and frequency (default is 2). 

341 delay_s : int, optional 

342 The delay in seconds between checks (default is 5 seconds). 

343 

344 Returns 

345 ------- 

346 bool 

347 True if the wavelength and frequency are set correctly, False otherwise. 

348 """ 

349 

350 def correct_preset(laser_state: tbt.LaserState): 

351 if laser_state.wavelength_nm == wavelength: 

352 return math.isclose(laser_state.frequency_khz, frequency_khz, rel_tol=0.05) 

353 # TODO use constant for tolerance): 

354 return False 

355 

356 for _ in range(num_attempts): 

357 if correct_preset(factory.active_laser_state()): 

358 return True 

359 print("Adjusting preset...") 

360 tfs_laser.Laser_SetPreset( 

361 wavelength_nm=wavelength.value, frequency_kHz=frequency_khz 

362 ) 

363 time_remaining = timeout_s 

364 while time_remaining > 0: 

365 laser_state = factory.active_laser_state() 

366 # print(time_remaining, laser_state.frequency_khz) 

367 if correct_preset(laser_state=laser_state): 

368 return True 

369 time.sleep(delay_s) 

370 time_remaining -= delay_s 

371 

372 return False 

373 

374 

375def read_power(delay_s: float = Constants.laser_delay_s) -> float: 

376 """ 

377 Measure the laser power in watts. 

378 

379 This function measures the laser power using an external power meter. 

380 

381 Parameters 

382 ---------- 

383 delay_s : float, optional 

384 The delay in seconds before reading the power (default is Constants.laser_delay_s). 

385 

386 Returns 

387 ------- 

388 float 

389 The measured laser power in watts. 

390 """ 

391 tfs_laser.Laser_ExternalPowerMeter_PowerMonitoringON() 

392 tfs_laser.Laser_ExternalPowerMeter_SetZeroOffset() 

393 tfs_laser.Laser_FireContinuously_Start() 

394 time.sleep(delay_s) 

395 power = tfs_laser.Laser_ExternalPowerMeter_ReadPower() 

396 tfs_laser.Laser_FireContinuously_Stop() 

397 tfs_laser.Laser_ExternalPowerMeter_PowerMonitoringOFF() 

398 return power 

399 

400 

401def insert_shutter(microscope: tbt.Microscope) -> bool: 

402 """ 

403 Insert the laser shutter. 

404 

405 This function inserts the laser shutter and verifies that it has been inserted correctly. 

406 

407 Parameters 

408 ---------- 

409 microscope : tbt.Microscope 

410 The microscope object for which to insert the laser shutter. 

411 

412 Returns 

413 ------- 

414 bool 

415 True if the laser shutter is successfully inserted. 

416 

417 Raises 

418 ------ 

419 SystemError 

420 If the laser shutter cannot be inserted. 

421 """ 

422 devices.CCD_view(microscope=microscope) 

423 if tfs_laser.Shutter_GetState() != "Inserted": 

424 tfs_laser.Shutter_Insert() 

425 state = tfs_laser.Shutter_GetState() 

426 if state != "Inserted": 

427 raise SystemError( 

428 f"Could not insert laser shutter, current laser shutter state is '{state}'." 

429 ) 

430 devices.CCD_pause(microscope=microscope) 

431 return True 

432 

433 

434def retract_shutter(microscope: tbt.Microscope) -> bool: 

435 """ 

436 Retract the laser shutter. 

437 

438 This function retracts the laser shutter and verifies that it has been retracted correctly. 

439 

440 Parameters 

441 ---------- 

442 microscope : tbt.Microscope 

443 The microscope object for which to retract the laser shutter. 

444 

445 Returns 

446 ------- 

447 bool 

448 True if the laser shutter is successfully retracted. 

449 

450 Raises 

451 ------ 

452 SystemError 

453 If the laser shutter cannot be retracted. 

454 """ 

455 devices.CCD_view(microscope=microscope) 

456 if tfs_laser.Shutter_GetState() != "Retracted": 

457 tfs_laser.Shutter_Retract() 

458 state = tfs_laser.Shutter_GetState() 

459 if state != "Retracted": 

460 raise SystemError( 

461 f"Could not retract laser shutter, current laser shutter state is '{state}'." 

462 ) 

463 devices.CCD_pause(microscope=microscope) 

464 return True 

465 

466 

467def pulse_polarization( 

468 polarization: tbt.LaserPolarization, wavelength: tbt.LaserWavelength 

469) -> bool: 

470 """ 

471 Configure the polarization of the laser light. 

472 

473 This function sets the polarization of the laser light based on the specified polarization and wavelength. The polarization is controlled via "FlipperConfiguration", which takes the following values: 

474 - Waveplate_None switches to Vert. (P) 

475 - Waveplate_1030 switches to Horiz. (S) 

476 - Waveplate_515 switches to Horiz. (S) 

477 

478 Parameters 

479 ---------- 

480 polarization : tbt.LaserPolarization 

481 The desired polarization of the laser light. 

482 wavelength : tbt.LaserWavelength 

483 The wavelength of the laser light. 

484 

485 Returns 

486 ------- 

487 bool 

488 True if the polarization is set correctly. 

489 

490 Raises 

491 ------ 

492 KeyError 

493 If the laser wavelength or pulse polarization is invalid. 

494 """ 

495 if polarization == tbt.LaserPolarization.VERTICAL: 

496 tfs_laser.FlipperConfiguration("Waveplate_None") 

497 return True 

498 elif polarization == tbt.LaserPolarization.HORIZONTAL: 

499 match_db = { 

500 tbt.LaserWavelength.NM_1030: "Waveplate_1030", 

501 tbt.LaserWavelength.NM_515: "Waveplate_515", 

502 } 

503 try: 

504 tfs_laser.FlipperConfiguration(match_db[wavelength]) 

505 except KeyError: 

506 raise KeyError( 

507 f"Invalid laser wavelength, valid options are {[i.value for i in tbt.LaserWavelength]}" 

508 ) 

509 return True 

510 else: 

511 raise KeyError( 

512 f"Invalid pulse polarization, valid options are {[i.value for i in tbt.LaserPolarization]}" 

513 ) 

514 

515 

516def pulse_settings(pulse: tbt.LaserPulse) -> True: 

517 """ 

518 Apply the pulse settings to the laser. 

519 

520 This function applies the specified pulse settings to the laser, including wavelength, pulse divider, pulse energy, and polarization. 

521 

522 Parameters 

523 ---------- 

524 pulse : tbt.LaserPulse 

525 The pulse settings to apply. 

526 

527 Returns 

528 ------- 

529 bool 

530 True if the pulse settings are applied correctly. 

531 """ 

532 active_state = factory.active_laser_state() 

533 if pulse.wavelength_nm != active_state.wavelength_nm: 

534 # wavelength settings 

535 set_wavelength(wavelength=pulse.wavelength_nm) 

536 pulse_divider(divider=pulse.divider) 

537 pulse_energy_uj(energy_uj=pulse.energy_uj) 

538 pulse_polarization(polarization=pulse.polarization, wavelength=pulse.wavelength_nm) 

539 return True 

540 

541 

542def retract_laser_objective() -> bool: 

543 """ 

544 Retract the laser objective to a safe position. 

545 

546 This function retracts the laser objective to a predefined safe position. 

547 

548 Returns 

549 ------- 

550 bool 

551 True if the laser objective is successfully retracted. 

552 """ 

553 objective_position(position_mm=Constants.laser_objective_retracted_mm) 

554 return True 

555 

556 

557def objective_position( 

558 position_mm: float, 

559 tolerance_mm=Constants.laser_objective_tolerance_mm, 

560) -> bool: 

561 """ 

562 Move the laser objective to the requested position. 

563 

564 This function moves the laser objective to the specified position and verifies that it has been moved correctly. 

565 

566 Parameters 

567 ---------- 

568 position_mm : float 

569 The desired position of the laser objective in millimeters. 

570 tolerance_mm : float, optional 

571 The tolerance for the laser objective position in millimeters (default is Constants.laser_objective_tolerance_mm). 

572 

573 Returns 

574 ------- 

575 bool 

576 True if the laser objective is moved to the requested position correctly. 

577 

578 Raises 

579 ------ 

580 ValueError 

581 If the requested position is out of range. 

582 SystemError 

583 If the laser objective cannot be moved to the requested position. 

584 """ 

585 tfs_laser.LIP_UnlockZ() 

586 

587 if not ut.in_interval( 

588 val=position_mm, 

589 limit=Constants.laser_objective_limit_mm, 

590 type=tbt.IntervalType.CLOSED, 

591 ): 

592 raise ValueError( 

593 f"Requested laser objective position of {position_mm} mm is out of range. Laser objective can travel from {Constants.laser_objective_limit_mm.min} to {Constants.laser_objective_limit_mm.max} mm." 

594 ) 

595 

596 for _ in range(2): 

597 if ut.in_interval( 

598 val=tfs_laser.LIP_GetZPosition(), 

599 limit=tbt.Limit( 

600 min=position_mm - tolerance_mm, max=position_mm + tolerance_mm 

601 ), 

602 type=tbt.IntervalType.CLOSED, 

603 ): 

604 return True 

605 tfs_laser.LIP_SetZPosition(position_mm, asynchronously=False) 

606 

607 raise SystemError( 

608 f"Unable to move laser injection port objective to requested position of {position_mm} +/- {tolerance_mm} mm.", 

609 f"Currently at {tfs_laser.LIP_GetZPosition()} mm.", 

610 ) 

611 

612 

613def _shift_axis( 

614 target: float, 

615 current: float, 

616 tolerance: float, 

617 axis: str, 

618) -> bool: 

619 """ 

620 Helper function for beam shift. 

621 

622 This function adjusts the beam shift for the specified axis to the target value within the given tolerance. 

623 

624 Parameters 

625 ---------- 

626 target : float 

627 The target value for the beam shift. 

628 current : float 

629 The current value of the beam shift. 

630 tolerance : float 

631 The tolerance for the beam shift. 

632 axis : str 

633 The axis to adjust ("X" or "Y"). 

634 

635 Returns 

636 ------- 

637 bool 

638 True if the beam shift is adjusted to the target value correctly, False otherwise. 

639 """ 

640 for _ in range(2): 

641 if ut.in_interval( 

642 val=current, 

643 limit=tbt.Limit( 

644 min=target - tolerance, 

645 max=target + tolerance, 

646 ), 

647 type=tbt.IntervalType.CLOSED, 

648 ): 

649 return True 

650 if axis == "X": 

651 tfs_laser.BeamShift_Set_X(value=target) 

652 current = tfs_laser.BeamShift_Get_X() 

653 if axis == "Y": 

654 tfs_laser.BeamShift_Set_Y(value=target) 

655 current = tfs_laser.BeamShift_Get_Y() 

656 

657 return False 

658 

659 

660def beam_shift( 

661 shift_um: tbt.Point, 

662 shift_tolerance_um: float = Constants.laser_beam_shift_tolerance_um, 

663) -> bool: 

664 """ 

665 Adjust the laser beam shift to the specified values. 

666 

667 This function adjusts the laser beam shift to the specified x and y values within the given tolerance. 

668 

669 Parameters 

670 ---------- 

671 shift_um : tbt.Point 

672 The target beam shift values in micrometers. 

673 shift_tolerance_um : float, optional 

674 The tolerance for the beam shift in micrometers (default is Constants.laser_beam_shift_tolerance_um). 

675 

676 Returns 

677 ------- 

678 bool 

679 True if the beam shift is adjusted to the target values correctly. 

680 

681 Raises 

682 ------ 

683 ValueError 

684 If the beam shift cannot be adjusted to the target values. 

685 """ 

686 current_shift_x = tfs_laser.BeamShift_Get_X() 

687 current_shift_y = tfs_laser.BeamShift_Get_Y() 

688 

689 if not ( 

690 _shift_axis( 

691 target=shift_um.x, 

692 current=current_shift_x, 

693 tolerance=shift_tolerance_um, 

694 axis="X", 

695 ) 

696 and _shift_axis( 

697 target=shift_um.y, 

698 current=current_shift_y, 

699 tolerance=shift_tolerance_um, 

700 axis="Y", 

701 ) 

702 ): 

703 raise ValueError( 

704 f"Unable to set laser beam shift. Requested beam shift of (x,y) = ({shift_um.x} um,{shift_um.y} um,), but current beam shift is ({tfs_laser.BeamShift_Get_X()} um, {tfs_laser.BeamShift_Get_Y()} um)." 

705 ) 

706 return True 

707 

708 

709def create_pattern(pattern: tbt.LaserPattern): 

710 """ 

711 Create a laser pattern and check that it is set correctly. 

712 

713 This function creates a laser pattern based on the specified pattern settings and verifies that it has been set correctly. 

714 

715 Parameters 

716 ---------- 

717 pattern : tbt.LaserPattern 

718 The laser pattern settings to create. 

719 

720 Returns 

721 ------- 

722 bool 

723 True if the pattern is created and set correctly. 

724 

725 Raises 

726 ------ 

727 ValueError 

728 If the pattern geometry type is unsupported. 

729 SystemError 

730 If the pattern cannot be set correctly. 

731 """ 

732 pattern_mode(mode=pattern.mode) 

733 

734 # check if pattern is empty or not 

735 if isinstance(pattern.geometry, tbt.LaserBoxPattern): 

736 box = pattern.geometry 

737 tfs_laser.Patterning_CreatePattern_Box( 

738 sizeX_um=box.size_x_um, 

739 sizeY_um=box.size_y_um, 

740 pitchX_um=box.pitch_x_um, 

741 pitchY_um=box.pitch_y_um, 

742 dwellTime_ms=pattern.pixel_dwell_ms, 

743 passes_int=box.passes, 

744 pulsesPerPixel_int=pattern.pulses_per_pixel, 

745 scanrotation_degrees=pattern.rotation_deg, 

746 scantype_string=box.scan_type.value, # cast enum to string 

747 coordinateReference_string=box.coordinate_ref.value, # cast enum to string 

748 ) 

749 elif isinstance(pattern.geometry, tbt.LaserLinePattern): 

750 line = pattern.geometry 

751 tfs_laser.Patterning_CreatePattern_Line( 

752 sizeX_um=line.size_um, 

753 pitchX_um=line.pitch_um, 

754 dwellTime_ms=pattern.pixel_dwell_ms, 

755 passes_int=line.passes, 

756 pulsesPerPixel_int=pattern.pulses_per_pixel, 

757 scanrotation_degrees=pattern.rotation_deg, 

758 scantype_string=line.scan_type.value, # cast enum to string 

759 ) 

760 else: 

761 raise ValueError( 

762 f"Unsupported pattern geometry of type '{type(pattern.geometry)}'. Supported types are {tbt.LaserLinePattern, tbt.LaserBoxPattern}" 

763 ) 

764 laser_state = factory.active_laser_state() 

765 if laser_state.pattern != pattern: 

766 raise SystemError("Unable to correctly set Pattern.") 

767 return True 

768 

769 

770def apply_laser_settings(image_beam: tbt.Beam, settings: tbt.LaserSettings) -> bool: 

771 """ 

772 Apply the laser settings to the current patterning. 

773 

774 This function applies the specified laser settings to the current patterning, including beam scan rotation, pulse settings, objective position, beam shift, and patterning settings. 

775 

776 Parameters 

777 ---------- 

778 image_beam : tbt.Beam 

779 The beam settings for the image. 

780 settings : tbt.LaserSettings 

781 The laser settings to apply. 

782 

783 Returns 

784 ------- 

785 bool 

786 True if the laser settings are applied correctly. 

787 """ 

788 microscope = settings.microscope 

789 

790 # forces rotation of electron beam for laser (TFS required) 

791 img.beam_scan_rotation( 

792 beam=image_beam, 

793 microscope=microscope, 

794 rotation_deg=Constants.image_scan_rotation_for_laser_deg, 

795 ) 

796 # pulse settings 

797 pulse_settings(pulse=settings.pulse) 

798 

799 # objective position 

800 objective_position(settings.objective_position_mm) 

801 

802 # beam shift 

803 beam_shift(settings.beam_shift_um) 

804 

805 # apply patterning settings 

806 create_pattern(pattern=settings.pattern) 

807 

808 return True 

809 

810 

811def execute_patterning() -> bool: 

812 """ 

813 Execute the laser patterning. 

814 

815 This function starts the laser patterning process. 

816 

817 Returns 

818 ------- 

819 bool 

820 True if the patterning process is started successfully. 

821 """ 

822 tfs_laser.Patterning_Start() 

823 

824 return True 

825 

826 

827### main methods 

828 

829 

830def mill_region( 

831 settings: tbt.LaserSettings, 

832) -> bool: 

833 """ 

834 Perform laser milling on a specified region. 

835 

836 This function performs laser milling on a specified region using the provided laser settings. It checks the laser connection, applies the laser settings, inserts the shutter, executes the patterning, retracts the shutter, and resets the scan rotation. 

837 

838 Parameters 

839 ---------- 

840 settings : tbt.LaserSettings 

841 The laser settings to use for milling. 

842 

843 Returns 

844 ------- 

845 bool 

846 True if the milling process is completed successfully. 

847 

848 Raises 

849 ------ 

850 SystemError 

851 If the laser is not connected. 

852 """ 

853 # check connection 

854 if not laser_connected(): 

855 raise SystemError("Laser is not connected") 

856 

857 microscope = settings.microscope 

858 # initial_scan_rotation of ebeam 

859 devices.device_access(microscope=microscope) 

860 active_beam = factory.active_beam_with_settings(microscope=microscope) 

861 scan_settings = factory.active_scan_settings(microscope=microscope) 

862 initial_scan_rotation_deg = scan_settings.rotation_deg 

863 

864 # apply laser settings 

865 apply_laser_settings( 

866 image_beam=active_beam, 

867 settings=settings, 

868 ) 

869 

870 # insert shutter 

871 insert_shutter(microscope=microscope) 

872 

873 # execute patterning 

874 execute_patterning() 

875 

876 # retract shutter 

877 retract_shutter(microscope=microscope) 

878 time.sleep(1) 

879 

880 # reset scan rotation 

881 img.beam_scan_rotation( 

882 beam=active_beam, 

883 microscope=microscope, 

884 rotation_deg=initial_scan_rotation_deg, 

885 ) 

886 

887 return True 

888 

889 

890def laser_operation( 

891 step: tbt.Step, general_settings: tbt.GeneralSettings, slice_number: int 

892) -> bool: 

893 """ 

894 Perform a laser operation based on the specified step and settings. 

895 

896 This function performs a laser operation using the provided step and general settings. It logs the laser power before and after the operation, and performs the milling process. 

897 

898 Parameters 

899 ---------- 

900 step : tbt.Step 

901 The step object containing the operation settings. 

902 general_settings : tbt.GeneralSettings 

903 The general settings object. 

904 slice_number : int 

905 The slice number for the operation. 

906 

907 Returns 

908 ------- 

909 bool 

910 True if the laser operation is completed successfully. 

911 """ 

912 # log laser power before 

913 laser_power_w = read_power() 

914 log.laser_power( 

915 step_number=step.number, 

916 step_name=step.name, 

917 slice_number=slice_number, 

918 log_filepath=general_settings.log_filepath, 

919 dataset_name=Constants.pre_lasing_dataset_name, 

920 power_w=laser_power_w, 

921 ) 

922 

923 mill_region(settings=step.operation_settings) 

924 

925 # log laser power after 

926 laser_power_w = read_power() 

927 log.laser_power( 

928 step_number=step.number, 

929 step_name=step.name, 

930 slice_number=slice_number, 

931 log_filepath=general_settings.log_filepath, 

932 dataset_name=Constants.post_lasing_dataset_name, 

933 power_w=laser_power_w, 

934 ) 

935 

936 return True 

937 

938 

939def map_ebsd() -> bool: 

940 """ 

941 Start an EBSD map and ensure it takes the minimum expected time. 

942 

943 This function starts an EBSD map and checks that the mapping process takes at least the minimum expected time. If the mapping process is too short, it raises an error. 

944 

945 Returns 

946 ------- 

947 bool 

948 True if the EBSD mapping is completed successfully. 

949 

950 Raises 

951 ------ 

952 ValueError 

953 If the mapping process does not take the minimum expected time. 

954 """ 

955 start_time = time.time() 

956 tfs_laser.EBSD_StartMap() 

957 time.sleep(1) 

958 end_time = time.time() 

959 map_time = end_time - start_time 

960 if map_time < Constants.min_map_time_s: 

961 raise ValueError( 

962 f"Mapping did not take minimum expected time of {Constants.min_map_time_s} seconds, please reset EBSD mapping software" 

963 ) 

964 print(f"\t\tMapping Complete in {int(map_time)} seconds.") 

965 return True 

966 

967 

968def map_eds() -> bool: 

969 """ 

970 Start an EDS map and ensure it takes the minimum expected time. 

971 

972 This function starts an EDS map and checks that the mapping process takes at least the minimum expected time. If the mapping process is too short, it raises an error. 

973 

974 Returns 

975 ------- 

976 bool 

977 True if the EDS mapping is completed successfully. 

978 

979 Raises 

980 ------ 

981 ValueError 

982 If the mapping process does not take the minimum expected time. 

983 """ 

984 start_time = time.time() 

985 tfs_laser.EDS_StartMap() 

986 time.sleep(1) 

987 end_time = time.time() 

988 map_time = end_time - start_time 

989 if map_time < Constants.min_map_time_s: 

990 raise ValueError( 

991 f"Mapping did not take minimum expected time of {Constants.min_map_time_s} seconds, please reset EDS mapping software" 

992 ) 

993 print(f"\t\tMapping Complete in {int(map_time)} seconds.") 

994 return True