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

249 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-09-09 18:27 +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 

158try: 

159 from pytribeam.laser import tfs_laser as external 

160except: 

161 pass 

162import pytribeam.types as tbt 

163 

164 

165def detector_insertable( 

166 microscope: tbt.Microscope, 

167 detector: tbt.DetectorType, 

168) -> bool: 

169 """ 

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

171 

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

173 

174 ## Parameters 

175 

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

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

178 

179 ## Returns 

180 

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

182 

183 ## Warnings 

184 

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

186 """ 

187 # check if the detector is being read by Autoscript 

188 try: 

189 # make requested detector the active detector 

190 microscope.detector.type.value = detector.value 

191 except: 

192 warnings.warn( 

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

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

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

196 ) 

197 return False 

198 

199 # check if it is insertable 

200 try: 

201 state = microscope.detector.state 

202 if state == tbt.RetractableDeviceState.STATIONARY.value: 202 ↛ 203,   202 ↛ 2042 missed branches: 1) line 202 didn't jump to line 203 because the condition on line 202 was never true, 2) line 202 didn't jump to line 204 because the condition on line 202 was always true

203 return False 

204 return True 

205 except Exception: 

206 return False 

207 

208 

209def detector_state( 

210 microscope: tbt.Microscope, 

211 detector: tbt.DetectorType, 

212) -> tbt.RetractableDeviceState: 

213 """ 

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

215 

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

217 

218 ## Parameters 

219 

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

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

222 

223 ## Returns 

224 

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

226 """ 

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

228 # try: 

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

230 # except Exception: 

231 # return tbt.RetractableDeviceState.STATIONARY 

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

233 microscope=microscope, 

234 detector=detector, 

235 ): 

236 return tbt.RetractableDeviceState.STATIONARY 

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

238 

239 

240def detectors_will_collide( 

241 microscope: tbt.Microscope, 

242 detector_to_insert: tbt.DetectorType, 

243) -> bool: 

244 """ 

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

246 

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

248 

249 ## Parameters 

250 

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

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

253 

254 ## Returns 

255 

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

257 """ 

258 device_retracted = tbt.RetractableDeviceState.RETRACTED.value 

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

260 if detector_to_insert in detector_combo: 260 ↛ 259line 260 didn't jump to line 259 because the condition on line 260 was always true

261 for detector in detector_combo: 261 ↛ 259,   261 ↛ 2622 missed branches: 1) line 261 didn't jump to line 259 because the loop on line 261 didn't complete, 2) line 261 didn't jump to line 262 because the loop on line 261 never started

262 if detector == detector_to_insert: 

263 continue 

264 if detector == tbt.DetectorType.EDS: 

265 if external.EDS_CameraStatus() != device_retracted: 

266 return True 

267 elif detector == tbt.DetectorType.EBSD: 267 ↛ 271line 267 didn't jump to line 271 because the condition on line 267 was always true

268 if external.EBSD_CameraStatus() != device_retracted: 268 ↛ 261,   268 ↛ 2692 missed branches: 1) line 268 didn't jump to line 261 because the condition on line 268 was always true, 2) line 268 didn't jump to line 269 because the condition on line 268 was never true

269 return True 

270 else: 

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

272 if state.value != device_retracted: 272 ↛ 261line 272 didn't jump to line 261 because the condition on line 272 was always true

273 return True 

274 return False 

275 

276 

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

278 """ 

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

280 

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

282 

283 ## Parameters 

284 

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

286 

287 ## Returns 

288 

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

290 """ 

291 img.set_view( 

292 microscope=microscope, 

293 quad=tbt.ViewQuad.UPPER_LEFT, 

294 ) 

295 img.set_beam_device( 

296 microscope=microscope, 

297 device=tbt.Device.ELECTRON_BEAM, 

298 ) 

299 return True 

300 

301 

302def insert_EBSD( 

303 microscope: tbt.Microscope, 

304) -> bool: 

