Coverage for src/pytribeam/insertable_devices.py: 85%

244 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-10-23 20:42 +0000

1#!/usr/bin/python3 

2"""Insertable-device, detector, CCD-view, and specimen-current utilities. 

3 

4This module provides utilities for controlling insertable microscope devices 

5used during `pytribeam` workflows. It handles built-in microscope detectors 

6through the microscope/AutoScript interface and EBSD/EDS camera control through 

7the external Laser API interface when available. 

8 

9The main responsibilities of this module are: 

10 

11- checking whether microscope detectors are insertable, 

12- querying detector and EBSD/EDS camera state, 

13- preventing insertion of detector combinations known to collide, 

14- inserting and retracting microscope detectors, 

15- inserting and retracting EBSD and EDS cameras, 

16- retracting all available insertable devices before workflow operations, 

17- using the CCD camera to visualize detector or stage motion, and 

18- measuring specimen current with the electron beam. 

19 

20Most workflow code should use `retract_all_devices`, `insert_detector`, 

21`insert_EBSD`, `insert_EDS`, `CCD_view`, `CCD_pause`, and `specimen_current` 

22rather than directly manipulating detector or camera state. 

23 

24## Device types 

25 

26This module works with two classes of insertable devices: 

27 

28| Device class | Examples | Control interface | 

29| --- | --- | --- | 

30| Microscope detectors | CBS, ABS, ETD, other AutoScript detectors | `microscope.detector` | 

31| External EBSD/EDS cameras | EBSD camera, EDS camera | Laser API bridge | 

32 

33EBSD and EDS camera functions require the external Laser API object imported 

34from `pytribeam.laser`. If that API is unavailable, EBSD/EDS insertion and 

35retraction functions will not be usable. 

36 

37## Typical usage 

38 

39Retract all available and enabled insertable devices: 

40 

41```python 

42from pytribeam import insertable_devices as devices 

43 

44devices.retract_all_devices( 

45 microscope=microscope, 

46 enable_EBSD=True, 

47 enable_EDS=True, 

48) 

49``` 

50 

51Insert a microscope detector after checking collision constraints: 

52 

53```python 

54import pytribeam.types as tbt 

55from pytribeam import insertable_devices as devices 

56 

57devices.insert_detector( 

58 microscope=microscope, 

59 detector=tbt.DetectorType.CBS, 

60) 

61``` 

62 

63Insert EBSD or EDS cameras: 

64 

65```python 

66from pytribeam import insertable_devices as devices 

67 

68devices.insert_EBSD(microscope) 

69devices.insert_EDS(microscope) 

70``` 

71 

72Use the CCD camera to visualize motion: 

73 

74```python 

75from pytribeam import insertable_devices as devices 

76 

77devices.CCD_view(microscope) 

78# move stage or insert/retract detector 

79devices.CCD_pause(microscope) 

80``` 

81 

82Measure specimen current: 

83 

84```python 

85from pytribeam import insertable_devices as devices 

86 

87current_na = devices.specimen_current(microscope) 

88``` 

89 

90## Collision handling 

91 

92`detectors_will_collide` checks requested detector insertion against known 

93disallowed detector combinations defined by `Constants.detector_collisions`. 

94This check includes both microscope-controlled detectors and EBSD/EDS cameras 

95when the external API is available. 

96 

97Insertion functions raise `SystemError` if the requested device may collide with 

98another detector or if the device cannot be inserted successfully. 

99 

100## CCD visualization 

101 

102Several insertion, retraction, and stage-motion workflows use `CCD_view` and 

103`CCD_pause` to provide visual feedback during hardware motion. These functions 

104temporarily switch to the CCD camera in a selected view quadrant and then restore 

105the initially active view. 

106 

107If the CCD camera is not available on the microscope, a warning is issued and 

108the workflow continues. 

109 

110## Specimen-current measurement 

111 

112`specimen_current` temporarily switches to the electron beam, selects the ETD 

113detector, sets the horizontal field width, acquires an image, reads the specimen 

114current, and restores the original detector and field width. 

115 

116Specimen current is returned in nanoamperes. 

117 

118> **Warning** 

119> 

120> Functions in this module can move insertable hardware inside the microscope 

121> chamber. Confirm detector positions, stage position, sample geometry, chamber 

122> clearance, and workflow state before inserting or retracting devices. 

123 

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

125""" 

