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

244 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-09-03 19:02 +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# try: 

161# from pytribeam.laser import tfs_laser as external 

162# except: 

163# pass 

164 

165 

166def detector_insertable( 

167 microscope: tbt.Microscope, 

168 detector: tbt.DetectorType, 

169) -> bool: 

170 """ 

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

172 

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

174 

175 ## Parameters 

176 

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

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

179 

180 ## Returns 

181 

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

183 

184 ## Warnings 

185 

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

187 """ 

188 # check if the detector is being read by Autoscript 

189 try: 

190 # make requested detector the active detector 

191 microscope.detector.type.value = detector.value 

192 except: 

193 warnings.warn( 

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

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

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

197 ) 

198 return False 

199 

200 # check if it is insertable 

201 try: 

202 state = microscope.detector.state 

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

204 return False 

205 return True 

206 except Exception: 

207 return False 

208 

209 

210def detector_state( 

211 microscope: tbt.Microscope, 

212 detector: tbt.DetectorType, 

213) -> tbt.RetractableDeviceState: 

214 """ 

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

216 

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

218 

219 ## Parameters 

220 

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

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

223 

224 ## Returns 

225 

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

227 """ 

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

229 # try: 

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

231 # except Exception: 

232 # return tbt.RetractableDeviceState.STATIONARY 

233 if not detector_insertable( 233 ↛ 238line 233 didn't jump to line 238 because the condition on line 233 was always true

234 microscope=microscope, 

235 detector=detector, 

236 ): 

237 return tbt.RetractableDeviceState.STATIONARY 

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

239 

240 

241def detectors_will_collide( 

242 microscope: tbt.Microscope, 

243 detector_to_insert: tbt.DetectorType, 

244) -> bool: 

245 """ 

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

247 

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

249 

250 ## Parameters 

251 

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

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

254 

255 ## Returns 

256 

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

258 """ 

259 device_retracted = tbt.RetractableDeviceState.RETRACTED.value 

260 for detector_combo in Constants.detector_collisions: 260 ↛ 275line 260 didn't jump to line 275 because the loop on line 260 didn't complete

261 if detector_to_insert in detector_combo: 261 ↛ 260,   261 ↛ 2622 missed branches: 1) line 261 didn't jump to line 260 because the condition on line 261 was always true, 2) line 261 didn't jump to line 262 because the condition on line 261 was never true

262 for detector in detector_combo: 

263 if detector == detector_to_insert: 263 ↛ 264,   263 ↛ 2652 missed branches: 1) line 263 didn't jump to line 264 because the condition on line 263 was never true, 2) line 263 didn't jump to line 265 because the condition on line 263 was always true

264 continue 

265 if detector == tbt.DetectorType.EDS: 

266 if external.EDS_CameraStatus() != device_retracted: 

267 return True 

268 elif detector == tbt.DetectorType.EBSD: 268 ↛ 269,   268 ↛ 2722 missed branches: 1) line 268 didn't jump to line 269 because the condition on line 268 was never true, 2) line 268 didn't jump to line 272 because the condition on line 268 was always true

269 if external.EBSD_CameraStatus() != device_retracted: 

270 return True 

271 else: 

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

273 if state.value != device_retracted: 273 ↛ 262,   273 ↛ 2742 missed branches: 1) line 273 didn't jump to line 262 because the condition on line 273 was always true, 2) line 273 didn't jump to line 274 because the condition on line 273 was never true

274 return True 

275 return False 

276 

277 

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

279 """ 

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

281 

282 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. 

283 

284 ## Parameters 

285 

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

287 

288 ## Returns 

289 

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

291 """ 

292 img.set_view( 

293 microscope=microscope, 

294 quad=tbt.ViewQuad.UPPER_LEFT, 

295 ) 

296 img.set_beam_device( 

297 microscope=microscope, 

298 device=tbt.Device.ELECTRON_BEAM, 

299 ) 

300 return True 

301 

302 

303def insert_EBSD( 

304 microscope: tbt.Microscope, 

305) -> bool: 

306 """ 

307 Insert the EBSD camera into the microscope. 

308 

309 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. 

310 

311 ## Parameters 

312 

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

314 

315 ## Returns 

316 

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

318 

319 ## Raises 

320 

321 - `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. 

322 """ 

