Coverage for src/pytribeam/image.py: 19%
349 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-23 20:42 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-23 20:42 +0000
1#!/usr/bin/python3
2"""Microscope imaging configuration and acquisition utilities.
4This module provides the imaging-control functions used throughout `pytribeam`.
5It includes helpers for selecting microscope views and imaging devices,
6configuring electron/ion beam parameters, setting detector state, configuring
7scan settings, and acquiring image frames.
9Most workflows should use `image_operation` or `prepare_imaging` rather than
10calling low-level beam, detector, and scan setters directly.
12## Typical usage
14```python
15from pytribeam import image
17image.image_operation(
18 step=step,
19 image_settings=image_settings,
20 general_settings=general_settings,
21 slice_number=slice_number,
22)
23```
25For lower-level control:
27```python
28from pytribeam import image
29import pytribeam.types as tbt
31image.set_view(microscope, tbt.ViewQuad.UPPER_LEFT)
32image.set_beam_device(microscope, tbt.Device.ELECTRON_BEAM)
33image.prepare_imaging(image_settings)
34```
36## Main entry points
38- `image_operation`: perform a complete image-acquisition operation for one
39 workflow step and slice.
40- `prepare_imaging`: apply beam, detector, and scan settings before acquisition.
41- `collect_single_image`: acquire and save one image frame.
42- `collect_multiple_images`: acquire multiple image frames.
43- `imaging_device`: select the active beam/device and prepare voltage/current.
44- `imaging_detector`: configure and insert the requested detector when needed.
45- `imaging_scan`: apply scan settings other than resolution.
47## Beam configuration
49Beam helper functions configure common electron- and ion-beam properties,
50including voltage, current, dwell time, horizontal field width, working distance,
51scan rotation, scan resolution, full-frame scan mode, and angular corrections.
53The generic beam helpers operate on `tbt.ElectronBeam` and `tbt.IonBeam`
54settings objects and use shared dispatch/utility behavior to access the
55corresponding microscope beam object.
57## Detector configuration
59Detector helper functions select detector type and mode, set contrast and
60brightness, and optionally run automatic contrast/brightness adjustment.
61Insertable detectors are handled through `pytribeam.insertable_devices`.
63## Image acquisition
65The acquisition helpers support both preset and custom scan resolutions. Image
66files are written according to the paths and formats defined by the active
67`tbt.ImageSettings` and workflow settings.
69## Unit conventions
71User-facing imaging settings use explicit field names to indicate units:
73| Quantity | Units |
74| --- | --- |
75| Beam voltage | kilovolts |
76| Beam current | nanoamperes |
77| Dwell time | microseconds |
78| Horizontal field width | millimeters |
79| Working distance | millimeters |
80| Scan rotation | degrees |
81| Contrast/brightness | normalized range from 0 to 1 |
83Values are converted internally as needed before being passed to the microscope
84API.
86> **Warning**
87>
88> Functions in this module can change microscope beam, detector, scan, and image
89> acquisition state. Confirm that the selected beam, detector, field of view,
90> scan conditions, and save paths are appropriate before acquiring images.
92<hr style="height: 12px; background-color: #333; border: none;">
93"""
95__all__ = [
96 "RestoreError",
97 "detector_settings_restored",
98 "get_available_detector_types",
99 "get_available_detector_modes",
100 "get_available_insertable_detector_states",
101 "get_available_insertable_detectors",
102 "beam_angular_correction",
103 "beam_current",
104 "beam_dwell_time",
105 "beam_hfw",
106 "beam_ready",
107 "beam_scan_full_frame",
108 "beam_scan_resolution",
109 "beam_scan_rotation",
110 "beam_voltage",
111 "beam_working_distance",
112 "collect_multiple_images",
113 "collect_single_image",
114 "detector_auto_cb",
115 "detector_brightness",
116 "detector_cb",
117 "detector_contrast",
118 "detector_mode",
119 "detector_type",
120 "grab_custom_resolution_frame",
121 "grab_preset_resolution_frame",
122 "image_operation",
123 "imaging_detector",
124 "imaging_device",
125 "imaging_scan",
126 "prepare_imaging",
127 "set_beam_device",
128 "set_view",
129]
131# Default python modules
132# from functools import singledispatch
133import contextlib
134from pathlib import Path
135import time
136import warnings
137from typing import Iterator, List, Optional
138import math
140# Local scripts
141import pytribeam.constants as cs
142import pytribeam.insertable_devices as devices
143import pytribeam.types as tbt
144import pytribeam.utilities as ut
147class RestoreError(RuntimeError):
148 """The device, detector type, or detector mode could not be restored after a
149 temporary change, so the microscope UI may not be as it was."""
152@contextlib.contextmanager
153def detector_settings_restored(
154 microscope: tbt.Microscope,
155 device: Optional[tbt.Device] = None,
156) -> Iterator[tbt.DetectorType]:
157 """
158 Temporarily switch the active device, and restore the device, detector type,
159 and detector mode afterwards, however the block exits.
161 Used by the functions that probe which detectors and modes are available,
162 which change the active detector while they search. The detector type and
163 mode are read after switching to *device*, since detector settings belong
164 to the device, and restored on it before switching back.
166 ## Parameters
168 - `microscope` (`tbt.Microscope`): The microscope object to use.
169 - `device` (`tbt.Device, optional`): The device to switch to while probing. If None, the active device is used.
171 ## Yields
173 - `tbt.DetectorType`: The detector type that was active on *device* before the block.
175 ## Raises
177 - `RestoreError`: If any setting could not be restored. Every setting is still attempted. If the block itself raised, that error propagates unchanged and the restore failure is reported as a warning, so neither is lost.
178 """
179 original_device = tbt.Device(microscope.imaging.get_active_device())
180 if device is None: 180 ↛ 182line 180 didn't jump to line 182 because the condition on line 180 was always true
181 device = original_device
182 set_beam_device(microscope, device)
183 original_detector_type = tbt.DetectorType(microscope.detector.type.value)
184 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value)
185 block_raised = True
186 try:
187 yield original_detector_type
188 block_raised = False
189 finally:
190 steps = ( 190 ↛ exit, 190 ↛ 2012 missed branches: 1) line 190 didn't jump to the function exit, 2) line 190 didn't jump to line 201 because
191 (
192 "detector type",
193 lambda: detector_type(microscope, original_detector_type),
194 ),
195 (
196 "detector mode",
197 lambda: detector_mode(microscope, original_detector_mode),
198 ),
199 ("device", lambda: set_beam_device(microscope, original_device)),
200 )
201 failed = []
202 for name, step in steps:
203 try:
204 step()
205 except Exception as error:
206 failed.append(f"{name} ({error})")
207 if failed:
208 message = "Could not restore the " + ", ".join(failed) + "."
209 if block_raised:
210 warnings.warn(message) # the block's own error is propagating
211 else:
212 raise RestoreError(message)
215def get_available_detector_types(
216 microscope: tbt.Microscope, device: Optional[tbt.Device] = None
217) -> List[tbt.DetectorType]:
218 """
219 Get the list of available detector types for a given device
221 ## Parameters
223 - `microscope` (`Microscope`): The microscope object from which to retrieve the application files.
224 - `device` (`Device`): The device to check.
226 ## Returns
228 - `List[DetectorType]`: A sorted list of detector types available on the microscope.
230 The active device, detector type, and detector mode are restored afterwards,
231 even if probing fails (see `detector_settings_restored`).
233 ## Raises
235 - `RestoreError`: If a setting could not be restored afterwards.
236 """
237 with detector_settings_restored(microscope, device):
238 # Make sure each detector type AutoScript lists can actually be set (it lies)
239 available_types = []
240 for dt in microscope.detector.type.available_values:
241 dt = tbt.DetectorType(dt)
242 try:
243 # make requested detector the active detector
244 detector_type(microscope, dt)
245 except:
246 pass
247 else:
248 available_types.append(dt)
249 return available_types
252def get_available_detector_modes(
253 microscope: tbt.Microscope,
254 device: Optional[tbt.Device] = None,
255 detector: Optional[tbt.DetectorType] = None,
256):
257 """
258 Get the list of available detector modes for a given detector type
260 ## Parameters
262 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
263 - `device` (`Device`): The device to check.
264 - `detector_type` (`tbt.DetectorType`): The detector type to check modes for. If None, returns the modes for the current device
266 ## Returns
268 - `List[DetectorMode]`: The detector modes available for the detector, or None (with a warning) if the detector cannot be selected on this device.
270 The active device, detector type, and detector mode are restored afterwards,
271 even if probing fails (see `detector_settings_restored`).
273 ## Raises
275 - `RestoreError`: If a setting could not be restored afterwards.
276 """
277 with detector_settings_restored(microscope, device) as original_detector_type:
278 if detector is None:
279 detector = original_detector_type
281 # Set the detector to the requested value
282 if original_detector_type != detector: 282 ↛ 295line 282 didn't jump to line 295 because the condition on line 282 was always true
283 try:
284 # make requested detector the active detector
285 detector_type(microscope, detector)
286 except:
287 warnings.warn(
288 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device
289 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system.
290 No detector modes will be returned."""
291 )
292 return None
294 # Get the available values of detector mode for this type
295 available_modes = [ 295 ↛ exitline 295 didn't run the list comprehension on line 295
296 tbt.DetectorMode(dm) for dm in microscope.detector.mode.available_values
297 ]
298 return available_modes
301def get_available_insertable_detector_states(
302 microscope: tbt.Microscope,
303) -> List[tbt.DetectorType]:
304 """
305 Get the state of all insertable detectors available on the microscope
307 ## Parameters
309 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
311 ## Returns
313 - `List[Tuple[DetectorType, RetractableDeviceState]]`: Each insertable detector and its state.
315 The active device, detector type, and detector mode are restored afterwards,
316 even if probing fails (see `detector_settings_restored`).
318 ## Raises
320 - `RestoreError`: If a setting could not be restored afterwards.
321 """
322 device = tbt.Device.ELECTRON_BEAM
323 with detector_settings_restored(microscope, device):
324 insertable_detector_states = []
325 for dt in get_available_detector_types(microscope, device): 325 ↛ 329line 325 didn't jump to line 329 because the loop on line 325 didn't complete
326 if devices.detector_insertable(microscope=microscope, detector=dt): 326 ↛ 325line 326 didn't jump to line 325 because the condition on line 326 was always true
327 state = devices.detector_state(microscope=microscope, detector=dt)
328 insertable_detector_states.append((dt, state))
329 return insertable_detector_states
332def get_available_insertable_detectors(
333 microscope: tbt.Microscope,
334) -> List[tbt.DetectorType]:
335 """
336 Get the list of available detector types that are insertable
338 ## Parameters
340 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
342 ## Returns
344 - `List[DetectorType]`: A sorted list of insertable detector types available on the microscope.
346 The active device, detector type, and detector mode are restored afterwards,
347 even if probing fails (see `detector_settings_restored`).
349 ## Raises
351 - `RestoreError`: If a setting could not be restored afterwards.
352 """
353 device = tbt.Device.ELECTRON_BEAM
354 with detector_settings_restored(microscope, device):
355 insertable_detectors = []
356 for dt in get_available_detector_types(microscope, device):
357 if devices.detector_insertable(microscope=microscope, detector=dt):
358 insertable_detectors.append(dt)
359 return insertable_detectors
362def beam_angular_correction(
363 microscope: tbt.Microscope,
364 dynamic_focus: bool,
365 tilt_correction: bool,
366 # correction_angle_deg: float = None,
367 delay_s: float = 0.5,
368) -> bool:
369 """
370 Uses auto mode to set tilt correction and dynamic focus.
372 This function configures the electron beam's angular correction mode to automatic,
373 sets the scan rotation to zero, and enables or disables dynamic focus and tilt correction
374 based on the provided parameters.
376 ## Parameters
378 - `microscope` (`tbt.Microscope`): The microscope object to configure.
379 - `dynamic_focus` (`bool`): If True, dynamic focus will be turned on. If False, dynamic focus will be turned off.
380 - `tilt_correction` (`bool`): If True, tilt correction will be turned on. If False, tilt correction will be turned off.
381 - `delay_s` (`float, optional`): The delay in seconds to wait after turning dynamic focus or tilt correction on or off (default is 0.5).
383 ## Returns
385 - `bool`: True if the configuration is successful, False otherwise.
387 ## Raises
389 - `SystemError`: If unable to turn dynamic focus or tilt correction on or off.
391 ## Examples
393 >>> import pytribeam.types as tbt
394 >>> microscope = tbt.Microscope()
395 >>> microscope.connect("localhost")
396 Client connecting to [localhost:7520]...
397 Client connected to [localhost:7520]
398 >>> success = beam_angular_correction(microscope, dynamic_focus=True, tilt_correction=False)
399 >>> print(success)
400 True"""
401 angular_correction = microscope.beams.electron_beam.angular_correction
402 angular_correction.mode = tbt.AngularCorrectionMode.AUTOMATIC
403 # scan rotation must be zero for auto mode
404 microscope.beams.electron_beam.scanning.rotation.value = 0.0
406 # manual adjustment not implemented yet, only works at zero scan rotation
407 # if correction_angle_deg is not None:
408 # angular_correction.mode = tbt.AngularCorrectionMode.MANUAL
409 if dynamic_focus: 409 ↛ 414line 409 didn't jump to line 414 because the condition on line 409 was always true
410 angular_correction.dynamic_focus.turn_on()
411 time.sleep(delay_s)
412 if not angular_correction.dynamic_focus.is_on:
413 raise SystemError("Unable to turn dynamic focus on.")
414 if (not dynamic_focus) or (dynamic_focus is None):
415 angular_correction.dynamic_focus.turn_off()
416 time.sleep(delay_s)
417 if angular_correction.dynamic_focus.is_on:
418 raise SystemError("Unable to turn dynamic focus off.")
420 if tilt_correction:
421 angular_correction.tilt_correction.turn_on()
422 time.sleep(delay_s)
423 if not angular_correction.tilt_correction.is_on:
424 raise SystemError("Unable to turn tilt correction on.")
425 if (not tilt_correction) or (tilt_correction is None):
426 angular_correction.tilt_correction.turn_off()
427 time.sleep(delay_s)
428 if angular_correction.tilt_correction.is_on:
429 raise SystemError("Unable to turn tilt correction off.")
432def beam_current(
433 beam: tbt.Beam,
434 microscope: tbt.Microscope,
435 current_na: float,
436 current_tol_na: float,
437 delay_s: float = 5.0,
438) -> bool:
439 """
440 Sets the current for the selected beam type, with inputs in units of nanoamps.
442 This function sets the beam current for the specified beam type on the microscope.
443 If the current difference exceeds the tolerance, it adjusts the beam current and
444 waits for the specified delay.
446 ## Parameters
448 - `beam` (`tbt.Beam`): The beam type to configure.
449 - `microscope` (`tbt.Microscope`): The microscope object to configure.
450 - `current_na` (`float`): The desired beam current in nanoamps.
451 - `current_tol_na` (`float`): The tolerance for the beam current in nanoamps.
452 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam current (default is 5.0).
454 ## Returns
456 - `bool`: True if the beam current is set successfully, False otherwise.
458 ## Raises
460 - `ValueError`: If the beam current cannot be adjusted within the specified tolerance.
462 ## Examples
464 >>> import pytribeam.types as tbt
465 >>> microscope = tbt.Microscope()
466 >>> microscope.connect("localhost")
467 Client connecting to [localhost:7520]...
468 Client connected to [localhost:7520]
469 >>> beam = tbt.ElectronBeam(settings=None)
470 >>> success = beam_current(beam, microscope, current_na=1.0, current_tol_na=0.1)
471 >>> print(success)
472 True"""
473 selected_beam = ut.beam_type(beam, microscope)
475 exisiting_current_a = selected_beam.beam_current.value # amps
476 delta_current_na = abs(
477 exisiting_current_a * cs.Conversions.A_TO_NA - current_na
478 ) # nanoamps
479 if delta_current_na > current_tol_na:
480 warnings.warn(
481 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
482 )
483 print("Adjusting beam current...")
484 selected_beam.beam_current.value = current_na * cs.Conversions.NA_TO_A
485 time.sleep(delay_s)
487 new_current_na = selected_beam.beam_current.value * cs.Conversions.A_TO_NA
488 current_diff_na = abs(new_current_na - current_na)
489 if current_diff_na > current_tol_na:
490 raise ValueError(
491 f"""Could not correctly adjust beam voltage,
492 requested {current_na} nA, current beam current is
493 {new_current_na} nA"""
494 )
496 return True
499def beam_dwell_time(
500 beam: tbt.Beam,
501 microscope: tbt.Microscope,
502 dwell_us: float,
503 delay_s: float = 0.1,
504) -> bool:
505 """
506 Sets the dwell time for the selected beam, with inputs in units of microseconds.
508 This function sets the dwell time for the specified beam type on the microscope.
509 It converts the dwell time from microseconds to seconds, sets the dwell time, and
510 waits for the specified delay.
512 ## Parameters
514 - `beam` (`tbt.Beam`): The beam type to configure.
515 - `microscope` (`tbt.Microscope`): The microscope object to configure.
516 - `dwell_us` (`float`): The desired dwell time in microseconds.
517 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the dwell time (default is 0.1).
519 ## Returns
521 - `bool`: True if the dwell time is set successfully, False otherwise.
523 ## Raises
525 - `ValueError`: If the dwell time cannot be adjusted correctly.
527 ## Examples
529 >>> import pytribeam.types as tbt
530 >>> microscope = tbt.Microscope()
531 >>> microscope.connect("localhost")
532 Client connecting to [localhost:7520]...
533 Client connected to [localhost:7520]
534 >>> beam = tbt.ElectronBeam(settings=None)
535 >>> success = beam_dwell_time(beam, microscope, dwell_us=10.0)
536 >>> print(success)
537 True"""
538 selected_beam = ut.beam_type(beam, microscope)
539 dwell_s = dwell_us * cs.Conversions.US_TO_S
540 selected_beam.scanning.dwell_time.value = dwell_s
541 time.sleep(delay_s)
542 if not math.isclose(
543 selected_beam.scanning.dwell_time.value,
544 dwell_s,
545 rel_tol=cs.Constants.beam_dwell_tol_ratio,
546 ):
547 raise ValueError(
548 f"""Could not correctly adjust dwell time,
549 requested {dwell_s} seconds, current dwell time is
550 {selected_beam.scanning.dwell_time.value} seconds"""
551 )
553 return True
556def beam_hfw(
557 beam: tbt.Beam,
558 microscope: tbt.Microscope,
559 hfw_mm: float,
560 delay_s: float = 0.1,
561 hfw_tol_mm: float = 1e-6, # 1 nm of tolerance
562) -> bool:
563 """
564 Sets the horizontal field width for the selected beam, with inputs in units of millimeters.
566 This function sets the horizontal field width (HFW) for the specified beam type on the microscope.
567 It converts the HFW from millimeters to meters, sets the HFW, and waits for the specified delay.
568 This should be done after adjusting the working distance.
570 ## Parameters
572 - `beam` (`tbt.Beam`): The beam type to configure.
573 - `microscope` (`tbt.Microscope`): The microscope object to configure.
574 - `hfw_mm` (`float`): The desired horizontal field width in millimeters.
575 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the HFW (default is 0.1).
577 ## Returns
579 - `bool`: True if the HFW is set successfully, False otherwise.
581 ## Raises
583 - `ValueError`: If the HFW cannot be adjusted correctly.
585 ## Examples
587 >>> import pytribeam.types as tbt
588 >>> microscope = tbt.Microscope()
589 >>> microscope.connect("localhost")
590 Client connecting to [localhost:7520]...
591 Client connected to [localhost:7520]
592 >>> beam = tbt.ElectronBeam(settings=None)
593 >>> success = beam_hfw(beam, microscope, hfw_mm=1.0)
594 >>> print(success)
595 True"""
596 selected_beam = ut.beam_type(beam, microscope)
597 hfw_m = hfw_mm * cs.Conversions.MM_TO_M
598 selected_beam.horizontal_field_width.value = hfw_m
599 time.sleep(delay_s)
600 if not math.isclose(
601 selected_beam.horizontal_field_width.value,
602 hfw_m,
603 abs_tol=hfw_tol_mm
604 * cs.Conversions.MM_TO_M, # convert to meters for autoscript calls
605 ):
606 raise ValueError(
607 f"""Could not correctly adjust horizontal field width,
608 requested {hfw_mm} millimeters, current field with is
609 {selected_beam.horizontal_field_width.value * cs.Conversions.M_TO_MM} millimeters"""
610 )
612 return True
615def beam_ready(
616 beam: tbt.Beam,
617 microscope: tbt.Microscope,
618 delay_s: float = 5.0,
619 attempts: int = 2,
620) -> bool:
621 """
622 Checks if the beam is on or blanked, and tries to turn it on and unblank it if possible.
624 This function checks the vacuum state, ensures the beam is on, and unblanks the beam if it is blanked.
625 It makes multiple attempts to turn on and unblank the beam, waiting for the specified delay between attempts.
627 ## Parameters
629 - `beam` (`tbt.Beam`): The beam type to check.
630 - `microscope` (`tbt.Microscope`): The microscope object to check.
631 - `delay_s` (`float, optional`): The delay in seconds to wait between attempts (default is 5.0).
632 - `attempts` (`int, optional`): The number of attempts to turn on and unblank the beam (default is 2).
634 ## Returns
636 - `bool`: True if the beam is ready (on and unblanked), False otherwise.
638 ## Raises
640 - `ValueError`: If the vacuum is not pumped. If the beam cannot be turned on after the specified number of attempts. If the beam cannot be unblanked after the specified number of attempts.
642 ## Examples
644 >>> import pytribeam.types as tbt
645 >>> microscope = tbt.Microscope()
646 >>> microscope.connect("localhost")
647 Client connecting to [localhost:7520]...
648 Client connected to [localhost:7520]
649 >>> beam = tbt.ElectronBeam(settings=None)
650 >>> success = beam_ready(beam, microscope)
651 >>> print(success)
652 True"""
653 selected_beam = ut.beam_type(beam, microscope)
655 # check vaccum
656 vacuum = microscope.vacuum.chamber_state
657 if vacuum != tbt.VacuumState.PUMPED.value:
658 raise ValueError(f'Vacuum is not pumped, current state is "{vacuum}"')
660 # check beam on
661 count: int = 0
662 while count < attempts: 662 ↛ 663, 662 ↛ 6682 missed branches: 1) line 662 didn't jump to line 663 because the condition on line 662 was never true, 2) line 662 didn't jump to line 668 because the condition on line 662 was always true
663 beam_on = selected_beam.is_on
664 if not beam_on: 664 ↛ 667line 664 didn't jump to line 667 because the condition on line 664 was always true
665 selected_beam.turn_on()
666 time.sleep(delay_s)
667 count += 1
668 if not beam_on:
669 raise ValueError(
670 f"Unable to turn on {beam.type} beam after {attempts} attempts."
671 )
673 # check beam blank
674 count: int = 0
675 while count < attempts:
676 beam_blank = selected_beam.is_blanked
677 if beam_blank:
678 selected_beam.unblank()
679 time.sleep(delay_s)
680 count += 1
681 if beam_blank:
682 raise ValueError(
683 f"Unable to unblank {beam.type} beam after {attempts} attempts."
684 )
686 return True
689def beam_scan_full_frame(
690 beam: tbt.Beam,
691 microscope: tbt.Microscope,
692) -> bool:
693 """
694 Set beam scan mode to full frame.
696 This function sets the scanning mode of the specified beam to full frame on the microscope.
698 ## Parameters
700 - `beam` (`tbt.Beam`): The beam type to configure.
701 - `microscope` (`tbt.Microscope`): The microscope object to configure.
703 ## Returns
705 - `bool`: True if the scan mode is set to full frame successfully, False otherwise.
707 ## Raises
709 - `SystemError`: If unable to set the scan mode to full frame.
711 ## Examples
713 >>> import pytribeam.types as tbt
714 >>> microscope = tbt.Microscope()
715 >>> microscope.connect("localhost")
716 Client connecting to [localhost:7520]...
717 Client connected to [localhost:7520]
718 >>> beam = tbt.ElectronBeam(settings=None)
719 >>> success = beam_scan_full_frame(beam, microscope)
720 >>> print(success)
721 True"""
722 selected_beam = ut.beam_type(beam, microscope)
723 selected_beam.scanning.mode.set_full_frame()
724 scan_mode = selected_beam.scanning.mode.value
725 if not scan_mode == tbt.ScanMode.FULL_FRAME.value:
726 raise SystemError(
727 f"Unable to set imaging to full frame. Current scan mode is {scan_mode}."
728 )
729 return True
732def beam_scan_resolution(
733 beam: tbt.Beam,
734 microscope: tbt.Microscope,
735 resolution: tbt.Resolution,
736 delay_s: float = 0.1,
737) -> bool:
738 """
739 Sets the scan resolution for the selected beam, with inputs in units of preset resolutions.
741 This function sets the scan resolution for the specified beam type on the microscope.
742 It only works for preset resolutions and waits for the specified delay after setting the resolution.
744 ## Parameters
746 - `beam` (`tbt.Beam`): The beam type to configure.
747 - `microscope` (`tbt.Microscope`): The microscope object to configure.
748 - `resolution` (`tbt.Resolution`): The desired scan resolution (must be a preset resolution).
749 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the resolution (default is 0.1).
751 ## Returns
753 - `bool`: True if the scan resolution is set successfully, False otherwise.
755 ## Raises
757 - `ValueError`: If a custom resolution is requested or if the resolution cannot be adjusted correctly.
759 ## Examples
761 >>> import pytribeam.types as tbt
762 >>> import pytribeam.utility as ut
763 >>> microscope = tbt.Microscope()
764 >>> microscope.connect("localhost")
765 Client connecting to [localhost:7520]...
766 Client connected to [localhost:7520]
767 >>> beam = tbt.ElectronBeam(settings=None)
768 >>> resolution = tbt.PresetResolution.HIGH
769 >>> success = beam_scan_resolution(beam, microscope, resolution)
770 >>> print(success)
771 True"""
772 if not isinstance(resolution, tbt.PresetResolution):
773 raise ValueError(
774 f"Requested a custom resolution of {resolution.value}. Only preset resolutions allowed."
775 )
777 selected_beam = ut.beam_type(beam, microscope)
778 selected_beam.scanning.resolution.value = resolution.value
779 time.sleep(delay_s)
780 if selected_beam.scanning.resolution.value != resolution.value:
781 raise ValueError(
782 f"""Could not correctly adjust scan resolution,
783 requested {resolution}, current resolution is
784 {selected_beam.scanning.resolution.value}"""
785 )
787 return True
790def beam_scan_rotation(
791 beam: tbt.Beam,
792 microscope: tbt.Microscope,
793 rotation_deg: float,
794 delay_s: float = 0.1,
795) -> bool:
796 """
797 Sets the scan rotation for the selected beam, with inputs in units of degrees.
799 This function sets the scan rotation for the specified beam type on the microscope.
800 It converts the rotation from degrees to radians, sets the scan rotation, and waits for the specified delay.
802 ## Parameters
804 - `beam` (`tbt.Beam`): The beam type to configure.
805 - `microscope` (`tbt.Microscope`): The microscope object to configure.
806 - `rotation_deg` (`float`): The desired scan rotation in degrees.
807 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the scan rotation (default is 0.1).
809 ## Returns
811 - `bool`: True if the scan rotation is set successfully, False otherwise.
813 ## Raises
815 - `ValueError`: If the scan rotation cannot be adjusted correctly.
817 ## Examples
819 >>> import pytribeam.types as tbt
820 >>> microscope = tbt.Microscope()
821 >>> microscope.connect("localhost")
822 Client connecting to [localhost:7520]...
823 Client connected to [localhost:7520]
824 >>> beam = tbt.ElectronBeam(settings=None)
825 >>> success = beam_scan_rotation(beam, microscope, rotation_deg=45.0)
826 >>> print(success)
827 True"""
828 selected_beam = ut.beam_type(beam, microscope)
829 rotation_rad = rotation_deg * cs.Conversions.DEG_TO_RAD
830 selected_beam.scanning.rotation.value = rotation_rad
831 time.sleep(delay_s)
832 if selected_beam.scanning.rotation.value != rotation_rad:
833 raise ValueError(
834 f"""Could not correctly adjust scan rotation,
835 requested {rotation_rad} radians, current scan rotation is
836 {selected_beam.scanning.rotation.value} radians"""
837 )
839 return True
842def beam_voltage(
843 beam: tbt.Beam,
844 microscope: tbt.Microscope,
845 voltage_kv: float,
846 voltage_tol_kv: float,
847 delay_s: float = 5.0,
848) -> bool:
849 """
850 Sets the voltage for a given beam type, with inputs in units of kilovolts.
852 This function sets the beam voltage for the specified beam type on the microscope.
853 If the voltage difference exceeds the tolerance, it adjusts the beam voltage and waits for the specified delay.
855 ## Parameters
857 - `beam` (`tbt.Beam`): The beam type to configure.
858 - `microscope` (`tbt.Microscope`): The microscope object to configure.
859 - `voltage_kv` (`float`): The desired beam voltage in kilovolts.
860 - `voltage_tol_kv` (`float`): The tolerance for the beam voltage in kilovolts.
861 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam voltage (default is 5.0).
863 ## Returns
865 - `bool`: True if the beam voltage is set successfully, False otherwise.
867 ## Raises
869 - `ValueError`: If the beam voltage cannot be adjusted within the specified tolerance. If the beam is not controllable.
871 ## Examples
873 >>> import pytribeam.types as tbt
874 >>> microscope = tbt.Microscope()
875 >>> microscope.connect("localhost")
876 Client connecting to [localhost:7520]...
877 Client connected to [localhost:7520]
878 >>> beam = tbt.ElectronBeam(settings=None)
879 >>> success = beam_voltage(beam, microscope, voltage_kv=15.0, voltage_tol_kv=0.5)
880 >>> print(success)
881 True"""
882 selected_beam = ut.beam_type(beam, microscope)
884 exisiting_voltage_v = selected_beam.high_voltage.value # volts
885 delta_voltage_kv = abs(
886 exisiting_voltage_v * cs.Conversions.V_TO_KV - voltage_kv
887 ) # kilovolts
888 if delta_voltage_kv > voltage_tol_kv: 888 ↛ 910line 888 didn't jump to line 910 because the condition on line 888 was always true
889 warnings.warn(
890 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
891 )
892 beam_controllable = selected_beam.high_voltage.is_controllable
894 if not beam_controllable:
895 raise ValueError(
896 f"Unable to modify beam voltage, beam is not currently controllable."
897 )
898 print("Adjusting beam voltage...")
899 selected_beam.high_voltage.value = voltage_kv * cs.Conversions.KV_TO_V
900 time.sleep(delay_s)
901 new_voltage_kv = selected_beam.high_voltage.value * cs.Conversions.V_TO_KV
902 voltage_diff_kv = abs(new_voltage_kv - voltage_kv)
903 if voltage_diff_kv > voltage_tol_kv:
904 raise ValueError(
905 f"""Could not correctly adjust beam voltage,
906 requested {voltage_kv} kV, current beam voltage is
907 {new_voltage_kv} kV"""
908 )
910 return True
913def beam_working_distance(
914 beam: tbt.Beam,
915 microscope: tbt.Microscope,
916 wd_mm: float,
917 delay_s: float = 0.1,
918) -> bool:
919 """
920 Sets the working distance for the selected beam, with inputs in units of millimeters.
922 This function sets the working distance (WD) for the specified beam type on the microscope.
923 It converts the WD from millimeters to meters, sets the WD, and waits for the specified delay.
924 This should be done before adjusting the horizontal field width.
926 ## Parameters
928 - `beam` (`tbt.Beam`): The beam type to configure.
929 - `microscope` (`tbt.Microscope`): The microscope object to configure.
930 - `wd_mm` (`float`): The desired working distance in millimeters.
931 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the working distance (default is 0.1).
933 ## Returns
935 - `bool`: True if the working distance is set successfully, False otherwise.
937 ## Raises
939 - `ValueError`: If the working distance cannot be adjusted correctly.
941 ## Examples
943 >>> import pytribeam.types as tbt
944 >>> microscope = tbt.Microscope()
945 >>> microscope.connect("localhost")
946 Client connecting to [localhost:7520]...
947 Client connected to [localhost:7520]
948 >>> beam = tbt.ElectronBeam(settings=None)
949 >>> success = beam_working_dist(beam, microscope, wd_mm=5.0)
950 >>> print(success)
951 True"""
952 selected_beam = ut.beam_type(beam, microscope)
953 wd_m = wd_mm * cs.Conversions.MM_TO_M
954 selected_beam.working_distance.value = wd_m
955 time.sleep(delay_s)
956 if selected_beam.working_distance.value != wd_m:
957 raise ValueError(
958 f"""Could not correctly adjust working distance,
959 requested {wd_m} meters, current working distance is
960 {selected_beam.working_distance.value} meters"""
961 )
963 return True
966def detector_auto_cb(
967 microscope: tbt.Microscope,
968 beam: tbt.Beam,
969 settings: tbt.ScanArea,
970 delay_s: float = 0.1,
971) -> bool:
972 """
973 Detector auto contrast brightness. Currently only reduced scan area option is supported.
975 This function sets the scanning mode to reduced area, runs the auto contrast-brightness function,
976 and then sets the scanning mode back to full frame.
978 ## Parameters
980 - `microscope` (`tbt.Microscope`): The microscope object to configure.
981 - `beam` (`tbt.Beam`): The beam type to configure.
982 - `settings` (`tbt.ScanArea`): The scan area settings for the reduced area.
983 - `delay_s` (`float, optional`): The delay in seconds to wait after each operation (default is 0.1).
985 ## Returns
987 - `bool`: True if the auto contrast-brightness is completed successfully, False otherwise.
989 ## Raises
991 - `SystemError`: If unable to set the scan mode to reduced area or full frame.
993 ## Examples
995 >>> import pytribeam.types as tbt
996 >>> microscope = tbt.Microscope()
997 >>> microscope.connect("localhost")
998 Client connecting to [localhost:7520]...
999 Client connected to [localhost:7520]
1000 >>> beam = tbt.ElectronBeam(settings=None)
1001 >>> auto_cb_settings = tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8)
1002 >>> success = detector_auto_cb(microscope, beam, auto_cb_settings)
1003 >>> print(success)
1004 True"""
1005 selected_beam = ut.beam_type(beam, microscope)
1006 selected_beam.scanning.mode.set_reduced_area(
1007 left=settings.left,
1008 top=settings.top,
1009 width=settings.width,
1010 height=settings.height,
1011 )
1012 scan_mode = selected_beam.scanning.mode.value
1013 if not scan_mode == tbt.ScanMode.REDUCED_AREA.value:
1014 raise SystemError(
1015 f"Unable to set imaging to reduced area before auto contrast-brightness. Current scan mode is {scan_mode}."
1016 )
1017 microscope.auto_functions.run_auto_cb()
1018 time.sleep(delay_s)
1019 selected_beam.scanning.mode.set_full_frame()
1020 time.sleep(delay_s)
1022 new_scan_mode = selected_beam.scanning.mode.value
1023 if new_scan_mode != tbt.ScanMode.FULL_FRAME.value:
1024 raise SystemError(
1025 f"Unable to set imaging back to full frame after auto contrast-brightness. Current scan mode is {new_scan_mode}."
1026 )
1027 return True
1030def detector_brightness(
1031 microscope: tbt.Microscope,
1032 brightness: float,
1033 delay_s: float = 0.1,
1034) -> bool:
1035 """
1036 Sets the detector brightness with input from 0 to 1.
1038 This function sets the brightness for the detector on the microscope and waits for the specified delay.
1040 ## Parameters
1042 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1043 - `brightness` (`float`): The desired brightness value (from 0 to 1).
1044 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the brightness (default is 0.1).
1046 ## Returns
1048 - `bool`: True if the brightness is set successfully, False otherwise.
1050 ## Raises
1052 - `ValueError`: If the brightness cannot be adjusted correctly.
1054 ## Examples
1056 >>> import pytribeam.types as tbt
1057 >>> microscope = tbt.Microscope()
1058 >>> microscope.connect("localhost")
1059 Client connecting to [localhost:7520]...
1060 Client connected to [localhost:7520]
1061 >>> success = detector_brightness(microscope, brightness=0.5)
1062 >>> print(success)
1063 True"""
1064 microscope.detector.brightness.value = brightness
1065 current_detector = microscope.detector.type.value
1066 time.sleep(delay_s)
1067 if not math.isclose(
1068 microscope.detector.brightness.value,
1069 brightness,
1070 abs_tol=cs.Constants.contrast_brightness_tolerance,
1071 ):
1072 raise ValueError(
1073 f"""Could not correctly adjust detector brightness,
1074 requested {brightness} on {current_detector} detector,
1075 current brightness is {microscope.detector.brightness.value}"""
1076 )
1077 return True
1080def detector_contrast(
1081 microscope: tbt.Microscope,
1082 contrast: float,
1083 delay_s: float = 0.1,
1084) -> bool:
1085 """
1086 Sets the detector contrast with input from 0 to 1.
1088 This function sets the contrast for the detector on the microscope and waits for the specified delay.
1090 ## Parameters
1092 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1093 - `contrast` (`float`): The desired contrast value (from 0 to 1).
1094 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the contrast (default is 0.1).
1096 ## Returns
1098 - `bool`: True if the contrast is set successfully, False otherwise.
1100 ## Raises
1102 - `ValueError`: If the contrast cannot be adjusted correctly.
1104 ## Examples
1106 >>> import pytribeam.types as tbt
1107 >>> microscope = tbt.Microscope()
1108 >>> microscope.connect("localhost")
1109 Client connecting to [localhost:7520]...
1110 Client connected to [localhost:7520]
1111 >>> success = detector_contrast(microscope, contrast=0.5)
1112 >>> print(success)
1113 True"""
1114 microscope.detector.contrast.value = contrast
1115 current_detector = microscope.detector.type.value
1116 time.sleep(delay_s)
1117 if not math.isclose(
1118 microscope.detector.contrast.value,
1119 contrast,
1120 abs_tol=cs.Constants.contrast_brightness_tolerance,
1121 ):
1122 raise ValueError(
1123 f"""Could not correctly adjust detector contrast,
1124 requested {contrast} on {current_detector} detector,
1125 current contrast is {microscope.detector.contrast.value}"""
1126 )
1127 return True
1130def detector_cb(
1131 microscope: tbt.Microscope,
1132 detector_settings: tbt.Detector,
1133 beam: tbt.Beam,
1134) -> bool:
1135 """
1136 Sets detector contrast and brightness.
1138 This function sets the contrast and brightness for the detector on the microscope.
1139 It also runs the auto contrast-brightness function if specified in the detector settings.
1140 Supports initial settings of contrast and brightness with fixed values before auto adjustment.
1142 ## Parameters
1144 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1145 - `detector_settings` (`tbt.Detector`): The detector settings, including contrast, brightness, and auto contrast-brightness settings.
1146 - `beam` (`tbt.Beam`): The beam type to configure.
1148 ## Returns
1150 - `bool`: True if the contrast and brightness are set successfully, False otherwise.
1152 ## Examples
1154 >>> import pytribeam.types as tbt
1155 >>> microscope = tbt.Microscope()
1156 >>> microscope.connect("localhost")
1157 Client connecting to [localhost:7520]...
1158 Client connected to [localhost:7520]
1159 >>> beam = tbt.ElectronBeam(settings=None)
1160 >>> detector_settings = tbt.Detector(contrast=None, brightness=None, auto_cb_settings=tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8))
1161 >>> success = detector_cb(microscope, detector_settings, beam)
1162 >>> print(success)
1163 True
1165 >>> detector_settings = tbt.Detector(contrast=0.2, brightness=0.3, auto_cb_settings=None)
1166 >>> success = detector_cb(microscope, detector_settings, beam)
1167 >>> print(success)
1168 True
1169 >>> beam_brightness = microscope.detector.brightness.value
1170 >>> print(beam_brightness)
1171 0.3
1172 >>> beam_contrast = microscope.detector.contrast.value
1173 >>> print(beam_contrast)
1174 0.2"""
1175 ### cannot ensure detector is the active one, will overwrite mode settings, so following line is not used
1176 # microscope.detector.type.value = detector_settings.type.value
1177 contrast = detector_settings.contrast
1178 brightness = detector_settings.brightness
1179 if contrast is not None:
1180 detector_contrast(microscope=microscope, contrast=contrast)
1181 if brightness is not None:
1182 detector_brightness(microscope=microscope, brightness=brightness)
1184 null_scan = tbt.ScanArea(left=None, top=None, width=None, height=None)
1185 if not null_scan == detector_settings.auto_cb_settings:
1186 detector_auto_cb(
1187 microscope=microscope,
1188 settings=detector_settings.auto_cb_settings,
1189 beam=beam,
1190 )
1191 return True
1194def detector_mode(
1195 microscope: tbt.Microscope,
1196 detector_mode: tbt.DetectorMode,
1197 delay_s: float = 0.1,
1198) -> bool:
1199 """
1200 Sets the detector mode.
1202 This function sets the mode for the detector on the microscope and waits for the specified delay.
1204 ## Parameters
1206 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1207 - `detector_mode` (`tbt.DetectorMode`): The desired detector mode.
1208 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector mode (default is 0.1).
1210 ## Returns
1212 - `bool`: True if the detector mode is set successfully, False otherwise.
1214 ## Raises
1216 - `ValueError`: If the detector mode cannot be set correctly.
1218 ## Examples
1220 >>> import pytribeam.types as tbt
1221 >>> microscope = tbt.Microscope()
1222 >>> microscope.connect("localhost")
1223 Client connecting to [localhost:7520]...
1224 Client connected to [localhost:7520]
1225 >>> mode = tbt.DetectorMode.SECONDARY_ELECTRONS
1226 >>> success = detector_mode(microscope, mode)
1227 >>> print(success)
1228 True"""
1229 microscope.detector.mode.value = detector_mode.value
1230 time.sleep(delay_s)
1231 if microscope.detector.mode.value != detector_mode.value:
1232 raise ValueError(
1233 f"""Could not correctly set detector mode,
1234 requested {detector_mode}, current mode is
1235 {microscope.detector.mode.value}"""
1236 )
1237 return True
1240def detector_type(
1241 microscope: tbt.Microscope,
1242 detector: tbt.DetectorType,
1243 delay_s: float = 0.1,
1244) -> bool:
1245 """
1246 Sets the detector type.
1248 This function sets the type for the detector on the microscope and waits for the specified delay.
1250 ## Parameters
1252 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1253 - `detector` (`tbt.DetectorType`): The desired detector type.
1254 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector type (default is 0.1).
1256 ## Returns
1258 - `bool`: True if the detector type is set successfully, False otherwise.
1260 ## Raises
1262 - `ValueError`: If the detector type cannot be set correctly.
1264 ## Examples
1266 >>> import pytribeam.types as tbt
1267 >>> microscope = tbt.Microscope()
1268 >>> microscope.connect("localhost")
1269 Client connecting to [localhost:7520]...
1270 Client connected to [localhost:7520]
1271 >>> detector = tbt.DetectorType.ETD
1272 >>> success = detector_type(microscope, detector)
1273 >>> print(success)
1274 True"""
1275 microscope.detector.type.value = detector.value
1276 time.sleep(delay_s)
1277 if microscope.detector.type.value != detector.value:
1278 raise ValueError(
1279 f"""Could not correctly set detector type,
1280 requested {detector}, current detector is
1281 {microscope.detector.type.value}"""
1282 )
1283 return True
1286def grab_custom_resolution_frame(
1287 img_settings: tbt.ImageSettings,
1288 save_path: Path,
1289) -> bool:
1290 """
1291 Method for single frame imaging used with custom resolutions.
1293 This function captures a single frame image using custom resolutions and saves it to the specified path.
1295 ## Parameters
1297 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope and scan resolution.
1298 - `save_path` (`Path`): The path to save the captured image.
1300 ## Returns
1302 - `bool`: True if the image is captured and saved successfully, False otherwise.
1304 ## Examples
1306 >>> import pytribeam.types as tbt
1307 >>> from pathlib import Path
1308 >>> microscope = tbt.Microscope()
1309 >>> microscope.connect("localhost")
1310 Client connecting to [localhost:7520]...
1311 Client connected to [localhost:7520]
1312 >>> img_settings = tbt.ImageSettings(
1313 ... microscope=microscope,
1314 ... beam=tbt.ElectronBeam(settings=None),
1315 ... detector=tbt.Detector(),
1316 ... scan=tbt.Scan(resolution=tbt.Resolution(width=1024, height=768)),
1317 ... bit_depth=tbt.ColorDepth.BITS_8,
1318 ... )
1319 >>> save_path = Path("/path/to/save/image.tif")
1320 >>> success = grab_custom_resolution_frame(img_settings, save_path)
1321 >>> print(success)
1322 True"""
1323 microscope = img_settings.microscope
1324 resolution = img_settings.scan.resolution
1325 microscope.imaging.grab_frame_to_disk(
1326 save_path.absolute().as_posix(),
1327 file_format=tbt.ImageFileFormat.TIFF.value,
1328 settings=tbt.GrabFrameSettings(
1329 resolution=f"{resolution.width}x{resolution.height}"
1330 ),
1331 )
1332 return True
1335def grab_preset_resolution_frame(
1336 img_settings: tbt.ImageSettings,
1337) -> tbt.AdornedImage:
1338 """
1339 Method for single frame imaging used with preset resolutions.
1341 This function captures a single frame image using preset resolutions.
1343 ## Parameters
1345 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, detector, and scan resolution.
1347 ## Returns
1349 - `tbt.AdornedImage`: The captured image.
1351 ## Examples
1353 >>> import pytribeam.types as tbt
1354 >>> microscope = tbt.Microscope()
1355 >>> microscope.connect("localhost")
1356 Client connecting to [localhost:7520]...
1357 Client connected to [localhost:7520]
1358 >>> img_settings = tbt.ImageSettings(
1359 ... microscope=microscope,
1360 ... beam=tbt.ElectronBeam(settings=None),
1361 ... detector=tbt.Detector(),
1362 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1363 ... bit_depth=tbt.ColorDepth.BITS_8,
1364 ... )
1365 >>> image = grab_preset_resolution_frame(img_settings)
1366 >>> print(image)
1367 AdornedImage(width=768, height=512, bit_depth=8)"""
1368 beam = img_settings.beam
1369 microscope = img_settings.microscope
1370 beam_scan_resolution(
1371 beam=beam,
1372 microscope=microscope,
1373 resolution=img_settings.scan.resolution,
1374 )
1375 return microscope.imaging.grab_frame(
1376 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth)
1377 )
1380def imaging_detector(img_settings: tbt.ImageSettings) -> bool:
1381 """
1382 Prepares the detector and inserts it if applicable.
1384 This function sets the detector type, inserts the detector if necessary, sets the detector mode,
1385 and adjusts the contrast and brightness settings. It is important to set detector mode settings
1386 right before contrast and brightness as any subsequent calls to a detector type can overwrite the mode.
1388 ## Parameters
1390 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and detector settings.
1392 ## Returns
1394 - `bool`: True if the detector is prepared successfully, False otherwise.
1396 ## Examples
1398 >>> import pytribeam.types as tbt
1399 >>> microscope = tbt.Microscope()
1400 >>> microscope.connect("localhost")
1401 Client connecting to [localhost:7520]...
1402 Client connected to [localhost:7520]
1403 >>> img_settings = tbt.ImageSettings(
1404 ... microscope=microscope,
1405 ... beam=tbt.ElectronBeam(settings=None),
1406 ... detector=tbt.Detector(
1407 ... type=tbt.DetectorType.ETD,
1408 ... mode=tbt.DetectorMode.SECONDARY_ELECTRONS,
1409 ... brightness=0.4,
1410 ... contrast=0.2,
1411 ... ),
1412 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1413 ... bit_depth=tbt.ColorDepth.BITS_8,
1414 ... )
1415 >>> success = imaging_detector(img_settings)
1416 >>> print(success)
1417 True"""
1418 microscope = img_settings.microscope
1419 detector = img_settings.detector.type
1420 detector_type(
1421 microscope=microscope,
1422 detector=detector,
1423 )
1424 detector_state = devices.detector_state(
1425 microscope=microscope,
1426 detector=detector,
1427 )
1428 if detector_state is not tbt.RetractableDeviceState.STATIONARY:
1429 devices.insert_detector(
1430 microscope=microscope,
1431 detector=detector,
1432 )
1433 detector_mode(
1434 microscope=microscope,
1435 detector_mode=img_settings.detector.mode,
1436 )
1437 detector_cb(
1438 microscope=microscope,
1439 detector_settings=img_settings.detector,
1440 beam=img_settings.beam,
1441 )
1442 return True
1445def imaging_device(
1446 microscope: tbt.Microscope,
1447 beam: tbt.Beam,
1448) -> bool:
1449 """
1450 Prepares the imaging beam, viewing quad, and the beam voltage and current.
1452 This function sets the beam device, ensures the beam is ready, sets the beam voltage and current,
1453 and applies angular correction if the beam type is electron.
1455 ## Parameters
1457 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1458 - `beam` (`tbt.Beam`): The beam type to configure, including its settings.
1460 ## Returns
1462 - `bool`: True if the imaging device is prepared successfully, False otherwise."""
1463 set_beam_device(microscope=microscope, device=beam.device)
1464 beam_ready(beam=beam, microscope=microscope)
1465 beam_voltage(
1466 beam=beam,
1467 microscope=microscope,
1468 voltage_kv=beam.settings.voltage_kv,
1469 voltage_tol_kv=beam.settings.voltage_tol_kv,
1470 )
1471 beam_current(
1472 beam=beam,
1473 microscope=microscope,
1474 current_na=beam.settings.current_na,
1475 current_tol_na=beam.settings.current_tol_na,
1476 )
1477 if beam.type == tbt.BeamType.ELECTRON: 1477 ↛ 1483line 1477 didn't jump to line 1483 because the condition on line 1477 was always true
1478 beam_angular_correction(
1479 microscope=microscope,
1480 dynamic_focus=beam.settings.dynamic_focus,
1481 tilt_correction=beam.settings.tilt_correction,
1482 )
1483 return True
1486def imaging_scan(img_settings: tbt.ImageSettings) -> bool:
1487 """
1488 Sets all scan settings except for the resolution.
1490 This function configures the scan settings for the specified image settings, including
1491 setting the scan mode to full frame, scan rotation, working distance, horizontal field width,
1492 and dwell time.
1494 ## Parameters
1496 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and scan settings.
1498 ## Returns
1500 - `bool`: True if the scan settings are configured successfully, False otherwise."""
1501 microscope = img_settings.microscope
1502 beam = img_settings.beam
1503 beam_scan_full_frame(
1504 beam=beam,
1505 microscope=microscope,
1506 )
1507 beam_scan_rotation(
1508 beam=beam, microscope=microscope, rotation_deg=img_settings.scan.rotation_deg
1509 )
1510 beam_working_distance(
1511 beam=beam,
1512 microscope=microscope,
1513 wd_mm=img_settings.beam.settings.working_dist_mm,
1514 )
1515 beam_hfw(beam=beam, microscope=microscope, hfw_mm=img_settings.beam.settings.hfw_mm)
1516 beam_dwell_time(
1517 beam=beam, microscope=microscope, dwell_us=img_settings.scan.dwell_time_us
1518 )
1519 return True
1522def prepare_imaging(img_settings: tbt.ImageSettings) -> bool:
1523 """
1524 Prepares various imaging settings.
1526 This function prepares the imaging device, scan settings, and detector settings
1527 based on the specified image settings.
1529 ## Parameters
1531 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1533 ## Returns
1535 - `bool`: True if the imaging settings are prepared successfully, False otherwise."""
1536 imaging_device(microscope=img_settings.microscope, beam=img_settings.beam)
1537 imaging_scan(img_settings=img_settings)
1538 imaging_detector(img_settings=img_settings)
1539 return True
1542def set_view(
1543 microscope: tbt.Microscope,
1544 quad: tbt.ViewQuad,
1545) -> bool:
1546 """
1547 Sets the active view to the specified quad.
1549 This function sets the active imaging view to the specified quad on the microscope.
1551 ## Parameters
1553 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1554 - `quad` (`tbt.ViewQuad`): The imaging view to select: - 1 is upper left - 2 is upper right - 3 is lower left - 4 is lower right
1556 ## Returns
1558 - `bool`: True if the active view is set successfully, False otherwise."""
1559 microscope.imaging.set_active_view(quad.value)
1562def set_beam_device(
1563 microscope: tbt.Microscope,
1564 device: tbt.Device,
1565 delay_s: float = 0.1,
1566) -> bool:
1567 """
1568 Sets the active imaging device.
1570 This function sets the active imaging device on the microscope and waits for the specified delay.
1572 ## Parameters
1574 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1575 - `device` (`tbt.Device`): The desired imaging device.
1576 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the device (default is 0.1).
1578 ## Returns
1580 - `bool`: True if the active device is set successfully, False otherwise.
1582 ## Raises
1584 - `ValueError`: If the active device cannot be set correctly."""
1585 microscope.imaging.set_active_device(device)
1586 time.sleep(delay_s)
1587 curr_device = tbt.Device(microscope.imaging.get_active_device())
1588 if curr_device != device.value:
1589 raise ValueError(
1590 f"""Could not set active device,
1591 requested device {device.value} but current device is {curr_device}.
1592 Device list:
1593 {tbt.Device.ELECTRON_BEAM.value}: Electron Beam
1594 {tbt.Device.ION_BEAM.value}: Ion Beam
1595 {tbt.Device.CCD_CAMERA.value}: CCD Camera
1596 {tbt.Device.IR_CAMERA.value}: IR Camera
1597 {tbt.Device.NAV_CAM.value}: Nav Cam"""
1598 )
1599 return True
1602#################
1605def collect_single_image(
1606 save_path: Path,
1607 img_settings: tbt.ImageSettings,
1608) -> bool:
1609 """
1610 Collects a single frame image with defined image settings.
1612 This function prepares the imaging settings, sets the view, and captures a single frame image.
1613 It saves the image to the specified path. If a non-preset resolution is requested, the image
1614 will be saved at 8-bit color depth.
1616 ## Parameters
1618 - `save_path` (`Path`): The path to save the captured image.
1619 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1621 ## Returns
1623 - `bool`: True if the image is captured and saved successfully, False otherwise."""
1624 beam = img_settings.beam
1625 microscope = img_settings.microscope
1626 set_view(microscope=microscope, quad=beam.default_view)
1627 prepare_imaging(img_settings=img_settings)
1629 resolution = img_settings.scan.resolution
1630 if isinstance(resolution, tbt.PresetResolution):
1631 img = grab_preset_resolution_frame(img_settings=img_settings)
1632 img.save(str(save_path))
1633 return True
1634 warnings.warn(
1635 f'Warning, non-preset resolution of "{img_settings.scan.resolution}" requested. Image will automatically be saved at 8-bit color depth'
1636 )
1637 if img_settings.bit_depth != tbt.ColorDepth.BITS_8:
1638 warnings.warn(
1639 f"Warning, non-preset resolution necessitates images be stored at 8-bit color depth, requested bit-depth of {img_settings.bit_depth.value} will be ignored."
1640 )
1641 grab_custom_resolution_frame(
1642 img_settings=img_settings,
1643 save_path=save_path,
1644 )
1645 return True
1648def collect_multiple_images(
1649 multiple_img_settings: List[tbt.ImageSettings], num_frames: int
1650) -> List[tbt.AdornedImage]:
1651 """
1652 Sets up scanning for multiple frames.
1654 This function is best used for collecting multiple segments on a single detector simultaneously.
1655 It is limited to preset resolutions only.
1657 ## Parameters
1659 - `multiple_img_settings` (`List[tbt.ImageSettings]`): The list of image settings for each frame, including the microscope, beam, scan, and detector settings.
1660 - `num_frames` (`int`): The number of frames to collect.
1662 ## Returns
1664 - `List[tbt.AdornedImage]`: The list of captured images.
1666 ## Raises
1668 - `ValueError`: If a non-preset resolution is requested for simultaneous multiple frame imaging.
1670 ## Notes
1672 This method has not yet been tested."""
1673 # TODO test this
1675 # Ensure that an insertable detector is done first so that inserting the detector doesn't interfere with other detectors
1676 # Will need to make sure that only one insertable detector at a time
1677 insertable_detector = [
1678 devices.detector_state(
1679 microscope=_set.microscope,
1680 detector=_set.detector,
1681 )
1682 is not tbt.RetractableDeviceState.STATIONARY
1683 for _set in multiple_img_settings
1684 ]
1685 if sum(insertable_detector) > 1:
1686 raise NotImplementedError(
1687 "Collecting multiple images with more than one insertable detector is not supported."
1688 )
1689 elif sum(insertable_detector) == 1:
1690 start = multiple_img_settings[insertable_detector.index(True)]
1691 multiple_img_settings = [start] + multiple_img_settings
1693 warnings.warn("Method not yet tested")
1694 views = []
1695 for quad in range(1, num_frames + 1):
1696 img_settings = multiple_img_settings[quad - 1]
1697 resolution = img_settings.scan.resolution
1698 if not isinstance(resolution, tbt.PresetResolution):
1699 raise ValueError(
1700 f'Only preset resolutions allowed for simultaneous multiple frame imaging, but resolution of "{resolution.width}x{resolution.height}" was requested.'
1701 )
1703 microscope = img_settings.microscope
1704 beam = img_settings.beam
1705 views.append(quad)
1706 set_view(microscope=microscope, quad=img_settings.beam.default_view)
1707 prepare_imaging(microscope=microscope, beam=beam, img_settings=img_settings)
1708 frames = microscope.imaging.grab_multiple_frames(
1709 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth, views=views)
1710 )
1711 return frames
1714# TODO
1715# def image_method(dict):
1716# """determine imaging method and return associated function"""
1717# pass
1719# TODO
1720# def collect_tiling_image():
1721# for loop
1722# collect_standard_image
1723# #stage move
1726# TODO add more complex imaging behavior, determine method in this function
1727def image_operation(
1728 step: tbt.Step,
1729 image_settings: tbt.ImageSettings,
1730 general_settings: tbt.GeneralSettings,
1731 slice_number: int,
1732) -> bool:
1733 """
1734 Performs an image operation based on the specified settings.
1736 This function collects an image, saves it to the specified directory, and turns off tilt correction and dynamic focus.
1738 ## Parameters
1740 - `step` (`tbt.Step`): The step information, including the name of the step.
1741 - `image_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1742 - `general_settings` (`tbt.GeneralSettings`): The general settings, including the experimental directory.
1743 - `slice_number` (`int`): The slice number for naming the saved image file.
1745 ## Returns
1747 - `bool`: True if the image operation is performed successfully, False otherwise."""
1748 print("\tCollecting image")
1749 # create folder in same directory as experimental directory
1750 image_directory = Path(general_settings.exp_dir).joinpath(step.name)
1751 image_directory.mkdir(parents=True, exist_ok=True)
1753 # TODO
1754 # determine_image_method()
1755 # collect_multiple_images()
1757 # single image process:
1758 save_path = image_directory.joinpath(f"{slice_number:04}.tif")
1759 collect_single_image(save_path=save_path, img_settings=image_settings)
1760 print(f"\tImage saved to {save_path}")
1762 # turn off tilt correction and dynamic focus
1763 beam_angular_correction(
1764 microscope=image_settings.microscope,
1765 dynamic_focus=False,
1766 tilt_correction=False,
1767 )
1769 return True