126 

127__all__ = [ 

128 "detector_insertable", 

129 "detector_state", 

130 "detectors_will_collide", 

131 "device_access", 

132 "insert_EBSD", 

133 "insert_EDS", 

134 "insert_detector", 

135 "retract_all_devices", 

136 "connect_EBSD", 

137 "retract_EBSD", 

138 "connect_EDS", 

139 "retract_EDS", 

140 "retract_device", 

141 "CCD_pause", 

142 "CCD_view", 

143 "specimen_current", 

144] 

145 

146# Default python modules 

147# from functools import singledispatch 

148import time 

149import warnings 

150 

151# 3rd party module 

152 

153# Local scripts 

154import pytribeam.constants as cs 

155from pytribeam.constants import Constants 

156import pytribeam.image as img 

157 

158import pytribeam.types as tbt 

159from pytribeam.laser import tfs_laser as external 

160 

161 

162def detector_insertable( 

163 microscope: tbt.Microscope, 

164 detector: tbt.DetectorType, 

165) -> bool: 

166 """ 

167 Determine whether or not the built-in microscope detector is insertable and return its state. 

168 

169 This function checks if the specified detector is being read by Autoscript and if it is insertable. 

170 

171 ## Parameters 

172 

173 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector. 

174 - `detector` (`tbt.DetectorType`): The type of the detector to check. 

175 

176 ## Returns 

177 

178 - `bool`: True if the detector is insertable, False otherwise. 

179 

180 ## Warnings 

181 

182 - `UserWarning`: If the detector type is invalid for the currently selected device or if the detector is not found on the system. 

183 """ 

184 # check if the detector is being read by Autoscript 

185 try: 

186 # make requested detector the active detector 

187 microscope.detector.type.value = detector.value 

188 except: 