305 """ 

306 Insert the EBSD camera into the microscope. 

307 

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

309 

310 ## Parameters 

311 

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

313 

314 ## Returns 

315 

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

317 

318 ## Raises 

319 

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

321 """ 

322 connect_EBSD() 

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

324 microscope=microscope, 

325 detector_to_insert=tbt.DetectorType.EBSD, 

326 ): 

327 raise SystemError( 

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

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

330 ) 

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

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

333 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 

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

335 if map_status != tbt.MapStatus.IDLE: 

336 raise SystemError( 

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

338 ) 

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

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

341 # TODO change to constants 

342 minutes_to_wait = 3 

343 timeout = minutes_to_wait * 60 # seconds 

344 waittime = 4 # seconds 

345 CCD_view(microscope=microscope) 

346 # Oxford Inst requires 2 inserts 

347 while True: 

348 external.EBSD_InsertCamera() # inserted state 

349 if ( 

350 external.EBSD_CameraStatus() 

351 == tbt.RetractableDeviceState.INSERTED.value 

352 ): 

353 break 

354 time.sleep(waittime) 

355 timeout = timeout - waittime 

356 if timeout < 1: 356 ↛ 348line 356 didn't jump to line 348 because the condition on line 356 was always true

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

358 break 

359 CCD_pause(microscope=microscope) 

360 

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

362 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED: 

363 print("\tEBSD Camera inserted") 

364 return True 

365 raise SystemError( 

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

367 ) 

368 

369 

370def insert_EDS( 

371 microscope: tbt.Microscope, 

372) -> bool: 

373 """ 

374 Insert the EDS camera into the microscope. 

375 

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

377 

378 ## Parameters 

379 

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

381 

382 ## Returns 

383 

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

385 

386 ## Raises 

387 

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

389 """ 

390 connect_EDS() 

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

392 microscope=microscope, 

393 detector_to_insert=tbt.DetectorType.EDS, 

394 ): 

395 raise SystemError( 

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

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

398 ) 

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

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

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

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

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

404 raise SystemError( 

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

406 ) 

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

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

409 CCD_view(microscope=microscope) 

410 external.EDS_InsertCamera() 

411 CCD_pause(microscope=microscope) 

412 

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

414 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED: 

415 print("\tEDS Camera inserted") 

416 return True 

417 raise SystemError( 

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

419 ) 

420 

421 

422def insert_detector( 

423 microscope: tbt.Microscope, 

424 detector: tbt.DetectorType, 

425 time_delay_s: float = 0.5, 

426) -> bool: 

427 """ 

428 Insert the selected detector into the microscope. 

429 

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

431 

432 ## Parameters 

433 

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

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

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

437 

438 ## Returns 

439 

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

441 

442 ## Raises 

443 

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

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

446 """ 

447 # ensure detector is the active one 

448 microscope.detector.type.value = detector.value 

449 # confirm detector is insertable 

450 try: 

451 state = microscope.detector.state 

452 except: 

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

454 if state == tbt.RetractableDeviceState.RETRACTED.value: 454 ↛ 455,   454 ↛ 4722 missed branches: 1) line 454 didn't jump to line 455 because the condition on line 454 was never true, 2) line 454 didn't jump to line 472 because the condition on line 454 was always true

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

456 microscope=microscope, 

457 detector_to_insert=detector, 

458 ): 

459 raise SystemError( 

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

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

462 ) 

463 

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

465 CCD_view(microscope=microscope) 

466 microscope.detector.insert() 

467 time.sleep(time_delay_s) 

468 CCD_pause(microscope=microscope) 

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

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

471 return True 

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

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

474 return True 

475 raise SystemError( 

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

477 ) 

478 

479 

480def retract_all_devices( 

481 microscope: tbt.Microscope, 

482 enable_EBSD: bool, 

483 enable_EDS: bool, 

484) -> bool: 

485 # TODO come up with better system for enable_EBSD_EDS 