323 connect_EBSD() 

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

325 microscope=microscope, 

326 detector_to_insert=tbt.DetectorType.EBSD, 

327 ): 

328 raise SystemError( 

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

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

331 ) 

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

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

334 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 334 ↛ 335,   334 ↛ 3362 missed branches: 1) line 334 didn't jump to line 335 because the condition on line 334 was never true, 2) line 334 didn't jump to line 336 because the condition on line 334 was always true

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

336 if map_status != tbt.MapStatus.IDLE: 

337 raise SystemError( 

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

339 ) 

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

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

342 # TODO change to constants 

343 minutes_to_wait = 3 

344 timeout = minutes_to_wait * 60 # seconds 

345 waittime = 4 # seconds 

346 CCD_view(microscope=microscope) 

347 # Oxford Inst requires 2 inserts 

348 while True: 

349 external.EBSD_InsertCamera() # inserted state 

350 if ( 350 ↛ 355line 350 didn't jump to line 355

351 external.EBSD_CameraStatus() 

352 == tbt.RetractableDeviceState.INSERTED.value 

353 ): 

354 break 

355 time.sleep(waittime) 

356 timeout = timeout - waittime 

357 if timeout < 1: 357 ↛ 349,   357 ↛ 3582 missed branches: 1) line 357 didn't jump to line 349 because the condition on line 357 was always true, 2) line 357 didn't jump to line 358 because the condition on line 357 was never true

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

359 break 

360 CCD_pause(microscope=microscope) 

361 

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

363 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED: 

364 print("\tEBSD Camera inserted") 

365 return True 

366 raise SystemError( 

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

368 ) 

369 

370 

371def insert_EDS( 

372 microscope: tbt.Microscope, 

373) -> bool: 

374 """ 

375 Insert the EDS camera into the microscope. 

376 

377 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. 

378 

379 ## Parameters 

380 

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

382 

383 ## Returns 

384 

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

386 

387 ## Raises 

388 

389 - `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. 

390 """ 

391 connect_EDS() 

392 if detectors_will_collide( 392 ↛ 400line 392 didn't jump to line 400 because the condition on line 392 was always true

393 microscope=microscope, 

394 detector_to_insert=tbt.DetectorType.EDS, 

395 ): 

396 raise SystemError( 

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

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

399 ) 

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

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

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

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

404 if map_status != tbt.MapStatus.IDLE: 404 ↛ 408line 404 didn't jump to line 408 because the condition on line 404 was always true

405 raise SystemError( 

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

407 ) 

408 if eds_cam_status != tbt.RetractableDeviceState.INSERTED: 408 ↛ 409,   408 ↛ 4142 missed branches: 1) line 408 didn't jump to line 409 because the condition on line 408 was never true, 2) line 408 didn't jump to line 414 because the condition on line 408 was always true

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

410 CCD_view(microscope=microscope) 

411 external.EDS_InsertCamera() 

412 CCD_pause(microscope=microscope) 

413 

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

415 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED: 

416 print("\tEDS Camera inserted") 

417 return True 

418 raise SystemError( 

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

420 ) 

421 

422 

423def insert_detector( 

424 microscope: tbt.Microscope, 

425 detector: tbt.DetectorType, 

426 time_delay_s: float = 0.5, 

427) -> bool: 

428 """ 

429 Insert the selected detector into the microscope. 

430 

431 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. 

432 

433 ## Parameters 

434 

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

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

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

438 

439 ## Returns 

440 

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

442 

443 ## Raises 

444 

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

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

447 """ 

448 # ensure detector is the active one 

449 microscope.detector.type.value = detector.value 

450 # confirm detector is insertable 

451 try: 

452 state = microscope.detector.state 

453 except: 

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

455 if state == tbt.RetractableDeviceState.RETRACTED.value: 

456 if detectors_will_collide( 456 ↛ 465line 456 didn't jump to line 465 because the condition on line 456 was always true

457 microscope=microscope, 

458 detector_to_insert=detector, 

459 ): 

460 raise SystemError( 

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

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

463 ) 

464 

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

466 CCD_view(microscope=microscope) 

467 microscope.detector.insert() 

468 time.sleep(time_delay_s) 

469 CCD_pause(microscope=microscope) 

