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
« 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.
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.
9The main responsibilities of this module are:
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.
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.
24## Device types
26This module works with two classes of insertable devices:
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 |
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.
37## Typical usage
39Retract all available and enabled insertable devices:
41```python
42from pytribeam import insertable_devices as devices
44devices.retract_all_devices(
45 microscope=microscope,
46 enable_EBSD=True,
47 enable_EDS=True,
48)
49```
51Insert a microscope detector after checking collision constraints:
53```python
54import pytribeam.types as tbt
55from pytribeam import insertable_devices as devices
57devices.insert_detector(
58 microscope=microscope,
59 detector=tbt.DetectorType.CBS,
60)
61```
63Insert EBSD or EDS cameras:
65```python
66from pytribeam import insertable_devices as devices
68devices.insert_EBSD(microscope)
69devices.insert_EDS(microscope)
70```
72Use the CCD camera to visualize motion:
74```python
75from pytribeam import insertable_devices as devices
77devices.CCD_view(microscope)
78# move stage or insert/retract detector
79devices.CCD_pause(microscope)
80```
82Measure specimen current:
84```python
85from pytribeam import insertable_devices as devices
87current_na = devices.specimen_current(microscope)
88```
90## Collision handling
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.
97Insertion functions raise `SystemError` if the requested device may collide with
98another detector or if the device cannot be inserted successfully.
100## CCD visualization
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.
107If the CCD camera is not available on the microscope, a warning is issued and
108the workflow continues.
110## Specimen-current measurement
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.
116Specimen current is returned in nanoamperes.
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.
124<hr style="height: 12px; background-color: #333; border: none;">
125"""
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]
146# Default python modules
147# from functools import singledispatch
148import time
149import warnings
151# 3rd party module
153# Local scripts
154import pytribeam.constants as cs
155from pytribeam.constants import Constants
156import pytribeam.image as img
158try:
159 from pytribeam.laser import tfs_laser as external
160except:
161 pass
162import pytribeam.types as tbt
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.
172 This function checks if the specified detector is being read by Autoscript and if it is insertable.
174 ## Parameters
176 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector.
177 - `detector` (`tbt.DetectorType`): The type of the detector to check.
179 ## Returns
181 - `bool`: True if the detector is insertable, False otherwise.
183 ## Warnings
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
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
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.
216 This function checks if the specified detector is insertable and returns its state.
218 ## Parameters
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.
223 ## Returns
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)
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.
247 This function checks if inserting the specified detector will cause a collision with any other detectors.
249 ## Parameters
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.
254 ## Returns
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
277def device_access(microscope: tbt.Microscope) -> bool:
278 """
279 Switch to the upper-left quadrant and assign the electron beam as the active device.
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.
283 ## Parameters
285 - `microscope` (`tbt.Microscope`): The microscope object for which to switch the view and assign the active device.
287 ## Returns
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
302def insert_EBSD(
303 microscope: tbt.Microscope,
304) -> bool:
305 """
306 Insert the EBSD camera into the microscope.
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.
310 ## Parameters
312 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EBSD camera.
314 ## Returns
316 - `bool`: True if the EBSD camera is successfully inserted.
318 ## Raises
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)
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 )
370def insert_EDS(
371 microscope: tbt.Microscope,
372) -> bool:
373 """
374 Insert the EDS camera into the microscope.
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.
378 ## Parameters
380 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EDS camera.
382 ## Returns
384 - `bool`: True if the EDS camera is successfully inserted.
386 ## Raises
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)
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 )
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.
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.
432 ## Parameters
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).
438 ## Returns
440 - `bool`: True if the detector is successfully inserted.
442 ## Raises
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 )
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 )
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.
489 This function retracts all insertable devices, first retracting microscope detectors and then retracting EBSD/EDS detectors if they are integrated and enabled.
491 ## Parameters
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.
497 ## Returns
499 - `bool`: True if all devices are successfully retracted.
501 ## Raises
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)
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 )
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)
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
546def connect_EBSD() -> tbt.RetractableDeviceState:
547 """
548 Connect to the EBSD system and retrieve the camera status.
550 This function attempts to connect to the EBSD system and retrieve the camera status. It raises a ConnectionError if the connection fails.
552 ## Returns
554 tbt.RetractableDeviceState
555 The status of the EBSD camera.
557 ## Raises
559 ConnectionError
560 If the EBSD control is not connected.
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)
573def retract_EBSD(microscope: tbt.Microscope) -> bool:
574 """
575 Retract the EBSD camera from the microscope.
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.
579 ## Parameters
581 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EBSD camera.
583 ## Returns
585 - `bool`: True if the EBSD camera is successfully retracted.
587 ## Raises
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
637def connect_EDS() -> tbt.RetractableDeviceState:
638 """
639 Connect to the EDS system and retrieve the camera status.
641 This function attempts to connect to the EDS system and retrieve the camera status. It raises a ConnectionError if the connection fails.
643 ## Returns
645 tbt.RetractableDeviceState
646 The status of the EDS camera.
648 ## Raises
650 ConnectionError
651 If the EDS control is not connected.
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)
664def retract_EDS(microscope: tbt.Microscope) -> bool:
665 """
666 Retract the EDS detector from the microscope.
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.
670 ## Parameters
672 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EDS camera.
674 ## Returns
676 - `bool`: True if the EDS camera is successfully retracted.
678 ## Raises
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
704def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool:
705 """
706 Retract the specified detector from the microscope.
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.
710 ## Parameters
712 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the detector.
713 - `detector` (`tbt.DetectorType`): The type of the detector to retract.
715 ## Returns
717 - `bool`: True if the detector is successfully retracted.
719 ## Raises
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)
735 return True
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.
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.
747 ## Parameters
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).
752 ## Returns
754 - `bool`: True if the CCD camera is successfully paused.
756 ## Warnings
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)
771 return True
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.
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.
783 ## Parameters
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).
788 ## Returns
790 - `bool`: True if the CCD camera is successfully used for visualization.
792 ## Warnings
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)
807 return True
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.
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.
820 ## Parameters
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).
826 ## Returns
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)
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 # )
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()
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 )
878 return current_na