189 warnings.warn( 

190 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device 

191 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system. 

192 Detector will be assumed to not be insertable.""" 

193 ) 

194 return False 

195 

196 # check if it is insertable 

197 try: 

198 state = microscope.detector.state 

199 if state == tbt.RetractableDeviceState.STATIONARY.value: 

200 return False 

201 return True 

202 except Exception: 

203 return False 

204 

205 

206def detector_state( 

207 microscope: tbt.Microscope, 

208 detector: tbt.DetectorType, 

209) -> tbt.RetractableDeviceState: 

210 """ 

211 Determine the state of the detector, only valid if the detector is insertable. 

212 

213 This function checks if the specified detector is insertable and returns its state. 

214 

215 ## Parameters 

216 

217 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector state. 

218 - `detector` (`tbt.DetectorType`): The type of the detector to check. 

219 

220 ## Returns 

221 

222 - `tbt.RetractableDeviceState`: The state of the detector if it is insertable, None otherwise. 

223 """ 

224 # check if the detector is being read by Autoscriptdevice_access(microscope) 

225 # try: 

226 # return tbt.RetractableDeviceState(microscope.detector.state) 

227 # except Exception: 

228 # return tbt.RetractableDeviceState.STATIONARY 

229 if not detector_insertable( 

230 microscope=microscope, 

231 detector=detector, 

232 ): 

233 return tbt.RetractableDeviceState.STATIONARY 

234 return tbt.RetractableDeviceState(microscope.detector.state) 

235 

236 

237def detectors_will_collide( 

238 microscope: tbt.Microscope, 

239 detector_to_insert: tbt.DetectorType, 

240) -> bool: 

241 """ 

242 Determine if a collision may occur when inserting the specified detector. 

243 

244 This function checks if inserting the specified detector will cause a collision with any other detectors. 

245 

246 ## Parameters 

247 

248 - `microscope` (`tbt.Microscope`): The microscope object for which to check for potential collisions. 

249 - `detector_to_insert` (`tbt.DetectorType`): The type of the detector to insert. 

250 

251 ## Returns 

252 

253 - `bool`: True if a collision may occur, False otherwise. 

254 """ 

255 device_retracted = tbt.RetractableDeviceState.RETRACTED.value 

256 for detector_combo in Constants.detector_collisions: 

257 if detector_to_insert in detector_combo: 

258 for detector in detector_combo: 

259 if detector == detector_to_insert: 

260 continue 

261 if detector == tbt.DetectorType.EDS: 

262 if external.EDS_CameraStatus() != device_retracted: 

263 return True 

264 elif detector == tbt.DetectorType.EBSD: 

265 if external.EBSD_CameraStatus() != device_retracted: 265 ↛ 266line 265 didn't jump to line 266 because the condition on line 265 was never true

266 return True 

267 else: 

268 state = detector_state(microscope=microscope, detector=detector) 

269 if state.value != device_retracted: 

270 return True 

271 return False 

272 

273 

274def device_access(microscope: tbt.Microscope) -> bool: 

275 """ 

276 Switch to the upper-left quadrant and assign the electron beam as the active device. 

277 

278 This function switches the view to the upper-left quadrant and assigns the electron beam as the active device, which is the only device with access to insertable devices like the CBS/ABS detector. Other devices, like the ion beam, CCD, or Nav-Cam, do not have CBS/ABS access. 

279 

280 ## Parameters 

281 

282 - `microscope` (`tbt.Microscope`): The microscope object for which to switch the view and assign the active device. 

283 

284 ## Returns 

285 

286 - `True`: True if the operation was successful. 

287 """ 

288 img.set_view( 

289 microscope=microscope, 

290 quad=tbt.ViewQuad.UPPER_LEFT, 

291 ) 

292 img.set_beam_device( 

293 microscope=microscope, 

294 device=tbt.Device.ELECTRON_BEAM, 

295 ) 

296 return True 

297 

298 

299def insert_EBSD( 

300 microscope: tbt.Microscope, 

301) -> bool: 

302 """ 

303 Insert the EBSD camera into the microscope. 

304 

305 This function connects to the EBSD system, checks for potential collisions with other detectors, and inserts the EBSD camera if it is not already inserted. It raises an error if the EBSD camera cannot be inserted. 

306 

307 ## Parameters 

308 

309 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EBSD camera. 

310 

311 ## Returns 

312 

313 - `bool`: True if the EBSD camera is successfully inserted. 

314 

315 ## Raises 

316 

317 - `SystemError`: If a collision may occur with another detector, if the EBSD camera is in an error state, if the EBSD mapping is not idle, or if the EBSD camera cannot be inserted. 

318 """ 

319 connect_EBSD() 

320 if detectors_will_collide( 320 ↛ 324line 320 didn't jump to line 324 because the condition on line 320 was never true

321 microscope=microscope, 

322 detector_to_insert=tbt.DetectorType.EBSD, 

323 ): 

324 raise SystemError( 

325 f"""Error. Cannot insert EBSD which may collide with another detector. 

326 Disallowed detector combinations are: {Constants.detector_collisions}""" 

327 ) 

328 ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

329 map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

330 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 330 ↛ 331line 330 didn't jump to line 331 because the condition on line 330 was never true

331 raise SystemError("Error, EDS Camera in error state, workflow stopped.") 

332 if map_status != tbt.MapStatus.IDLE: 332 ↛ 333line 332 didn't jump to line 333 because the condition on line 332 was never true

333 raise SystemError( 

334 f'Error, EBSD mapping not in "{tbt.MapStatus.IDLE.value}" state.' 

335 ) 

336 if ebsd_cam_status != tbt.RetractableDeviceState.INSERTED: 336 ↛ 358line 336 didn't jump to line 358 because the condition on line 336 was always true

337 print("\tInserting EBSD Camera...") 

338 # TODO change to constants 

339 minutes_to_wait = 3 

340 timeout = minutes_to_wait * 60 # seconds 

341 waittime = 4 # seconds 

342 CCD_view(microscope=microscope) 

343 # Oxford Inst requires 2 inserts 

344 while True: 

345 external.EBSD_InsertCamera() # inserted state 

346 if ( 

347 external.EBSD_CameraStatus() 

348 == tbt.RetractableDeviceState.INSERTED.value 

349 ): 

350 break 

351 time.sleep(waittime) 

352 timeout = timeout - waittime 

353 if timeout < 1: 353 ↛ 354line 353 didn't jump to line 354 because the condition on line 353 was never true

354 warnings.warn("Warning: EBSD insert timeout. Trying to continue...") 

355 break 

356 CCD_pause(microscope=microscope) 

357 

358 new_ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

359 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED: 359 ↛ 362line 359 didn't jump to line 362 because the condition on line 359 was always true

360 print("\tEBSD Camera inserted") 

361 return True 

362 raise SystemError( 

363 f'EBSDS Camera is not inserted, currently in "{new_ebsd_cam_status}" state' 

364 ) 

365 

366 

367def insert_EDS( 

368 microscope: tbt.Microscope, 

369) -> bool: 

370 """ 

371 Insert the EDS camera into the microscope. 

372 

373 This function connects to the EDS system, checks for potential collisions with other detectors, and inserts the EDS camera if it is not already inserted. It raises an error if the EDS camera cannot be inserted. 

374 

375 ## Parameters 

376 

377 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EDS camera. 

378 

379 ## Returns 

380 

381 - `bool`: True if the EDS camera is successfully inserted. 

382 

383 ## Raises 

384 

385 - `SystemError`: If a collision may occur with another detector, if the EDS camera is in an error state, if the EDS mapping is not idle, or if the EDS camera cannot be inserted. 

386 """ 

387 connect_EDS() 

388 if detectors_will_collide( 

389 microscope=microscope, 

390 detector_to_insert=tbt.DetectorType.EDS, 

391 ): 

392 raise SystemError( 

393 f"""Error. Cannot insert EDS while CBS not in "Retracted" state. 

394 CBS detector currently in "{detector_state(microscope=microscope, detector=tbt.DetectorType.CBS).value}" state.""" 

395 ) 

396 eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

397 map_status = tbt.MapStatus(external.EDS_MappingStatus()) 

398 if eds_cam_status == tbt.RetractableDeviceState.ERROR: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true

399 raise SystemError("Error, EDS Camera in error state, workflow stopped.") 

400 if map_status != tbt.MapStatus.IDLE: 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true

401 raise SystemError( 

402 f'Error, EDS mapping not in "{tbt.MapStatus.IDLE.value}" state.' 

403 ) 

404 if eds_cam_status != tbt.RetractableDeviceState.INSERTED: 404 ↛ 410line 404 didn't jump to line 410 because the condition on line 404 was always true

405 print("\tInserting EDS Camera...") 

406 CCD_view(microscope=microscope) 

407 external.EDS_InsertCamera() 

408 CCD_pause(microscope=microscope) 

409 

410 new_eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

411 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED: 411 ↛ 414line 411 didn't jump to line 414 because the condition on line 411 was always true

412 print("\tEDS Camera inserted") 

413 return True 

414 raise SystemError( 

415 f'EDS Camera is not inserted, currently in "{new_eds_cam_status}" state' 

416 ) 

417 

418 

419def insert_detector( 

420 microscope: tbt.Microscope, 

421 detector: tbt.DetectorType, 

422 time_delay_s: float = 0.5, 

423) -> bool: 

424 """ 

425 Insert the selected detector into the microscope. 

426 

427 This function ensures the specified detector is the active one, confirms it is insertable, and inserts it if it is not already inserted. It raises an error if the detector cannot be inserted. 

428 

429 ## Parameters 

430 

431 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the detector. 

432 - `detector` (`tbt.DetectorType`): The type of the detector to insert. 

433 - `time_delay_s` (`float, optional`): The time delay in seconds after inserting the detector (default is 0.5 seconds). 

434 

435 ## Returns 

436 

437 - `bool`: True if the detector is successfully inserted. 

438 

439 ## Raises 

440 

441 - `ValueError`: If the detector is not insertable. 

442 - `SystemError`: If a collision may occur with another detector or if the detector cannot be inserted. 

443 """ 

444 # ensure detector is the active one 

445 microscope.detector.type.value = detector.value 

446 # confirm detector is insertable 

447 try: 

448 state = microscope.detector.state 

449 except: 

450 raise ValueError(f"{detector.value} detector is not insertable.") 

451 if state == tbt.RetractableDeviceState.RETRACTED.value: 451 ↛ 469line 451 didn't jump to line 469 because the condition on line 451 was always true

452 if detectors_will_collide( 

453 microscope=microscope, 

454 detector_to_insert=detector, 

455 ): 

456 raise SystemError( 

457 f"""Error. Cannot insert {detector.value} which may collide with another detector. 

458 Disallowed detector combinations are: {Constants.detector_collisions}""" 

459 ) 

460 

461 print(f"\tInserting {detector.value} detector...") 

462 CCD_view(microscope=microscope) 

463 microscope.detector.insert() 

464 time.sleep(time_delay_s) 

465 CCD_pause(microscope=microscope) 

466 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value: 466 ↛ 472line 466 didn't jump to line 472 because the condition on line 466 was always true

467 print(f"\t\t{detector.value} detector inserted.") 

468 return True 

469 elif state == tbt.RetractableDeviceState.INSERTED.value: 

470 print(f"\t{detector.value} detector is already inserted.") 

471 return True 

472 raise SystemError( 

473 f'Cannot insert {detector.value} detector, current detector state is "{state}".' 

474 ) 

475 

476 

477def retract_all_devices( 

478 microscope: tbt.Microscope, 

479 enable_EBSD: bool, 

480 enable_EDS: bool, 

481) -> bool: 

482 # TODO come up with better system for enable_EBSD_EDS 

483 """ 

484 Retract all insertable devices, including microscope detectors and EBSD/EDS detectors if integrated. 

485 

486 This function retracts all insertable devices, first retracting microscope detectors and then retracting EBSD/EDS detectors if they are integrated and enabled. 

487 

488 ## Parameters 

489 

490 - `microscope` (`tbt.Microscope`): The microscope object for accessing the Autoscript API. 

491 - `enable_EBSD` (`bool`): Whether to enable retraction of the EBSD detector. 

492 - `enable_EDS` (`bool`): Whether to enable retraction of the EDS detector. 

493 

494 ## Returns 

495 

496 - `bool`: True if all devices are successfully retracted. 

497 

498 ## Raises 

499 

500 - `None` 

501 """ 

502 print("\tRetracting devices, do not interact with xTUI during this process...") 

503 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

504 device_access(microscope) 

505 

506 for detector in microscope.detector.type.available_values: 

507 detector = tbt.DetectorType(detector) # overwrite 

508 state = detector_state( 

509 microscope=microscope, 

510 detector=detector, 

511 ) 

512 if ( 

513 state != tbt.RetractableDeviceState.STATIONARY 

514 and state != tbt.RetractableDeviceState.RETRACTED 

515 ): 

516 # if (state is not None) and (state != tbt.RetractableDeviceState.RETRACTED): 

517 retract_device( 

518 microscope=microscope, 

519 detector=detector, 

520 ) 

521 

522 # EBSD/EDS detectors: 

523 if external is None: 

524 # try: 

525 # external 

526 # except NameError: 

527 # pass 

528 print("\t\tLaser API not imported, EBSD and EDS detectors are unavailable") 

529 else: 

530 if enable_EBSD: 

531 retract_EBSD(microscope=microscope) 

532 if enable_EDS: 

533 retract_EDS(microscope=microscope) 

534 

535 # reset initial settings: 

536 img.set_view( 

537 microscope=microscope, 

538 quad=initial_view, 

539 ) 

540 print("\t\tAll available and enabled devices retracted.") 

541 return True 

542 

543 

544def connect_EBSD() -> tbt.RetractableDeviceState: 

545 """ 

546 Connect to the EBSD system and retrieve the camera status. 

547 

548 This function attempts to connect to the EBSD system and retrieve the camera status. It raises a ConnectionError if the connection fails. 

549 

550 ## Returns 

551 

552 tbt.RetractableDeviceState 

553 The status of the EBSD camera. 

554 

555 ## Raises 

556 

557 ConnectionError 

558 If the EBSD control is not connected. 

559 

560 """ 

561 try: 

562 status = external.EBSD_CameraStatus() 

563 except: 

564 raise ConnectionError( 

565 """EBSD control not connected, "Laser Control" from ThermoFisher must be open. 

566 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again.""" 

567 ) 

568 return tbt.RetractableDeviceState(status) 

569 

570 

571def retract_EBSD(microscope: tbt.Microscope) -> bool: 

572 """ 

573 Retract the EBSD camera from the microscope. 

574 

575 This function connects to the EBSD system, checks the camera status, and retracts the EBSD camera if it is not already retracted. It raises an error if the EBSD camera cannot be retracted. 

576 

577 ## Parameters 

578 

579 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EBSD camera. 

580 

581 ## Returns 

582 

583 - `bool`: True if the EBSD camera is successfully retracted. 

584 

585 ## Raises 

586 

587 - `SystemError`: If the EBSD camera is in an error state, if the EBSD mapping is not completed, or if the EBSD camera retraction fails. 

588 """ 

589 connect_EBSD() 

590 ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

591 if ebsd_status == tbt.RetractableDeviceState.ERROR: 591 ↛ 592line 591 didn't jump to line 592 because the condition on line 591 was never true

592 raise SystemError( 

593 "EBSD Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control" 

594 ) 

595 if ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

596 print( 

597 '\t\t\tEBSD Camera Retraction requested, please wait for "mapping complete" verification...' 

598 ) 

599 map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

600 # first check if mapping is finished properly 

601 minutes_to_wait = 5 # TODO set constant 

602 timeout = minutes_to_wait * 60 # seconds #TODO 

603 cameraokconfirmations = 3 # synchronization issue with EDAX, try to get map completed 3x before continuing 

604 waittime = 10 # seconds 

605 if map_status == tbt.MapStatus.ACTIVE: 605 ↛ 606line 605 didn't jump to line 606 because the condition on line 605 was never true

606 print("\t\t\tEBSD mapping currently active, waiting for mapping to finish") 

607 while True: 

608 current_map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

609 if current_map_status != tbt.MapStatus.ACTIVE: 609 ↛ 612line 609 didn't jump to line 612 because the condition on line 609 was always true

610 cameraokconfirmations = cameraokconfirmations - 1 

611 waittime = 3 # shorten wait time, polling 3x to see if mapping was really completed. 

612 time.sleep(waittime) 

613 timeout = timeout - waittime 

614 if cameraokconfirmations < 1: 

615 delay = minutes_to_wait * 60 - timeout 

616 print(f"\t\t\t\tEBSD mapping finished. Delay of {delay} seconds") 

617 break 

618 if timeout < 1: 618 ↛ 619line 618 didn't jump to line 619 because the condition on line 618 was never true

619 warnings.warn( 

620 "\t\t\tWarning, EBSD mapping timeout. Trying to continue..." 

621 ) 

622 break 

623 CCD_view(microscope=microscope) 

624 print("\t\t\tEBSD Camera retracting...") 

625 external.EBSD_RetractCamera() 

626 time.sleep(1) 

627 CCD_pause(microscope=microscope) 

628 current_ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

629 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 629 ↛ 630line 629 didn't jump to line 630 because the condition on line 629 was never true

630 raise SystemError("Error, EBSD Camera retraction failed, workflow stopped.") 

631 print("\t\tEBSD Camera retracted") 

632 return True 

633 

634 

635def connect_EDS() -> tbt.RetractableDeviceState: 

636 """ 

637 Connect to the EDS system and retrieve the camera status. 

638 

639 This function attempts to connect to the EDS system and retrieve the camera status. It raises a ConnectionError if the connection fails. 

640 

641 ## Returns 

642 

643 tbt.RetractableDeviceState 

644 The status of the EDS camera. 

645 

646 ## Raises 

647 

648 ConnectionError 

649 If the EDS control is not connected. 

650 

651 """ 

652 try: 

653 status = external.EDS_CameraStatus() 

654 except: 

655 raise ConnectionError( 

656 """EDS control not connected, "Laser Control" from ThermoFisher must be open. 

657 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again.""" 

658 ) 

659 return tbt.RetractableDeviceState(status) 

660 

661 

662def retract_EDS(microscope: tbt.Microscope) -> bool: 

663 """ 

664 Retract the EDS detector from the microscope. 

665 

666 This function connects to the EDS system, checks the camera status, and retracts the EDS camera if it is not already retracted. It raises an error if the EDS camera cannot be retracted. 

667 

668 ## Parameters 

669 

670 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EDS camera. 

671 

672 ## Returns 

673 

674 - `bool`: True if the EDS camera is successfully retracted. 

675 

676 ## Raises 

677 

678 - `SystemError`: If the EDS camera is in an error state or if the EDS camera retraction fails. 

679 """ 

680 # print(f"\t\tRetracting EDS detector") 

681 connect_EDS() 

682 eds_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

683 if eds_status == tbt.RetractableDeviceState.ERROR: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true

684 raise SystemError( 

685 "EDS Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control" 

686 ) 

687 if eds_status != tbt.RetractableDeviceState.RETRACTED: 

688 print("\t\t\tEDS Camera retracting...") 

689 CCD_view(microscope=microscope) 

690 external.EDS_RetractCamera() 

691 time.sleep(1) 

692 if ( 692 ↛ 696line 692 didn't jump to line 696

693 tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

694 != tbt.RetractableDeviceState.RETRACTED 

695 ): 

696 raise SystemError("Error, EDS Camera retraction failed, workflow stopped.") 

697 CCD_pause(microscope=microscope) 

698 print("\t\tEDS Camera retracted") 

699 return True 

700 

701 

702def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool: 

703 """ 

704 Retract the specified detector from the microscope. 

705 

706 This function ensures the specified detector is the active one, retracts it, and checks its state. It raises an error if the detector cannot be retracted. 

707 

708 ## Parameters 

709 

710 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the detector. 

711 - `detector` (`tbt.DetectorType`): The type of the detector to retract. 

712 

713 ## Returns 

714 

715 - `bool`: True if the detector is successfully retracted. 

716 

717 ## Raises 

718 

719 - `SystemError`: If the detector cannot be retracted. 

720 """ 

721 CCD_view(microscope=microscope) 

722 print(f"\t\tRetracting {detector.value} detector") 

723 microscope.detector.type.value = detector.value 

724 microscope.detector.retract() 

725 state = tbt.RetractableDeviceState(microscope.detector.state) 

726 if state != tbt.RetractableDeviceState.RETRACTED: 726 ↛ 727line 726 didn't jump to line 727 because the condition on line 726 was never true

727 raise SystemError( 

728 f"{detector.value} detector not retracted, current detector state is {state.value}" 

729 ) 

730 print(f"\t\t{detector.value} detector retracted") 

731 CCD_pause(microscope=microscope) 

732 

733 return True 

734 

735 

736def CCD_pause( 

737 microscope: tbt.Microscope, 

738 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT, 

739) -> bool: 

740 """ 

741 Pause the CCD camera, typically used after device or stage movement. 

742 

743 This function pauses the CCD camera by switching to the specified quadrant, setting the beam device to the CCD camera, and stopping the acquisition. It restores the initial view afterward. 

744 

745 ## Parameters 

746 

747 - `microscope` (`tbt.Microscope`): The microscope object for which to pause the CCD camera. 

748 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before pausing the CCD camera (default is tbt.ViewQuad.LOWER_RIGHT). 

749 

750 ## Returns 

751 

752 - `bool`: True if the CCD camera is successfully paused. 

753 

754 ## Warnings 

755 

756 - `UserWarning`: If the CCD camera is not installed on the microscope. 

757 """ 

758 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

759 img.set_view(microscope=microscope, quad=quad) 

760 try: 

761 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA) 

762 except: 

763 warnings.warn("CCD camera is not installed on this microscope.") 

764 else: 

765 microscope.imaging.stop_acquisition() 

766 finally: 

767 microscope.imaging.set_active_view(initial_view.value) 

768 

769 return True 

770 

771 

772def CCD_view( 

773 microscope: tbt.Microscope, 

774 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT, 

775) -> bool: 

776 """ 

777 Visualize detector or stage movement for the user using the CCD camera. 

778 

779 This function visualizes detector or stage movement by switching to the specified quadrant, setting the beam device to the CCD camera, and starting the acquisition. It restores the initial view afterward. 

780 

781 ## Parameters 

782 

783 - `microscope` (`tbt.Microscope`): The microscope object for which to visualize the movement. 

784 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before visualizing the movement (default is tbt.ViewQuad.LOWER_RIGHT). 

785 

786 ## Returns 

787 

788 - `bool`: True if the CCD camera is successfully used for visualization. 

789 

790 ## Warnings 

791 

792 - `UserWarning`: If the CCD camera is not installed on the microscope. 

793 """ 

794 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

795 img.set_view(microscope=microscope, quad=quad) 

796 try: 

797 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA) 

798 except: 

799 warnings.warn("CCD camera is not installed on this microscope.") 

800 else: 

801 microscope.imaging.start_acquisition() 

802 finally: 

803 microscope.imaging.set_active_view(initial_view.value) 

804 

805 return True 

806 

807 

808def specimen_current( 

809 microscope: tbt.Microscope, 

810 hfw_mm=Constants.specimen_current_hfw_mm, 

811 delay_s=Constants.specimen_current_delay_s, 

812) -> float: 

813 """ 

814 Measure the specimen current using the electron beam and return the value in nA. 

815 

816 This function sets the beam device to the electron beam, adjusts the horizontal field width (HFW) and detector, starts the acquisition, and measures the specimen current. It then resets the detector and HFW to their initial values. 

817 

818 ## Parameters 

819 

820 - `microscope` (`tbt.Microscope`): The microscope object for which to measure the specimen current. 

821 - `hfw_mm` (`float, optional`): The horizontal field width in millimeters (default is Constants.specimen_current_hfw_mm). 

822 - `delay_s` (`float, optional`): The delay in seconds before measuring the specimen current (default is Constants.specimen_current_delay_s). 

823 

824 ## Returns 

825 

826 - `float`: The measured specimen current in nA. 

827 """ 

828 img.set_beam_device( 

829 microscope=microscope, 

830 device=tbt.Device.ELECTRON_BEAM, 

831 ) 

832 initial_hfw_m = microscope.beams.electron_beam.horizontal_field_width.value 

833 initial_detector = tbt.DetectorType(microscope.detector.type.value) 

834 

835 # TODO: A safer structure would be a try/except block 

836 # try: 

837 # img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD) 

838 # img.beam_hfw( 

839 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

840 # microscope=microscope, 

841 # hfw_mm=hfw_mm, 

842 # ) 

843 # microscope.imaging.start_acquisition() 

844 # time.sleep(delay_s) 

845 # return microscope.state.specimen_current.value * cs.Conversions.A_TO_NA 

846 # finally: 

847 # microscope.imaging.stop_acquisition() 

848 # microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m 

849 # img.detector_type(microscope=microscope, detector=initial_detector) 

850 # img.beam_hfw( 

851 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

852 # microscope=microscope, 

853 # hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM, 

854 # ) 

855 

856 img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD) 

857 img.beam_hfw( 

858 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

859 microscope=microscope, 

860 hfw_mm=hfw_mm, 

861 ) 

862 microscope.imaging.start_acquisition() 

863 time.sleep(delay_s) 

864 current_na = microscope.state.specimen_current.value * cs.Conversions.A_TO_NA 

865 microscope.imaging.stop_acquisition() 

866 

867 # reset detector and hfw 

868 microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m 

869 img.detector_type(microscope=microscope, detector=initial_detector) 

870 img.beam_hfw( 

871 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

872 microscope=microscope, 

873 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM, 

874 ) 

875 

876 return current_na