470 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value: 

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

472 return True 

473 elif state == tbt.RetractableDeviceState.INSERTED.value: 473 ↛ 476line 473 didn't jump to line 476 because the condition on line 473 was always true

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

475 return True 

476 raise SystemError( 

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

478 ) 

479 

480 

481def retract_all_devices( 

482 microscope: tbt.Microscope, 

483 enable_EBSD: bool, 

484 enable_EDS: bool, 

485) -> bool: 

486 # TODO come up with better system for enable_EBSD_EDS 

487 """ 

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

489 

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

491 

492 ## Parameters 

493 

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

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

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

497 

498 ## Returns 

499 

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

501 

502 ## Raises 

503 

504 - `None` 

505 """ 

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

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

508 device_access(microscope) 

509 

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

511 detector = tbt.DetectorType(detector) # overwrite 

512 state = detector_state( 

513 microscope=microscope, 

514 detector=detector, 

515 ) 

516 if ( 516 ↛ 521line 516 didn't jump to line 521

517 state != tbt.RetractableDeviceState.STATIONARY 

518 and state != tbt.RetractableDeviceState.RETRACTED 

519 ): 

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

521 retract_device( 

522 microscope=microscope, 

523 detector=detector, 

524 ) 

525 

526 # EBSD/EDS detectors: 

527 if external is None: 527 ↛ 534line 527 didn't jump to line 534 because the condition on line 527 was always true

528 # try: 

529 # external 

530 # except NameError: 

531 # pass 

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

533 else: 

534 if enable_EBSD: 

535 retract_EBSD(microscope=microscope) 

536 if enable_EDS: 

537 retract_EDS(microscope=microscope) 

538 

539 # reset initial settings: 

540 img.set_view( 

541 microscope=microscope, 

542 quad=initial_view, 

543 ) 

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

545 return True 

546 

547 

548def connect_EBSD() -> tbt.RetractableDeviceState: 

549 """ 

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

551 

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

553 

554 ## Returns 

555 

556 tbt.RetractableDeviceState 

557 The status of the EBSD camera. 

558 

559 ## Raises 

560 

561 ConnectionError 

562 If the EBSD control is not connected. 

563 

564 """ 

565 try: 

566 status = external.EBSD_CameraStatus() 

567 except: 

568 raise ConnectionError( 

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

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

571 ) 

572 return tbt.RetractableDeviceState(status) 

573 

574 

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

576 """ 

577 Retract the EBSD camera from the microscope. 

578 

579 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. 

580 

581 ## Parameters 

582 

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

584 

585 ## Returns 

586 

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

588 

589 ## Raises 

590 

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

592 """ 

593 connect_EBSD() 

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

595 if ebsd_status == tbt.RetractableDeviceState.ERROR: 

596 raise SystemError( 

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

598 ) 

599 if ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

600 print( 

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

602 ) 

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

604 # first check if mapping is finished properly 

605 minutes_to_wait = 5 # TODO set constant 

606 timeout = minutes_to_wait * 60 # seconds #TODO 

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

608 waittime = 10 # seconds 

609 if map_status == tbt.MapStatus.ACTIVE: 

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

611 while True: 

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

613 if current_map_status != tbt.MapStatus.ACTIVE: 

614 cameraokconfirmations = cameraokconfirmations - 1 

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

616 time.sleep(waittime) 

617 timeout = timeout - waittime 

618 if cameraokconfirmations < 1: 

619 delay = minutes_to_wait * 60 - timeout 

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

621 break 

622 if timeout < 1: 

623 warnings.warn( 

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

625 ) 

626 break 

627 CCD_view(microscope=microscope) 

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

629 external.EBSD_RetractCamera() 

630 time.sleep(1) 

631 CCD_pause(microscope=microscope) 

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

633 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 633 ↛ 635line 633 didn't jump to line 635 because the condition on line 633 was always true

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

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

636 return True 

637 

638 

639def connect_EDS() -> tbt.RetractableDeviceState: 

640 """ 

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

642 

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

644 

645 ## Returns 

646 

647 tbt.RetractableDeviceState 

648 The status of the EDS camera. 

649 

650 ## Raises 

651 

652 ConnectionError 

653 If the EDS control is not connected. 

654 

655 """ 