486 """ 

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

488 

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

490 

491 ## Parameters 

492 

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

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

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

496 

497 ## Returns 

498 

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

500 

501 ## Raises 

502 

503 - `None` 

504 """ 

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

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

507 device_access(microscope) 

508 

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

510 detector = tbt.DetectorType(detector) # overwrite 

511 state = detector_state( 

512 microscope=microscope, 

513 detector=detector, 

514 ) 

515 if ( 515 ↛ 520line 515 didn't jump to line 520

516 state != tbt.RetractableDeviceState.STATIONARY 

517 and state != tbt.RetractableDeviceState.RETRACTED 

518 ): 

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

520 retract_device( 

521 microscope=microscope, 

522 detector=detector, 

523 ) 

524 

525 # EBSD/EDS detectors: 

526 try: 

527 external 

528 except NameError: 

529 pass 

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

531 else: 

532 if enable_EBSD: 

533 retract_EBSD(microscope=microscope) 

534 if enable_EDS: 

535 retract_EDS(microscope=microscope) 

536 

537 # reset initial settings: 

538 img.set_view( 

539 microscope=microscope, 

540 quad=initial_view, 

541 ) 

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

543 return True 

544 

545 

546def connect_EBSD() -> tbt.RetractableDeviceState: 

547 """ 

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

549 

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

551 

552 ## Returns 

553 

554 tbt.RetractableDeviceState 

555 The status of the EBSD camera. 

556 

557 ## Raises 

558 

559 ConnectionError 

560 If the EBSD control is not connected. 

561 

562 """ 

563 try: 

564 status = external.EBSD_CameraStatus() 

565 except: 

566 raise ConnectionError( 

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

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

569 ) 

570 return tbt.RetractableDeviceState(status) 

571 

572 

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

574 """ 

575 Retract the EBSD camera from the microscope. 

576 

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

578 

579 ## Parameters 

580 

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

582 

583 ## Returns 

584 

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

586 

587 ## Raises 

588 

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

590 """ 

591 connect_EBSD() 

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

593 if ebsd_status == tbt.RetractableDeviceState.ERROR: 

594 raise SystemError( 

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

596 ) 

597 if ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

598 print( 

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

600 ) 

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

602 # first check if mapping is finished properly 

603 minutes_to_wait = 5 # TODO set constant 

604 timeout = minutes_to_wait * 60 # seconds #TODO 

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

606 waittime = 10 # seconds 

607 if map_status == tbt.MapStatus.ACTIVE: 607 ↛ 608,   607 ↛ 6102 missed branches: 1) line 607 didn't jump to line 608 because the condition on line 607 was never true, 2) line 607 didn't jump to line 610 because the condition on line 607 was always true

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

609 while True: 

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

611 if current_map_status != tbt.MapStatus.ACTIVE: 

612 cameraokconfirmations = cameraokconfirmations - 1 

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

614 time.sleep(waittime) 

615 timeout = timeout - waittime 

616 if cameraokconfirmations < 1: 

617 delay = minutes_to_wait * 60 - timeout 

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

619 break 

620 if timeout < 1: 

621 warnings.warn( 

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

623 ) 

624 break 

625 CCD_view(microscope=microscope) 

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

627 external.EBSD_RetractCamera() 

628 time.sleep(1) 

629 CCD_pause(microscope=microscope) 

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

631 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

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

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

634 return True 

635 

636 

637def connect_EDS() -> tbt.RetractableDeviceState: 

638 """ 

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

640 

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

642 

643 ## Returns 

644 

645 tbt.RetractableDeviceState 

646 The status of the EDS camera. 

647 

648 ## Raises 

649 

650 ConnectionError 

651 If the EDS control is not connected. 

652 

653 """ 

654 try: 

655 status = external.EDS_CameraStatus() 

656 except: 

657 raise ConnectionError( 

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

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

660 ) 

661 return tbt.RetractableDeviceState(status) 

662 

663 

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

