Coverage for src/pytribeam/image.py: 86%
283 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-03 19:02 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-03 19:02 +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 "beam_angular_correction",
97 "beam_current",
98 "beam_dwell_time",
99 "beam_hfw",
100 "beam_ready",
101 "beam_scan_full_frame",
102 "beam_scan_resolution",
103 "beam_scan_rotation",
104 "beam_voltage",
105 "beam_working_distance",
106 "collect_multiple_images",
107 "collect_single_image",
108 "detector_auto_cb",
109 "detector_brightness",
110 "detector_cb",
111 "detector_contrast",
112 "detector_mode",
113 "detector_type",
114 "grab_custom_resolution_frame",
115 "grab_preset_resolution_frame",
116 "image_operation",
117 "imaging_detector",
118 "imaging_device",
119 "imaging_scan",
120 "prepare_imaging",
121 "set_beam_device",
122 "set_view",
123]
125# Default python modules
126# from functools import singledispatch
127from pathlib import Path
128import time
129import warnings
130from typing import List
131import math
133# Local scripts
134import pytribeam.constants as cs
135import pytribeam.insertable_devices as devices
136import pytribeam.types as tbt
137import pytribeam.utilities as ut
140def beam_angular_correction(
141 microscope: tbt.Microscope,
142 dynamic_focus: bool,
143 tilt_correction: bool,
144 # correction_angle_deg: float = None,
145 delay_s: float = 0.5,
146) -> bool:
147 """
148 Uses auto mode to set tilt correction and dynamic focus.
150 This function configures the electron beam's angular correction mode to automatic,
151 sets the scan rotation to zero, and enables or disables dynamic focus and tilt correction
152 based on the provided parameters.
154 ## Parameters
156 - `microscope` (`tbt.Microscope`): The microscope object to configure.
157 - `dynamic_focus` (`bool`): If True, dynamic focus will be turned on. If False, dynamic focus will be turned off.
158 - `tilt_correction` (`bool`): If True, tilt correction will be turned on. If False, tilt correction will be turned off.
159 - `delay_s` (`float, optional`): The delay in seconds to wait after turning dynamic focus or tilt correction on or off (default is 0.5).
161 ## Returns
163 - `bool`: True if the configuration is successful, False otherwise.
165 ## Raises
167 - `SystemError`: If unable to turn dynamic focus or tilt correction on or off.
169 ## Examples
171 >>> import pytribeam.types as tbt
172 >>> microscope = tbt.Microscope()
173 >>> microscope.connect("localhost")
174 Client connecting to [localhost:7520]...
175 Client connected to [localhost:7520]
176 >>> success = beam_angular_correction(microscope, dynamic_focus=True, tilt_correction=False)
177 >>> print(success)
178 True"""
179 angular_correction = microscope.beams.electron_beam.angular_correction
180 angular_correction.mode = tbt.AngularCorrectionMode.AUTOMATIC
181 # scan rotation must be zero for auto mode
182 microscope.beams.electron_beam.scanning.rotation.value = 0.0
184 # manual adjustment not implemented yet, only works at zero scan rotation
185 # if correction_angle_deg is not None:
186 # angular_correction.mode = tbt.AngularCorrectionMode.MANUAL
187 if dynamic_focus:
188 angular_correction.dynamic_focus.turn_on()
189 time.sleep(delay_s)
190 if not angular_correction.dynamic_focus.is_on: 190 ↛ 192line 190 didn't jump to line 192 because the condition on line 190 was always true
191 raise SystemError("Unable to turn dynamic focus on.")
192 if (not dynamic_focus) or (dynamic_focus is None): 192 ↛ 198line 192 didn't jump to line 198 because the condition on line 192 was always true
193 angular_correction.dynamic_focus.turn_off()
194 time.sleep(delay_s)
195 if angular_correction.dynamic_focus.is_on: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 raise SystemError("Unable to turn dynamic focus off.")
198 if tilt_correction: 198 ↛ 199line 198 didn't jump to line 199 because the condition on line 198 was never true
199 angular_correction.tilt_correction.turn_on()
200 time.sleep(delay_s)
201 if not angular_correction.tilt_correction.is_on: 201 ↛ 203line 201 didn't jump to line 203 because the condition on line 201 was always true
202 raise SystemError("Unable to turn tilt correction on.")
203 if (not tilt_correction) or (tilt_correction is None): 203 ↛ exitline 203 didn't return from function 'beam_angular_correction' because the condition on line 203 was always true
204 angular_correction.tilt_correction.turn_off()
205 time.sleep(delay_s)
206 if angular_correction.tilt_correction.is_on: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true
207 raise SystemError("Unable to turn tilt correction off.")
210def beam_current(
211 beam: tbt.Beam,
212 microscope: tbt.Microscope,
213 current_na: float,
214 current_tol_na: float,
215 delay_s: float = 5.0,
216) -> bool:
217 """
218 Sets the current for the selected beam type, with inputs in units of nanoamps.
220 This function sets the beam current for the specified beam type on the microscope.
221 If the current difference exceeds the tolerance, it adjusts the beam current and
222 waits for the specified delay.
224 ## Parameters
226 - `beam` (`tbt.Beam`): The beam type to configure.
227 - `microscope` (`tbt.Microscope`): The microscope object to configure.
228 - `current_na` (`float`): The desired beam current in nanoamps.
229 - `current_tol_na` (`float`): The tolerance for the beam current in nanoamps.
230 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam current (default is 5.0).
232 ## Returns
234 - `bool`: True if the beam current is set successfully, False otherwise.
236 ## Raises
238 - `ValueError`: If the beam current cannot be adjusted within the specified tolerance.
240 ## Examples
242 >>> import pytribeam.types as tbt
243 >>> microscope = tbt.Microscope()
244 >>> microscope.connect("localhost")
245 Client connecting to [localhost:7520]...
246 Client connected to [localhost:7520]
247 >>> beam = tbt.ElectronBeam(settings=None)
248 >>> success = beam_current(beam, microscope, current_na=1.0, current_tol_na=0.1)
249 >>> print(success)
250 True"""
251 selected_beam = ut.beam_type(beam, microscope)
253 exisiting_current_a = selected_beam.beam_current.value # amps
254 delta_current_na = abs(
255 exisiting_current_a * cs.Conversions.A_TO_NA - current_na
256 ) # nanoamps
257 if delta_current_na > current_tol_na:
258 warnings.warn(
259 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
260 )
261 print("Adjusting beam current...")
262 selected_beam.beam_current.value = current_na * cs.Conversions.NA_TO_A
263 time.sleep(delay_s)
265 new_current_na = selected_beam.beam_current.value * cs.Conversions.A_TO_NA
266 current_diff_na = abs(new_current_na - current_na)
267 if current_diff_na > current_tol_na: 267 ↛ 268line 267 didn't jump to line 268 because the condition on line 267 was never true
268 raise ValueError(
269 f"""Could not correctly adjust beam voltage,
270 requested {current_na} nA, current beam current is
271 {new_current_na} nA"""
272 )
274 return True
277def beam_dwell_time(
278 beam: tbt.Beam,
279 microscope: tbt.Microscope,
280 dwell_us: float,
281 delay_s: float = 0.1,
282) -> bool:
283 """
284 Sets the dwell time for the selected beam, with inputs in units of microseconds.
286 This function sets the dwell time for the specified beam type on the microscope.
287 It converts the dwell time from microseconds to seconds, sets the dwell time, and
288 waits for the specified delay.
290 ## Parameters
292 - `beam` (`tbt.Beam`): The beam type to configure.
293 - `microscope` (`tbt.Microscope`): The microscope object to configure.
294 - `dwell_us` (`float`): The desired dwell time in microseconds.
295 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the dwell time (default is 0.1).
297 ## Returns
299 - `bool`: True if the dwell time is set successfully, False otherwise.
301 ## Raises
303 - `ValueError`: If the dwell time cannot be adjusted correctly.
305 ## Examples
307 >>> import pytribeam.types as tbt
308 >>> microscope = tbt.Microscope()
309 >>> microscope.connect("localhost")
310 Client connecting to [localhost:7520]...
311 Client connected to [localhost:7520]
312 >>> beam = tbt.ElectronBeam(settings=None)
313 >>> success = beam_dwell_time(beam, microscope, dwell_us=10.0)
314 >>> print(success)
315 True"""
316 selected_beam = ut.beam_type(beam, microscope)
317 dwell_s = dwell_us * cs.Conversions.US_TO_S
318 selected_beam.scanning.dwell_time.value = dwell_s
319 time.sleep(delay_s)
320 if not math.isclose( 320 ↛ 325line 320 didn't jump to line 325 because the condition on line 320 was never true
321 selected_beam.scanning.dwell_time.value,
322 dwell_s,
323 rel_tol=cs.Constants.beam_dwell_tol_ratio,
324 ):
325 raise ValueError(
326 f"""Could not correctly adjust dwell time,
327 requested {dwell_s} seconds, current dwell time is
328 {selected_beam.scanning.dwell_time.value} seconds"""
329 )
331 return True
334def beam_hfw(
335 beam: tbt.Beam,
336 microscope: tbt.Microscope,
337 hfw_mm: float,
338 delay_s: float = 0.1,
339 hfw_tol_mm: float = 1e-6, # 1 nm of tolerance
340) -> bool:
341 """
342 Sets the horizontal field width for the selected beam, with inputs in units of millimeters.
344 This function sets the horizontal field width (HFW) for the specified beam type on the microscope.
345 It converts the HFW from millimeters to meters, sets the HFW, and waits for the specified delay.
346 This should be done after adjusting the working distance.
348 ## Parameters
350 - `beam` (`tbt.Beam`): The beam type to configure.
351 - `microscope` (`tbt.Microscope`): The microscope object to configure.
352 - `hfw_mm` (`float`): The desired horizontal field width in millimeters.
353 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the HFW (default is 0.1).
355 ## Returns
357 - `bool`: True if the HFW is set successfully, False otherwise.
359 ## Raises
361 - `ValueError`: If the HFW cannot be adjusted correctly.
363 ## Examples
365 >>> import pytribeam.types as tbt
366 >>> microscope = tbt.Microscope()
367 >>> microscope.connect("localhost")
368 Client connecting to [localhost:7520]...
369 Client connected to [localhost:7520]
370 >>> beam = tbt.ElectronBeam(settings=None)
371 >>> success = beam_hfw(beam, microscope, hfw_mm=1.0)
372 >>> print(success)
373 True"""
374 selected_beam = ut.beam_type(beam, microscope)
375 hfw_m = hfw_mm * cs.Conversions.MM_TO_M
376 selected_beam.horizontal_field_width.value = hfw_m
377 time.sleep(delay_s)
378 if not math.isclose( 378 ↛ 384line 378 didn't jump to line 384 because the condition on line 378 was never true
379 selected_beam.horizontal_field_width.value,
380 hfw_m,
381 abs_tol=hfw_tol_mm
382 * cs.Conversions.MM_TO_M, # convert to meters for autoscript calls
383 ):
384 raise ValueError(
385 f"""Could not correctly adjust horizontal field width,
386 requested {hfw_mm} millimeters, current field with is
387 {selected_beam.horizontal_field_width.value * cs.Conversions.M_TO_MM} millimeters"""
388 )
390 return True
393def beam_ready(
394 beam: tbt.Beam,
395 microscope: tbt.Microscope,
396 delay_s: float = 5.0,
397 attempts: int = 2,
398) -> bool:
399 """
400 Checks if the beam is on or blanked, and tries to turn it on and unblank it if possible.
402 This function checks the vacuum state, ensures the beam is on, and unblanks the beam if it is blanked.
403 It makes multiple attempts to turn on and unblank the beam, waiting for the specified delay between attempts.
405 ## Parameters
407 - `beam` (`tbt.Beam`): The beam type to check.
408 - `microscope` (`tbt.Microscope`): The microscope object to check.
409 - `delay_s` (`float, optional`): The delay in seconds to wait between attempts (default is 5.0).
410 - `attempts` (`int, optional`): The number of attempts to turn on and unblank the beam (default is 2).
412 ## Returns
414 - `bool`: True if the beam is ready (on and unblanked), False otherwise.
416 ## Raises
418 - `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.
420 ## Examples
422 >>> import pytribeam.types as tbt
423 >>> microscope = tbt.Microscope()
424 >>> microscope.connect("localhost")
425 Client connecting to [localhost:7520]...
426 Client connected to [localhost:7520]
427 >>> beam = tbt.ElectronBeam(settings=None)
428 >>> success = beam_ready(beam, microscope)
429 >>> print(success)
430 True"""
431 selected_beam = ut.beam_type(beam, microscope)
433 # check vaccum
434 vacuum = microscope.vacuum.chamber_state
435 if vacuum != tbt.VacuumState.PUMPED.value: 435 ↛ 436line 435 didn't jump to line 436 because the condition on line 435 was never true
436 raise ValueError(f'Vacuum is not pumped, current state is "{vacuum}"')
438 # check beam on
439 count: int = 0
440 while count < attempts:
441 beam_on = selected_beam.is_on
442 if not beam_on:
443 selected_beam.turn_on()
444 time.sleep(delay_s)
445 count += 1
446 if not beam_on:
447 raise ValueError(
448 f"Unable to turn on {beam.type} beam after {attempts} attempts."
449 )
451 # check beam blank
452 count: int = 0
453 while count < attempts:
454 beam_blank = selected_beam.is_blanked
455 if beam_blank:
456 selected_beam.unblank()
457 time.sleep(delay_s)
458 count += 1
459 if beam_blank:
460 raise ValueError(
461 f"Unable to unblank {beam.type} beam after {attempts} attempts."
462 )
464 return True
467def beam_scan_full_frame(
468 beam: tbt.Beam,
469 microscope: tbt.Microscope,
470) -> bool:
471 """
472 Set beam scan mode to full frame.
474 This function sets the scanning mode of the specified beam to full frame on the microscope.
476 ## Parameters
478 - `beam` (`tbt.Beam`): The beam type to configure.
479 - `microscope` (`tbt.Microscope`): The microscope object to configure.
481 ## Returns
483 - `bool`: True if the scan mode is set to full frame successfully, False otherwise.
485 ## Raises
487 - `SystemError`: If unable to set the scan mode to full frame.
489 ## Examples
491 >>> import pytribeam.types as tbt
492 >>> microscope = tbt.Microscope()
493 >>> microscope.connect("localhost")
494 Client connecting to [localhost:7520]...
495 Client connected to [localhost:7520]
496 >>> beam = tbt.ElectronBeam(settings=None)
497 >>> success = beam_scan_full_frame(beam, microscope)
498 >>> print(success)
499 True"""
500 selected_beam = ut.beam_type(beam, microscope)
501 selected_beam.scanning.mode.set_full_frame()
502 scan_mode = selected_beam.scanning.mode.value
503 if not scan_mode == tbt.ScanMode.FULL_FRAME.value: 503 ↛ 504line 503 didn't jump to line 504 because the condition on line 503 was never true
504 raise SystemError(
505 f"Unable to set imaging to full frame. Current scan mode is {scan_mode}."
506 )
507 return True
510def beam_scan_resolution(
511 beam: tbt.Beam,
512 microscope: tbt.Microscope,
513 resolution: tbt.Resolution,
514 delay_s: float = 0.1,
515) -> bool:
516 """
517 Sets the scan resolution for the selected beam, with inputs in units of preset resolutions.
519 This function sets the scan resolution for the specified beam type on the microscope.
520 It only works for preset resolutions and waits for the specified delay after setting the resolution.
522 ## Parameters
524 - `beam` (`tbt.Beam`): The beam type to configure.
525 - `microscope` (`tbt.Microscope`): The microscope object to configure.
526 - `resolution` (`tbt.Resolution`): The desired scan resolution (must be a preset resolution).
527 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the resolution (default is 0.1).
529 ## Returns
531 - `bool`: True if the scan resolution is set successfully, False otherwise.
533 ## Raises
535 - `ValueError`: If a custom resolution is requested or if the resolution cannot be adjusted correctly.
537 ## Examples
539 >>> import pytribeam.types as tbt
540 >>> import pytribeam.utility as ut
541 >>> microscope = tbt.Microscope()
542 >>> microscope.connect("localhost")
543 Client connecting to [localhost:7520]...
544 Client connected to [localhost:7520]
545 >>> beam = tbt.ElectronBeam(settings=None)
546 >>> resolution = tbt.PresetResolution.HIGH
547 >>> success = beam_scan_resolution(beam, microscope, resolution)
548 >>> print(success)
549 True"""
550 if not isinstance(resolution, tbt.PresetResolution):
551 raise ValueError(
552 f"Requested a custom resolution of {resolution.value}. Only preset resolutions allowed."
553 )
555 selected_beam = ut.beam_type(beam, microscope)
556 selected_beam.scanning.resolution.value = resolution.value
557 time.sleep(delay_s)
558 if selected_beam.scanning.resolution.value != resolution.value: 558 ↛ 559line 558 didn't jump to line 559 because the condition on line 558 was never true
559 raise ValueError(
560 f"""Could not correctly adjust scan resolution,
561 requested {resolution}, current resolution is
562 {selected_beam.scanning.resolution.value}"""
563 )
565 return True
568def beam_scan_rotation(
569 beam: tbt.Beam,
570 microscope: tbt.Microscope,
571 rotation_deg: float,
572 delay_s: float = 0.1,
573) -> bool:
574 """
575 Sets the scan rotation for the selected beam, with inputs in units of degrees.
577 This function sets the scan rotation for the specified beam type on the microscope.
578 It converts the rotation from degrees to radians, sets the scan rotation, and waits for the specified delay.
580 ## Parameters
582 - `beam` (`tbt.Beam`): The beam type to configure.
583 - `microscope` (`tbt.Microscope`): The microscope object to configure.
584 - `rotation_deg` (`float`): The desired scan rotation in degrees.
585 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the scan rotation (default is 0.1).
587 ## Returns
589 - `bool`: True if the scan rotation is set successfully, False otherwise.
591 ## Raises
593 - `ValueError`: If the scan rotation cannot be adjusted correctly.
595 ## Examples
597 >>> import pytribeam.types as tbt
598 >>> microscope = tbt.Microscope()
599 >>> microscope.connect("localhost")
600 Client connecting to [localhost:7520]...
601 Client connected to [localhost:7520]
602 >>> beam = tbt.ElectronBeam(settings=None)
603 >>> success = beam_scan_rotation(beam, microscope, rotation_deg=45.0)
604 >>> print(success)
605 True"""
606 selected_beam = ut.beam_type(beam, microscope)
607 rotation_rad = rotation_deg * cs.Conversions.DEG_TO_RAD
608 selected_beam.scanning.rotation.value = rotation_rad
609 time.sleep(delay_s)
610 if selected_beam.scanning.rotation.value != rotation_rad: 610 ↛ 611line 610 didn't jump to line 611 because the condition on line 610 was never true
611 raise ValueError(
612 f"""Could not correctly adjust scan rotation,
613 requested {rotation_rad} radians, current scan rotation is
614 {selected_beam.scanning.rotation.value} radians"""
615 )
617 return True
620def beam_voltage(
621 beam: tbt.Beam,
622 microscope: tbt.Microscope,
623 voltage_kv: float,
624 voltage_tol_kv: float,
625 delay_s: float = 5.0,
626) -> bool:
627 """
628 Sets the voltage for a given beam type, with inputs in units of kilovolts.
630 This function sets the beam voltage for the specified beam type on the microscope.
631 If the voltage difference exceeds the tolerance, it adjusts the beam voltage and waits for the specified delay.
633 ## Parameters
635 - `beam` (`tbt.Beam`): The beam type to configure.
636 - `microscope` (`tbt.Microscope`): The microscope object to configure.
637 - `voltage_kv` (`float`): The desired beam voltage in kilovolts.
638 - `voltage_tol_kv` (`float`): The tolerance for the beam voltage in kilovolts.
639 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam voltage (default is 5.0).
641 ## Returns
643 - `bool`: True if the beam voltage is set successfully, False otherwise.
645 ## Raises
647 - `ValueError`: If the beam voltage cannot be adjusted within the specified tolerance. If the beam is not controllable.
649 ## Examples
651 >>> import pytribeam.types as tbt
652 >>> microscope = tbt.Microscope()
653 >>> microscope.connect("localhost")
654 Client connecting to [localhost:7520]...
655 Client connected to [localhost:7520]
656 >>> beam = tbt.ElectronBeam(settings=None)
657 >>> success = beam_voltage(beam, microscope, voltage_kv=15.0, voltage_tol_kv=0.5)
658 >>> print(success)
659 True"""
660 selected_beam = ut.beam_type(beam, microscope)
662 exisiting_voltage_v = selected_beam.high_voltage.value # volts
663 delta_voltage_kv = abs(
664 exisiting_voltage_v * cs.Conversions.V_TO_KV - voltage_kv
665 ) # kilovolts
666 if delta_voltage_kv > voltage_tol_kv:
667 warnings.warn(
668 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
669 )
670 beam_controllable = selected_beam.high_voltage.is_controllable
672 if not beam_controllable: 672 ↛ 673line 672 didn't jump to line 673 because the condition on line 672 was never true
673 raise ValueError(
674 f"Unable to modify beam voltage, beam is not currently controllable."
675 )
676 print("Adjusting beam voltage...")
677 selected_beam.high_voltage.value = voltage_kv * cs.Conversions.KV_TO_V
678 time.sleep(delay_s)
679 new_voltage_kv = selected_beam.high_voltage.value * cs.Conversions.V_TO_KV
680 voltage_diff_kv = abs(new_voltage_kv - voltage_kv)
681 if voltage_diff_kv > voltage_tol_kv: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true
682 raise ValueError(
683 f"""Could not correctly adjust beam voltage,
684 requested {voltage_kv} kV, current beam voltage is
685 {new_voltage_kv} kV"""
686 )
688 return True
691def beam_working_distance(
692 beam: tbt.Beam,
693 microscope: tbt.Microscope,
694 wd_mm: float,
695 delay_s: float = 0.1,
696) -> bool:
697 """
698 Sets the working distance for the selected beam, with inputs in units of millimeters.
700 This function sets the working distance (WD) for the specified beam type on the microscope.
701 It converts the WD from millimeters to meters, sets the WD, and waits for the specified delay.
702 This should be done before adjusting the horizontal field width.
704 ## Parameters
706 - `beam` (`tbt.Beam`): The beam type to configure.
707 - `microscope` (`tbt.Microscope`): The microscope object to configure.
708 - `wd_mm` (`float`): The desired working distance in millimeters.
709 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the working distance (default is 0.1).
711 ## Returns
713 - `bool`: True if the working distance is set successfully, False otherwise.
715 ## Raises
717 - `ValueError`: If the working distance cannot be adjusted correctly.
719 ## Examples
721 >>> import pytribeam.types as tbt
722 >>> microscope = tbt.Microscope()
723 >>> microscope.connect("localhost")
724 Client connecting to [localhost:7520]...
725 Client connected to [localhost:7520]
726 >>> beam = tbt.ElectronBeam(settings=None)
727 >>> success = beam_working_dist(beam, microscope, wd_mm=5.0)
728 >>> print(success)
729 True"""
730 selected_beam = ut.beam_type(beam, microscope)
731 wd_m = wd_mm * cs.Conversions.MM_TO_M
732 selected_beam.working_distance.value = wd_m
733 time.sleep(delay_s)
734 if selected_beam.working_distance.value != wd_m: 734 ↛ 735line 734 didn't jump to line 735 because the condition on line 734 was never true
735 raise ValueError(
736 f"""Could not correctly adjust working distance,
737 requested {wd_m} meters, current working distance is
738 {selected_beam.working_distance.value} meters"""
739 )
741 return True
744def detector_auto_cb(
745 microscope: tbt.Microscope,
746 beam: tbt.Beam,
747 settings: tbt.ScanArea,
748 delay_s: float = 0.1,
749) -> bool:
750 """
751 Detector auto contrast brightness. Currently only reduced scan area option is supported.
753 This function sets the scanning mode to reduced area, runs the auto contrast-brightness function,
754 and then sets the scanning mode back to full frame.
756 ## Parameters
758 - `microscope` (`tbt.Microscope`): The microscope object to configure.
759 - `beam` (`tbt.Beam`): The beam type to configure.
760 - `settings` (`tbt.ScanArea`): The scan area settings for the reduced area.
761 - `delay_s` (`float, optional`): The delay in seconds to wait after each operation (default is 0.1).
763 ## Returns
765 - `bool`: True if the auto contrast-brightness is completed successfully, False otherwise.
767 ## Raises
769 - `SystemError`: If unable to set the scan mode to reduced area or full frame.
771 ## Examples
773 >>> import pytribeam.types as tbt
774 >>> microscope = tbt.Microscope()
775 >>> microscope.connect("localhost")
776 Client connecting to [localhost:7520]...
777 Client connected to [localhost:7520]
778 >>> beam = tbt.ElectronBeam(settings=None)
779 >>> auto_cb_settings = tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8)
780 >>> success = detector_auto_cb(microscope, beam, auto_cb_settings)
781 >>> print(success)
782 True"""
783 selected_beam = ut.beam_type(beam, microscope)
784 selected_beam.scanning.mode.set_reduced_area(
785 left=settings.left,
786 top=settings.top,
787 width=settings.width,
788 height=settings.height,
789 )
790 scan_mode = selected_beam.scanning.mode.value
791 if not scan_mode == tbt.ScanMode.REDUCED_AREA.value: 791 ↛ 792line 791 didn't jump to line 792 because the condition on line 791 was never true
792 raise SystemError(
793 f"Unable to set imaging to reduced area before auto contrast-brightness. Current scan mode is {scan_mode}."
794 )
795 microscope.auto_functions.run_auto_cb()
796 time.sleep(delay_s)
797 selected_beam.scanning.mode.set_full_frame()
798 time.sleep(delay_s)
800 new_scan_mode = selected_beam.scanning.mode.value
801 if new_scan_mode != tbt.ScanMode.FULL_FRAME.value: 801 ↛ 802line 801 didn't jump to line 802 because the condition on line 801 was never true
802 raise SystemError(
803 f"Unable to set imaging back to full frame after auto contrast-brightness. Current scan mode is {new_scan_mode}."
804 )
805 return True
808def detector_brightness(
809 microscope: tbt.Microscope,
810 brightness: float,
811 delay_s: float = 0.1,
812) -> bool:
813 """
814 Sets the detector brightness with input from 0 to 1.
816 This function sets the brightness for the detector on the microscope and waits for the specified delay.
818 ## Parameters
820 - `microscope` (`tbt.Microscope`): The microscope object to configure.
821 - `brightness` (`float`): The desired brightness value (from 0 to 1).
822 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the brightness (default is 0.1).
824 ## Returns
826 - `bool`: True if the brightness is set successfully, False otherwise.
828 ## Raises
830 - `ValueError`: If the brightness cannot be adjusted correctly.
832 ## Examples
834 >>> import pytribeam.types as tbt
835 >>> microscope = tbt.Microscope()
836 >>> microscope.connect("localhost")
837 Client connecting to [localhost:7520]...
838 Client connected to [localhost:7520]
839 >>> success = detector_brightness(microscope, brightness=0.5)
840 >>> print(success)
841 True"""
842 microscope.detector.brightness.value = brightness
843 current_detector = microscope.detector.type.value
844 time.sleep(delay_s)
845 if not math.isclose(
846 microscope.detector.brightness.value,
847 brightness,
848 abs_tol=cs.Constants.contrast_brightness_tolerance,
849 ):
850 raise ValueError(
851 f"""Could not correctly adjust detector brightness,
852 requested {brightness} on {current_detector} detector,
853 current brightness is {microscope.detector.brightness.value}"""
854 )
855 return True
858def detector_contrast(
859 microscope: tbt.Microscope,
860 contrast: float,
861 delay_s: float = 0.1,
862) -> bool:
863 """
864 Sets the detector contrast with input from 0 to 1.
866 This function sets the contrast for the detector on the microscope and waits for the specified delay.
868 ## Parameters
870 - `microscope` (`tbt.Microscope`): The microscope object to configure.
871 - `contrast` (`float`): The desired contrast value (from 0 to 1).
872 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the contrast (default is 0.1).
874 ## Returns
876 - `bool`: True if the contrast is set successfully, False otherwise.
878 ## Raises
880 - `ValueError`: If the contrast cannot be adjusted correctly.
882 ## Examples
884 >>> import pytribeam.types as tbt
885 >>> microscope = tbt.Microscope()
886 >>> microscope.connect("localhost")
887 Client connecting to [localhost:7520]...
888 Client connected to [localhost:7520]
889 >>> success = detector_contrast(microscope, contrast=0.5)
890 >>> print(success)
891 True"""
892 microscope.detector.contrast.value = contrast
893 current_detector = microscope.detector.type.value
894 time.sleep(delay_s)
895 if not math.isclose(
896 microscope.detector.contrast.value,
897 contrast,
898 abs_tol=cs.Constants.contrast_brightness_tolerance,
899 ):
900 raise ValueError(
901 f"""Could not correctly adjust detector contrast,
902 requested {contrast} on {current_detector} detector,
903 current contrast is {microscope.detector.contrast.value}"""
904 )
905 return True
908def detector_cb(
909 microscope: tbt.Microscope,
910 detector_settings: tbt.Detector,
911 beam: tbt.Beam,
912) -> bool:
913 """
914 Sets detector contrast and brightness.
916 This function sets the contrast and brightness for the detector on the microscope.
917 It also runs the auto contrast-brightness function if specified in the detector settings.
918 Supports initial settings of contrast and brightness with fixed values before auto adjustment.
920 ## Parameters
922 - `microscope` (`tbt.Microscope`): The microscope object to configure.
923 - `detector_settings` (`tbt.Detector`): The detector settings, including contrast, brightness, and auto contrast-brightness settings.
924 - `beam` (`tbt.Beam`): The beam type to configure.
926 ## Returns
928 - `bool`: True if the contrast and brightness are set successfully, False otherwise.
930 ## Examples
932 >>> import pytribeam.types as tbt
933 >>> microscope = tbt.Microscope()
934 >>> microscope.connect("localhost")
935 Client connecting to [localhost:7520]...
936 Client connected to [localhost:7520]
937 >>> beam = tbt.ElectronBeam(settings=None)
938 >>> 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))
939 >>> success = detector_cb(microscope, detector_settings, beam)
940 >>> print(success)
941 True
943 >>> detector_settings = tbt.Detector(contrast=0.2, brightness=0.3, auto_cb_settings=None)
944 >>> success = detector_cb(microscope, detector_settings, beam)
945 >>> print(success)
946 True
947 >>> beam_brightness = microscope.detector.brightness.value
948 >>> print(beam_brightness)
949 0.3
950 >>> beam_contrast = microscope.detector.contrast.value
951 >>> print(beam_contrast)
952 0.2"""
953 ### cannot ensure detector is the active one, will overwrite mode settings, so following line is not used
954 # microscope.detector.type.value = detector_settings.type.value
955 contrast = detector_settings.contrast
956 brightness = detector_settings.brightness
957 if contrast is not None:
958 detector_contrast(microscope=microscope, contrast=contrast)
959 if brightness is not None:
960 detector_brightness(microscope=microscope, brightness=brightness)
962 null_scan = tbt.ScanArea(left=None, top=None, width=None, height=None)
963 if not null_scan == detector_settings.auto_cb_settings:
964 detector_auto_cb(
965 microscope=microscope,
966 settings=detector_settings.auto_cb_settings,
967 beam=beam,
968 )
969 return True
972def detector_mode(
973 microscope: tbt.Microscope,
974 detector_mode: tbt.DetectorMode,
975 delay_s: float = 0.1,
976) -> bool:
977 """
978 Sets the detector mode.
980 This function sets the mode for the detector on the microscope and waits for the specified delay.
982 ## Parameters
984 - `microscope` (`tbt.Microscope`): The microscope object to configure.
985 - `detector_mode` (`tbt.DetectorMode`): The desired detector mode.
986 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector mode (default is 0.1).
988 ## Returns
990 - `bool`: True if the detector mode is set successfully, False otherwise.
992 ## Raises
994 - `ValueError`: If the detector mode cannot be set correctly.
996 ## Examples
998 >>> import pytribeam.types as tbt
999 >>> microscope = tbt.Microscope()
1000 >>> microscope.connect("localhost")
1001 Client connecting to [localhost:7520]...
1002 Client connected to [localhost:7520]
1003 >>> mode = tbt.DetectorMode.SECONDARY_ELECTRONS
1004 >>> success = detector_mode(microscope, mode)
1005 >>> print(success)
1006 True"""
1007 microscope.detector.mode.value = detector_mode.value
1008 time.sleep(delay_s)
1009 if microscope.detector.mode.value != detector_mode.value: 1009 ↛ 1010line 1009 didn't jump to line 1010 because the condition on line 1009 was never true
1010 raise ValueError(
1011 f"""Could not correctly set detector mode,
1012 requested {detector_mode}, current mode is
1013 {microscope.detector.mode.value}"""
1014 )
1015 return True
1018def detector_type(
1019 microscope: tbt.Microscope,
1020 detector: tbt.DetectorType,
1021 delay_s: float = 0.1,
1022) -> bool:
1023 """
1024 Sets the detector type.
1026 This function sets the type for the detector on the microscope and waits for the specified delay.
1028 ## Parameters
1030 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1031 - `detector` (`tbt.DetectorType`): The desired detector type.
1032 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector type (default is 0.1).
1034 ## Returns
1036 - `bool`: True if the detector type is set successfully, False otherwise.
1038 ## Raises
1040 - `ValueError`: If the detector type cannot be set correctly.
1042 ## Examples
1044 >>> import pytribeam.types as tbt
1045 >>> microscope = tbt.Microscope()
1046 >>> microscope.connect("localhost")
1047 Client connecting to [localhost:7520]...
1048 Client connected to [localhost:7520]
1049 >>> detector = tbt.DetectorType.ETD
1050 >>> success = detector_type(microscope, detector)
1051 >>> print(success)
1052 True"""
1053 microscope.detector.type.value = detector.value
1054 time.sleep(delay_s)
1055 if microscope.detector.type.value != detector.value: 1055 ↛ 1056line 1055 didn't jump to line 1056 because the condition on line 1055 was never true
1056 raise ValueError(
1057 f"""Could not correctly set detector type,
1058 requested {detector}, current detector is
1059 {microscope.detector.type.value}"""
1060 )
1061 return True
1064def grab_custom_resolution_frame(
1065 img_settings: tbt.ImageSettings,
1066 save_path: Path,
1067) -> bool:
1068 """
1069 Method for single frame imaging used with custom resolutions.
1071 This function captures a single frame image using custom resolutions and saves it to the specified path.
1073 ## Parameters
1075 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope and scan resolution.
1076 - `save_path` (`Path`): The path to save the captured image.
1078 ## Returns
1080 - `bool`: True if the image is captured and saved successfully, False otherwise.
1082 ## Examples
1084 >>> import pytribeam.types as tbt
1085 >>> from pathlib import Path
1086 >>> microscope = tbt.Microscope()
1087 >>> microscope.connect("localhost")
1088 Client connecting to [localhost:7520]...
1089 Client connected to [localhost:7520]
1090 >>> img_settings = tbt.ImageSettings(
1091 ... microscope=microscope,
1092 ... beam=tbt.ElectronBeam(settings=None),
1093 ... detector=tbt.Detector(),
1094 ... scan=tbt.Scan(resolution=tbt.Resolution(width=1024, height=768)),
1095 ... bit_depth=tbt.ColorDepth.BITS_8,
1096 ... )
1097 >>> save_path = Path("/path/to/save/image.tif")
1098 >>> success = grab_custom_resolution_frame(img_settings, save_path)
1099 >>> print(success)
1100 True"""
1101 microscope = img_settings.microscope
1102 resolution = img_settings.scan.resolution
1103 microscope.imaging.grab_frame_to_disk(
1104 save_path.absolute().as_posix(),
1105 file_format=tbt.ImageFileFormat.TIFF.value,
1106 settings=tbt.GrabFrameSettings(
1107 resolution=f"{resolution.width}x{resolution.height}"
1108 ),
1109 )
1110 return True
1113def grab_preset_resolution_frame(
1114 img_settings: tbt.ImageSettings,
1115) -> tbt.AdornedImage:
1116 """
1117 Method for single frame imaging used with preset resolutions.
1119 This function captures a single frame image using preset resolutions.
1121 ## Parameters
1123 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, detector, and scan resolution.
1125 ## Returns
1127 - `tbt.AdornedImage`: The captured image.
1129 ## Examples
1131 >>> import pytribeam.types as tbt
1132 >>> microscope = tbt.Microscope()
1133 >>> microscope.connect("localhost")
1134 Client connecting to [localhost:7520]...
1135 Client connected to [localhost:7520]
1136 >>> img_settings = tbt.ImageSettings(
1137 ... microscope=microscope,
1138 ... beam=tbt.ElectronBeam(settings=None),
1139 ... detector=tbt.Detector(),
1140 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1141 ... bit_depth=tbt.ColorDepth.BITS_8,
1142 ... )
1143 >>> image = grab_preset_resolution_frame(img_settings)
1144 >>> print(image)
1145 AdornedImage(width=768, height=512, bit_depth=8)"""
1146 beam = img_settings.beam
1147 microscope = img_settings.microscope
1148 beam_scan_resolution(
1149 beam=beam,
1150 microscope=microscope,
1151 resolution=img_settings.scan.resolution,
1152 )
1153 return microscope.imaging.grab_frame(
1154 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth)
1155 )
1158def imaging_detector(img_settings: tbt.ImageSettings) -> bool:
1159 """
1160 Prepares the detector and inserts it if applicable.
1162 This function sets the detector type, inserts the detector if necessary, sets the detector mode,
1163 and adjusts the contrast and brightness settings. It is important to set detector mode settings
1164 right before contrast and brightness as any subsequent calls to a detector type can overwrite the mode.
1166 ## Parameters
1168 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and detector settings.
1170 ## Returns
1172 - `bool`: True if the detector is prepared successfully, False otherwise.
1174 ## Examples
1176 >>> import pytribeam.types as tbt
1177 >>> microscope = tbt.Microscope()
1178 >>> microscope.connect("localhost")
1179 Client connecting to [localhost:7520]...
1180 Client connected to [localhost:7520]
1181 >>> img_settings = tbt.ImageSettings(
1182 ... microscope=microscope,
1183 ... beam=tbt.ElectronBeam(settings=None),
1184 ... detector=tbt.Detector(
1185 ... type=tbt.DetectorType.ETD,
1186 ... mode=tbt.DetectorMode.SECONDARY_ELECTRONS,
1187 ... brightness=0.4,
1188 ... contrast=0.2,
1189 ... ),
1190 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1191 ... bit_depth=tbt.ColorDepth.BITS_8,
1192 ... )
1193 >>> success = imaging_detector(img_settings)
1194 >>> print(success)
1195 True"""
1196 microscope = img_settings.microscope
1197 detector = img_settings.detector.type
1198 detector_type(
1199 microscope=microscope,
1200 detector=detector,
1201 )
1202 detector_state = devices.detector_state(
1203 microscope=microscope,
1204 detector=detector,
1205 )
1206 if detector_state is not tbt.RetractableDeviceState.STATIONARY: 1206 ↛ 1207line 1206 didn't jump to line 1207 because the condition on line 1206 was never true
1207 devices.insert_detector(
1208 microscope=microscope,
1209 detector=detector,
1210 )
1211 detector_mode(
1212 microscope=microscope,
1213 detector_mode=img_settings.detector.mode,
1214 )
1215 detector_cb(
1216 microscope=microscope,
1217 detector_settings=img_settings.detector,
1218 beam=img_settings.beam,
1219 )
1220 return True
1223def imaging_device(
1224 microscope: tbt.Microscope,
1225 beam: tbt.Beam,
1226) -> bool:
1227 """
1228 Prepares the imaging beam, viewing quad, and the beam voltage and current.
1230 This function sets the beam device, ensures the beam is ready, sets the beam voltage and current,
1231 and applies angular correction if the beam type is electron.
1233 ## Parameters
1235 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1236 - `beam` (`tbt.Beam`): The beam type to configure, including its settings.
1238 ## Returns
1240 - `bool`: True if the imaging device is prepared successfully, False otherwise."""
1241 set_beam_device(microscope=microscope, device=beam.device)
1242 beam_ready(beam=beam, microscope=microscope)
1243 beam_voltage(
1244 beam=beam,
1245 microscope=microscope,
1246 voltage_kv=beam.settings.voltage_kv,
1247 voltage_tol_kv=beam.settings.voltage_tol_kv,
1248 )
1249 beam_current(
1250 beam=beam,
1251 microscope=microscope,
1252 current_na=beam.settings.current_na,
1253 current_tol_na=beam.settings.current_tol_na,
1254 )
1255 if beam.type == tbt.BeamType.ELECTRON: 1255 ↛ 1261line 1255 didn't jump to line 1261 because the condition on line 1255 was always true
1256 beam_angular_correction(
1257 microscope=microscope,
1258 dynamic_focus=beam.settings.dynamic_focus,
1259 tilt_correction=beam.settings.tilt_correction,
1260 )
1261 return True
1264def imaging_scan(img_settings: tbt.ImageSettings) -> bool:
1265 """
1266 Sets all scan settings except for the resolution.
1268 This function configures the scan settings for the specified image settings, including
1269 setting the scan mode to full frame, scan rotation, working distance, horizontal field width,
1270 and dwell time.
1272 ## Parameters
1274 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and scan settings.
1276 ## Returns
1278 - `bool`: True if the scan settings are configured successfully, False otherwise."""
1279 microscope = img_settings.microscope
1280 beam = img_settings.beam
1281 beam_scan_full_frame(
1282 beam=beam,
1283 microscope=microscope,
1284 )
1285 beam_scan_rotation(
1286 beam=beam, microscope=microscope, rotation_deg=img_settings.scan.rotation_deg
1287 )
1288 beam_working_distance(
1289 beam=beam,
1290 microscope=microscope,
1291 wd_mm=img_settings.beam.settings.working_dist_mm,
1292 )
1293 beam_hfw(beam=beam, microscope=microscope, hfw_mm=img_settings.beam.settings.hfw_mm)
1294 beam_dwell_time(
1295 beam=beam, microscope=microscope, dwell_us=img_settings.scan.dwell_time_us
1296 )
1297 return True
1300def prepare_imaging(img_settings: tbt.ImageSettings) -> bool:
1301 """
1302 Prepares various imaging settings.
1304 This function prepares the imaging device, scan settings, and detector settings
1305 based on the specified image settings.
1307 ## Parameters
1309 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1311 ## Returns
1313 - `bool`: True if the imaging settings are prepared successfully, False otherwise."""
1314 imaging_device(microscope=img_settings.microscope, beam=img_settings.beam)
1315 imaging_scan(img_settings=img_settings)
1316 imaging_detector(img_settings=img_settings)
1317 return True
1320def set_view(
1321 microscope: tbt.Microscope,
1322 quad: tbt.ViewQuad,
1323) -> bool:
1324 """
1325 Sets the active view to the specified quad.
1327 This function sets the active imaging view to the specified quad on the microscope.
1329 ## Parameters
1331 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1332 - `quad` (`tbt.ViewQuad`): The imaging view to select: - 1 is upper left - 2 is upper right - 3 is lower left - 4 is lower right
1334 ## Returns
1336 - `bool`: True if the active view is set successfully, False otherwise."""
1337 microscope.imaging.set_active_view(quad.value)
1340def set_beam_device(
1341 microscope: tbt.Microscope,
1342 device: tbt.Device,
1343 delay_s: float = 0.1,
1344) -> bool:
1345 """
1346 Sets the active imaging device.
1348 This function sets the active imaging device on the microscope and waits for the specified delay.
1350 ## Parameters
1352 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1353 - `device` (`tbt.Device`): The desired imaging device.
1354 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the device (default is 0.1).
1356 ## Returns
1358 - `bool`: True if the active device is set successfully, False otherwise.
1360 ## Raises
1362 - `ValueError`: If the active device cannot be set correctly."""
1363 microscope.imaging.set_active_device(device)
1364 time.sleep(delay_s)
1365 curr_device = tbt.Device(microscope.imaging.get_active_device())
1366 if curr_device != device.value: 1366 ↛ 1367line 1366 didn't jump to line 1367 because the condition on line 1366 was never true
1367 raise ValueError(
1368 f"""Could not set active device,
1369 requested device {device.value} but current device is {curr_device}.
1370 Device list:
1371 {tbt.Device.ELECTRON_BEAM.value}: Electron Beam
1372 {tbt.Device.ION_BEAM.value}: Ion Beam
1373 {tbt.Device.CCD_CAMERA.value}: CCD Camera
1374 {tbt.Device.IR_CAMERA.value}: IR Camera
1375 {tbt.Device.NAV_CAM.value}: Nav Cam"""
1376 )
1377 return True
1380#################
1383def collect_single_image(
1384 save_path: Path,
1385 img_settings: tbt.ImageSettings,
1386) -> bool:
1387 """
1388 Collects a single frame image with defined image settings.
1390 This function prepares the imaging settings, sets the view, and captures a single frame image.
1391 It saves the image to the specified path. If a non-preset resolution is requested, the image
1392 will be saved at 8-bit color depth.
1394 ## Parameters
1396 - `save_path` (`Path`): The path to save the captured image.
1397 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1399 ## Returns
1401 - `bool`: True if the image is captured and saved successfully, False otherwise."""
1402 beam = img_settings.beam
1403 microscope = img_settings.microscope
1404 set_view(microscope=microscope, quad=beam.default_view)
1405 prepare_imaging(img_settings=img_settings)
1407 resolution = img_settings.scan.resolution
1408 if isinstance(resolution, tbt.PresetResolution):
1409 img = grab_preset_resolution_frame(img_settings=img_settings)
1410 img.save(str(save_path))
1411 return True
1412 warnings.warn(
1413 f'Warning, non-preset resolution of "{img_settings.scan.resolution}" requested. Image will automatically be saved at 8-bit color depth'
1414 )
1415 if img_settings.bit_depth != tbt.ColorDepth.BITS_8: 1415 ↛ 1416line 1415 didn't jump to line 1416 because the condition on line 1415 was never true
1416 warnings.warn(
1417 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."
1418 )
1419 grab_custom_resolution_frame(
1420 img_settings=img_settings,
1421 save_path=save_path,
1422 )
1423 return True
1426def collect_multiple_images(
1427 multiple_img_settings: List[tbt.ImageSettings], num_frames: int
1428) -> List[tbt.AdornedImage]:
1429 """
1430 Sets up scanning for multiple frames.
1432 This function is best used for collecting multiple segments on a single detector simultaneously.
1433 It is limited to preset resolutions only.
1435 ## Parameters
1437 - `multiple_img_settings` (`List[tbt.ImageSettings]`): The list of image settings for each frame, including the microscope, beam, scan, and detector settings.
1438 - `num_frames` (`int`): The number of frames to collect.
1440 ## Returns
1442 - `List[tbt.AdornedImage]`: The list of captured images.
1444 ## Raises
1446 - `ValueError`: If a non-preset resolution is requested for simultaneous multiple frame imaging.
1448 ## Notes
1450 This method has not yet been tested."""
1451 # TODO test this
1453 # Ensure that an insertable detector is done first so that inserting the detector doesn't interfere with other detectors
1454 # Will need to make sure that only one insertable detector at a time
1455 insertable_detector = [
1456 devices.detector_state(
1457 microscope=_set.microscope,
1458 detector=_set.detector,
1459 )
1460 is not tbt.RetractableDeviceState.STATIONARY
1461 for _set in multiple_img_settings
1462 ]
1463 if sum(insertable_detector) > 1:
1464 raise NotImplementedError(
1465 "Collecting multiple images with more than one insertable detector is not supported."
1466 )
1467 elif sum(insertable_detector) == 1:
1468 start = multiple_img_settings[insertable_detector.index(True)]
1469 multiple_img_settings = [start] + multiple_img_settings
1471 warnings.warn("Method not yet tested")
1472 views = []
1473 for quad in range(1, num_frames + 1):
1474 img_settings = multiple_img_settings[quad - 1]
1475 resolution = img_settings.scan.resolution
1476 if not isinstance(resolution, tbt.PresetResolution): 1476 ↛ 1481line 1476 didn't jump to line 1481 because the condition on line 1476 was always true
1477 raise ValueError(
1478 f'Only preset resolutions allowed for simultaneous multiple frame imaging, but resolution of "{resolution.width}x{resolution.height}" was requested.'
1479 )
1481 microscope = img_settings.microscope
1482 beam = img_settings.beam
1483 views.append(quad)
1484 set_view(microscope=microscope, quad=img_settings.beam.default_view)
1485 prepare_imaging(microscope=microscope, beam=beam, img_settings=img_settings)
1486 frames = microscope.imaging.grab_multiple_frames(
1487 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth, views=views)
1488 )
1489 return frames
1492# TODO
1493# def image_method(dict):
1494# """determine imaging method and return associated function"""
1495# pass
1497# TODO
1498# def collect_tiling_image():
1499# for loop
1500# collect_standard_image
1501# #stage move
1504# TODO add more complex imaging behavior, determine method in this function
1505def image_operation(
1506 step: tbt.Step,
1507 image_settings: tbt.ImageSettings,
1508 general_settings: tbt.GeneralSettings,
1509 slice_number: int,
1510) -> bool:
1511 """
1512 Performs an image operation based on the specified settings.
1514 This function collects an image, saves it to the specified directory, and turns off tilt correction and dynamic focus.
1516 ## Parameters
1518 - `step` (`tbt.Step`): The step information, including the name of the step.
1519 - `image_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1520 - `general_settings` (`tbt.GeneralSettings`): The general settings, including the experimental directory.
1521 - `slice_number` (`int`): The slice number for naming the saved image file.
1523 ## Returns
1525 - `bool`: True if the image operation is performed successfully, False otherwise."""
1526 print("\tCollecting image")
1527 # create folder in same directory as experimental directory
1528 image_directory = Path(general_settings.exp_dir).joinpath(step.name)
1529 image_directory.mkdir(parents=True, exist_ok=True)
1531 # TODO
1532 # determine_image_method()
1533 # collect_multiple_images()
1535 # single image process:
1536 save_path = image_directory.joinpath(f"{slice_number:04}.tif")
1537 collect_single_image(save_path=save_path, img_settings=image_settings)
1538 print(f"\tImage saved to {save_path}")
1540 # turn off tilt correction and dynamic focus
1541 beam_angular_correction(
1542 microscope=image_settings.microscope,
1543 dynamic_focus=False,
1544 tilt_correction=False,
1545 )
1547 return True