656 try: 

657 status = external.EDS_CameraStatus() 

658 except: 

659 raise ConnectionError( 

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

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

662 ) 

663 return tbt.RetractableDeviceState(status) 

664 

665 

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

667 """ 

668 Retract the EDS detector from the microscope. 

669 

670 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. 

671 

672 ## Parameters 

673 

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

675 

676 ## Returns 

677 

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

679 

680 ## Raises 

681 

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

683 """ 

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

685 connect_EDS() 

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

687 if eds_status == tbt.RetractableDeviceState.ERROR: 

688 raise SystemError( 

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

690 ) 

691 if eds_status != tbt.RetractableDeviceState.RETRACTED: 691 ↛ 703line 691 didn't jump to line 703 because the condition on line 691 was always true

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

693 CCD_view(microscope=microscope) 

694 external.EDS_RetractCamera() 

695 time.sleep(1) 

696 if ( 

697 tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

698 != tbt.RetractableDeviceState.RETRACTED 

699 ): 

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

701 CCD_pause(microscope=microscope) 

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

703 return True 

704 

705 

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

707 """ 

708 Retract the specified detector from the microscope. 

709 

710 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. 

711 

712 ## Parameters 

713 

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

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

716 

717 ## Returns 

718 

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

720 

721 ## Raises 

722 

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

724 """ 

725 CCD_view(microscope=microscope) 

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

727 microscope.detector.type.value = detector.value 

728 microscope.detector.retract() 

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

730 if state != tbt.RetractableDeviceState.RETRACTED: 730 ↛ 734line 730 didn't jump to line 734 because the condition on line 730 was always true

731 raise SystemError( 

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

733 ) 

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

735 CCD_pause(microscope=microscope) 

736 

737 return True 

738 

739 

740def CCD_pause( 

741 microscope: tbt.Microscope, 

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

743) -> bool: 

744 """ 

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

746 

747 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. 

748 

749 ## Parameters 

750 

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

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

753 

754 ## Returns 

755 

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

757 

758 ## Warnings 

759 

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

761 """ 

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

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

764 try: 

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

766 except: 

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

768 else: 

769 microscope.imaging.stop_acquisition() 

770 finally: 

771 microscope.imaging.set_active_view(initial_view.value) 

772 

773 return True 

774 

775 

776def CCD_view( 

777 microscope: tbt.Microscope, 

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

779) -> bool: 

780 """ 

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

782 

783 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. 

784 

785 ## Parameters 

786 

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

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

789 

790 ## Returns 

791 

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

793 

794 ## Warnings 

795 

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

797 """ 

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

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

800 try: 

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

802 except: 

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

804 else: 

805 microscope.imaging.start_acquisition() 

806 finally: 

807 microscope.imaging.set_active_view(initial_view.value) 

808 

809 return True 

810 

811 

812def specimen_current( 

813 microscope: tbt.Microscope, 

814 hfw_mm=Constants.specimen_current_hfw_mm, 

815 delay_s=Constants.specimen_current_delay_s, 

816) -> float: 

817 """ 

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

819 

820 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. 

821 

822 ## Parameters 

823 

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

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

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

827 

828 ## Returns 

829 

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

831 """ 

832 img.set_beam_device( 

833 microscope=microscope, 

834 device=tbt.Device.ELECTRON_BEAM, 

835 ) 

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

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

838 

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

840 # try: 

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

842 # img.beam_hfw( 

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

844 # microscope=microscope, 

845 # hfw_mm=hfw_mm, 

846 # ) 

847 # microscope.imaging.start_acquisition() 

848 # time.sleep(delay_s) 

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

850 # finally: 

851 # microscope.imaging.stop_acquisition() 

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

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

854 # img.beam_hfw( 

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

856 # microscope=microscope, 

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

858 # ) 

859 

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

861 img.beam_hfw( 

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

863 microscope=microscope, 

864 hfw_mm=hfw_mm, 

865 ) 

866 microscope.imaging.start_acquisition() 

867 time.sleep(delay_s) 

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

869 microscope.imaging.stop_acquisition() 

870 

871 # reset detector and hfw 

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

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

874 img.beam_hfw( 

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

876 microscope=microscope, 

877 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM, 

878 ) 

879 

880 return current_na