665 """ 

666 Retract the EDS detector from the microscope. 

667 

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

669 

670 ## Parameters 

671 

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

673 

674 ## Returns 

675 

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

677 

678 ## Raises 

679 

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

681 """ 

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

683 connect_EDS() 

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

685 if eds_status == tbt.RetractableDeviceState.ERROR: 685 ↛ 689line 685 didn't jump to line 689 because the condition on line 685 was always true

686 raise SystemError( 

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

688 ) 

689 if eds_status != tbt.RetractableDeviceState.RETRACTED: 

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

691 CCD_view(microscope=microscope) 

692 external.EDS_RetractCamera() 

693 time.sleep(1) 

694 if ( 694 ↛ 698,   694 ↛ 6992 missed branches: 1) line 694 didn't jump to line 698, 2) line 694 didn't jump to line 699

695 tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

696 != tbt.RetractableDeviceState.RETRACTED 

697 ): 

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

699 CCD_pause(microscope=microscope) 

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

701 return True 

702 

703 

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

705 """ 

706 Retract the specified detector from the microscope. 

707 

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

709 

710 ## Parameters 

711 

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

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

714 

715 ## Returns 

716 

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

718 

719 ## Raises 

720 

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

722 """ 

723 CCD_view(microscope=microscope) 

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

725 microscope.detector.type.value = detector.value 

726 microscope.detector.retract() 

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

728 if state != tbt.RetractableDeviceState.RETRACTED: 

729 raise SystemError( 

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

731 ) 

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

733 CCD_pause(microscope=microscope) 

734 

735 return True 

736 

737 

738def CCD_pause( 

739 microscope: tbt.Microscope, 

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

741) -> bool: 

742 """ 

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

744 

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

746 

747 ## Parameters 

748 

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

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

751 

752 ## Returns 

753 

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

755 

756 ## Warnings 

757 

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

759 """ 

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

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

762 try: 

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

764 except: 

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

766 else: 

767 microscope.imaging.stop_acquisition() 

768 finally: 

769 microscope.imaging.set_active_view(initial_view.value) 

770 

771 return True 

772 

773 

774def CCD_view( 

775 microscope: tbt.Microscope, 

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

777) -> bool: 

778 """ 

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

780 

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

782 

783 ## Parameters 

784 

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

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

787 

788 ## Returns 

789 

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

791 

792 ## Warnings 

793 

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

795 """ 

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

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

798 try: 

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

800 except: 

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

802 else: 

803 microscope.imaging.start_acquisition() 

804 finally: 

805 microscope.imaging.set_active_view(initial_view.value) 

806 

807 return True 

808 

809 

810def specimen_current( 

811 microscope: tbt.Microscope, 

812 hfw_mm=Constants.specimen_current_hfw_mm, 

813 delay_s=Constants.specimen_current_delay_s, 

814) -> float: 

815 """ 

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

817 

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

819 

820 ## Parameters 

821 

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

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

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

825 

826 ## Returns 

827 

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

829 """ 

830 img.set_beam_device( 

831 microscope=microscope, 

832 device=tbt.Device.ELECTRON_BEAM, 

833 ) 

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

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

836 

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

838 # try: 

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

840 # img.beam_hfw( 

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

842 # microscope=microscope, 

843 # hfw_mm=hfw_mm, 

844 # ) 

845 # microscope.imaging.start_acquisition() 

846 # time.sleep(delay_s) 

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

848 # finally: 

849 # microscope.imaging.stop_acquisition() 

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

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

852 # img.beam_hfw( 

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

854 # microscope=microscope, 

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

856 # ) 

857 

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

859 img.beam_hfw( 

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

861 microscope=microscope, 

862 hfw_mm=hfw_mm, 

863 ) 

864 microscope.imaging.start_acquisition() 

865 time.sleep(delay_s) 

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

867 microscope.imaging.stop_acquisition() 

868 

869 # reset detector and hfw 

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

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

872 img.beam_hfw( 

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

874 microscope=microscope, 

875 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM, 

876 ) 

877 

878 return current_na