Coverage for src/pytribeam/fib.py: 0%
111 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
1#!/usr/bin/python3
2"""
3FIB Module
4==========
6This module contains functions for performing various operations related to Focused Ion Beam (FIB) milling, including preparing the microscope for milling, creating patterns, and performing milling operations.
8Functions
9---------
10application_files(microscope: tbt.Microscope) -> List[str]
11 Get the list of application files from the current microscope.
13shutter_control(microscope: tbt.Microscope) -> None
14 Ensure auto control is set on the e-beam shutter. Manual control is not currently offered.
16prepare_milling(microscope: tbt.Microscope, application: str, patterning_device: tbt.Device = tbt.Device.ION_BEAM) -> bool
17 Clear old patterns, assign patterning to ion beam by default, and load the application.
19create_pattern(geometry, microscope: tbt.Microscope, **kwargs: dict) -> bool
20 Create a pattern on the microscope based on the provided geometry.
22create_pattern(geometry: tbt.FIBRectanglePattern, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.RectanglePattern
23 Create a rectangle pattern on the microscope.
25create_pattern(geometry: tbt.FIBRegularCrossSection, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.RegularCrossSectionPattern
26 Create a regular cross-section pattern on the microscope.
28create_pattern(geometry: tbt.FIBCleaningCrossSection, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.CleaningCrossSectionPattern
29 Create a cleaning cross-section pattern on the microscope.
31create_pattern(geometry: tbt.FIBStreamPattern, microscope: tbt.Microscope, **kwargs: dict) -> tbt.StreamPattern
32 Create a stream pattern on the microscope.
34image_processing(geometry: tbt.FIBStreamPattern, input_image_path: Path) -> bool
35 Perform image processing for FIB stream pattern.
37mill_operation(step: tbt.Step, fib_settings: tbt.FIBSettings, general_settings: tbt.GeneralSettings, slice_number: int) -> bool
38 Perform a milling operation based on the provided step and settings.
39"""
41# Default python modules
42from functools import singledispatch
43from pathlib import Path
44import warnings
45from typing import List
46from functools import singledispatch
47import subprocess
49# Autoscript included modules
50from PIL import Image as pil_img
51import cv2
52import numpy as np
54# 3rd party module
56# Local scripts
57import pytribeam.constants as cs
58from pytribeam.constants import Conversions
59import pytribeam.types as tbt
60import pytribeam.image as img
63def application_files(microscope: tbt.Microscope) -> List[str]:
64 """
65 Get the list of application files from the current microscope.
67 This function retrieves the list of application files available on the current microscope, removes any "None" entries, and sorts the list.
69 Parameters
70 ----------
71 microscope : tbt.Microscope
72 The microscope object from which to retrieve the application files.
74 Returns
75 -------
76 List[str]
77 A sorted list of application files available on the microscope.
78 """
79 apps = microscope.patterning.list_all_application_files()
81 # Remove "None" entry
82 while "None" in apps:
83 apps.remove("None")
84 apps.sort(key=str.casefold)
86 return apps
89def shutter_control(microscope: tbt.Microscope) -> None:
90 """
91 Ensure auto control is set on the e-beam shutter. Manual control is not currently offered.
93 This function checks if the e-beam protective shutter is installed and sets its mode to automatic if it is not already set. If the shutter cannot be set to automatic mode, a SystemError is raised.
95 Parameters
96 ----------
97 microscope : tbt.Microscope
98 The microscope object for which to control the e-beam shutter.
100 Raises
101 ------
102 SystemError
103 If the e-beam shutter is installed but cannot be set to automatic mode.
105 Warnings
106 --------
107 UserWarning
108 If the e-beam shutter is not installed or if it is set to automatic mode.
109 """
110 shutter = microscope.beams.electron_beam.protective_shutter
111 if not shutter.is_installed:
112 warnings.warn("Protective E-beam shutter not installed on this system.")
113 return
114 status = shutter.mode.value
115 if status != tbt.ProtectiveShutterMode.AUTOMATIC:
116 shutter.mode.value = tbt.ProtectiveShutterMode.AUTOMATIC
117 new_status = shutter.mode.value
118 if new_status != tbt.ProtectiveShutterMode.AUTOMATIC:
119 raise SystemError(
120 "E-beam shutter for FIB milling is installed but cannot set control to 'Automatic' mode."
121 )
122 warnings.warn(
123 "E-beam shutter for FIB milling operations is in auto-mode, which may not insert at certain tilt angles and stage positions. Manual control not available."
124 )
125 return
128def prepare_milling(
129 microscope: tbt.Microscope,
130 application: str,
131 patterning_device: tbt.Device = tbt.Device.ION_BEAM,
132) -> bool:
133 # TODO validation and error checking from TFS
134 # TODO support e-beam patterning via the mill_beam settings
135 """
136 Clear old patterns, assign patterning to ion beam by default, and load the application.
138 This function clears old patterns, assigns the patterning device to the specified beam (ion beam by default), and loads the specified application. It validates the patterning device and application file.
140 Parameters
141 ----------
142 microscope : tbt.Microscope
143 The microscope object for which to prepare milling.
144 application : str
145 The name of the application file to load.
146 patterning_device : tbt.Device, optional
147 The device to use for patterning (default is tbt.Device.ION_BEAM).
149 Returns
150 -------
151 bool
152 True if the preparation is successful.
154 Raises
155 ------
156 ValueError
157 If the patterning device is invalid or if the application file is not found on the system.
158 """
159 valid_devices = [tbt.Device.ELECTRON_BEAM, tbt.Device.ION_BEAM]
160 if patterning_device not in valid_devices:
161 raise ValueError(
162 f"Invalid patterning device of '{patterning_device}' requested, only '{[i for i in valid_devices]}' are valid."
163 )
164 microscope.patterning.clear_patterns()
165 microscope.patterning.set_default_beam_type(beam_index=patterning_device.value)
166 if application not in microscope.patterning.list_all_application_files():
167 raise ValueError(
168 f"Invalid application file on this system, there is no patterning application with name: '{application}'."
169 )
170 else:
171 microscope.patterning.set_default_application_file(application)
173 return True
176@singledispatch
177def create_pattern(
178 geometry,
179 microscope: tbt.Microscope,
180 **kwargs: dict,
181) -> bool: # FIBRectanglePattern
182 """
183 Create a pattern on the microscope based on the provided geometry.
185 This function creates a pattern on the microscope based on the provided geometry. It is a generic function that raises a NotImplementedError if no handler is available for the provided geometry type.
187 Parameters
188 ----------
189 geometry : Any
190 The geometry of the pattern to create.
191 microscope : tbt.Microscope
192 The microscope object on which to create the pattern.
193 kwargs : dict
194 Additional keyword arguments.
196 Returns
197 -------
198 bool
199 True if the pattern is created successfully.
201 Raises
202 ------
203 NotImplementedError
204 If no handler is available for the provided geometry type.
205 """
206 _ = geometry
207 __ = microscope
208 ___ = kwargs
209 raise NotImplementedError(f"No handler for type {type(geometry)}")
212@create_pattern.register
213def _(
214 geometry: tbt.FIBRectanglePattern,
215 microscope: tbt.Microscope,
216 **kwargs: dict,
217) -> tbt.as_dynamics.RectanglePattern:
218 """
219 Create a rectangle pattern on the microscope.
221 Parameters
222 ----------
223 geometry : tbt.FIBRectanglePattern
224 The geometry of the rectangle pattern to create.
225 microscope : tbt.Microscope
226 The microscope object on which to create the pattern.
227 kwargs : dict
228 Additional keyword arguments.
230 Returns
231 -------
232 tbt.as_dynamics.RectanglePattern
233 The created rectangle pattern.
234 """
235 pattern = microscope.patterning.create_rectangle(
236 center_x=geometry.center_um.x * Conversions.UM_TO_M,
237 center_y=geometry.center_um.y * Conversions.UM_TO_M,
238 width=geometry.width_um * Conversions.UM_TO_M,
239 height=geometry.height_um * Conversions.UM_TO_M,
240 depth=geometry.depth_um * Conversions.UM_TO_M,
241 )
242 pattern.scan_direction = geometry.scan_direction.value
243 pattern.scan_type = geometry.scan_type.value
245 return pattern
248@create_pattern.register
249def _(
250 geometry: tbt.FIBRegularCrossSection,
251 microscope: tbt.Microscope,
252 **kwargs: dict,
253) -> tbt.as_dynamics.RegularCrossSectionPattern:
254 """
255 Create a regular cross-section pattern on the microscope.
257 Parameters
258 ----------
259 geometry : tbt.FIBRegularCrossSection
260 The geometry of the regular cross-section pattern to create.
261 microscope : tbt.Microscope
262 The microscope object on which to create the pattern.
263 kwargs : dict
264 Additional keyword arguments.
266 Returns
267 -------
268 tbt.as_dynamics.RegularCrossSectionPattern
269 The created regular cross-section pattern.
270 """
271 pattern = microscope.patterning.create_regular_cross_section(
272 center_x=geometry.center_um.x * Conversions.UM_TO_M,
273 center_y=geometry.center_um.y * Conversions.UM_TO_M,
274 width=geometry.width_um * Conversions.UM_TO_M,
275 height=geometry.height_um * Conversions.UM_TO_M,
276 depth=geometry.depth_um * Conversions.UM_TO_M,
277 )
278 pattern.scan_direction = geometry.scan_direction.value
279 pattern.scan_type = geometry.scan_type.value
281 return pattern
284@create_pattern.register
285def _(
286 geometry: tbt.FIBCleaningCrossSection,
287 microscope: tbt.Microscope,
288 **kwargs: dict,
289) -> tbt.as_dynamics.CleaningCrossSectionPattern:
290 """
291 Create a cleaning cross-section pattern on the microscope.
293 Parameters
294 ----------
295 geometry : tbt.FIBCleaningCrossSection
296 The geometry of the cleaning cross-section pattern to create.
297 microscope : tbt.Microscope
298 The microscope object on which to create the pattern.
299 kwargs : dict
300 Additional keyword arguments.
302 Returns
303 -------
304 tbt.as_dynamics.CleaningCrossSectionPattern
305 The created cleaning cross-section pattern.
306 """
307 pattern = microscope.patterning.create_cleaning_cross_section(
308 center_x=geometry.center_um.x * Conversions.UM_TO_M,
309 center_y=geometry.center_um.y * Conversions.UM_TO_M,
310 width=geometry.width_um * Conversions.UM_TO_M,
311 height=geometry.height_um * Conversions.UM_TO_M,
312 depth=geometry.depth_um * Conversions.UM_TO_M,
313 )
314 pattern.scan_direction = geometry.scan_direction.value
315 pattern.scan_type = geometry.scan_type.value
317 return pattern
320@create_pattern.register
321def _(
322 geometry: tbt.FIBStreamPattern,
323 microscope: tbt.Microscope,
324 **kwargs: dict,
325) -> tbt.StreamPattern:
326 """
327 Create a stream pattern on the microscope.
329 Parameters
330 ----------
331 geometry : tbt.FIBStreamPattern
332 The geometry of the stream pattern to create.
333 microscope : tbt.Microscope
334 The microscope object on which to create the pattern.
335 kwargs : dict
336 Additional keyword arguments.
338 Returns
339 -------
340 tbt.StreamPattern
341 The created stream pattern.
342 """
344 # run image_processing and check that mask file is created
345 input_image_path = kwargs["kwargs"]["input_image_path"]
346 image_processing(
347 geometry=geometry,
348 input_image_path=input_image_path,
349 )
351 # get mask
352 mask_path = geometry.mask_file
353 with pil_img.open(mask_path) as mask_img:
354 width, height = mask_img.size
355 mask = np.asarray(mask_img).astype(int)
357 stream_def = tbt.StreamPatternDefinition()
358 stream_def.bit_depth = tbt.StreamDepth.BITS_16 # only supported bit depth now
359 dwell_time = geometry.dwell_us * Conversions.US_TO_S
361 scale_factor = cs.Constants.stream_pattern_scale / width
362 point_img = cv2.resize(
363 mask,
364 dsize=(int(width * scale_factor), int(height * scale_factor)),
365 interpolation=cv2.INTER_NEAREST,
366 )
367 idx = np.where(point_img == 1)
368 num_points = (
369 len(idx[0]) + 2
370 ) # top right and bottom left corners each have a point to make sure pattern is centered
372 # stream pattern is defined by 4 values for each point
373 # [x, y, dwell_time, flags]
374 # flags = 1 --> blank beam
375 # flags = 0 --> use beam
376 stream_def.points = np.zeros(shape=(num_points, 4), dtype=object)
377 stream_def.points[0] = [
378 1, # x (top left)
379 cs.Constants.stream_pattern_y_shift, # y (top left)
380 dwell_time, # dwell
381 1, # flag (1 means blank the beam)
382 ]
383 flags = 0
384 for i in range(1, num_points - 1): # start at first point
385 x = idx[1][i - 1] * 16 + 1
386 y = (
387 idx[0][i - 1] * 16 + cs.Constants.stream_pattern_y_shift
388 ) # + (0.17 * (2**stream_def.bit_depth))
389 stream_def.points[i] = [x, y, dwell_time, flags]
390 stream_def.points[-1] = [
391 2**stream_def.bit_depth, # x (bottom right)
392 2**stream_def.bit_depth
393 - cs.Constants.stream_pattern_y_shift, # y (bottom right)
394 dwell_time, # dwell
395 1, # flag
396 ]
398 stream_def.repeat_count = geometry.repeats
400 stream_pattern = microscope.patterning.create_stream(
401 center_x=0.0,
402 center_y=0.0,
403 stream_pattern_definition=stream_def,
404 )
406 return stream_pattern
409def image_processing(
410 geometry: tbt.FIBStreamPattern,
411 input_image_path: Path,
412) -> bool:
413 """
414 Perform image processing for FIB stream pattern.
416 This function runs an image processing script specified by the `recipe_file` in the `geometry` object, using the input image path and outputting the mask file.
418 Parameters
419 ----------
420 geometry : tbt.FIBStreamPattern
421 The geometry of the FIB stream pattern, including the `recipe_file` and `mask_file`.
422 input_image_path : Path
423 The path to the input image.
425 Returns
426 -------
427 bool
428 True if the image processing is successful.
430 Raises
431 ------
432 ValueError
433 If the subprocess call for the script does not execute correctly or if the mask file is not created.
434 """
435 output = subprocess.run(
436 [
437 "python",
438 (geometry.recipe_file).as_posix(), # recipe_file
439 input_image_path.as_posix(), # input path,
440 (geometry.mask_file).as_posix(), # outputpath,
441 ],
442 capture_output=True,
443 )
444 if output.returncode != 0:
445 raise ValueError(
446 f"Subprocess call for script {geometry.recipe_file} using executable 'python' did not execute correctly."
447 )
448 # check for mask file
449 if not geometry.mask_file.exists():
450 raise ValueError(
451 f"Mask file at location {geometry.mask_file} should have been created by image processing recipe but does not exist. Please check your recipe_file script."
452 )
453 # TODO
454 # save masks
456 return True
459# TODO add more complex patterning behavior
460def mill_operation(
461 step: tbt.Step,
462 fib_settings: tbt.FIBSettings,
463 general_settings: tbt.GeneralSettings,
464 slice_number: int,
465) -> bool:
466 """
467 Perform a milling operation based on the provided step and settings.
469 This function performs a milling operation using the specified step, FIB settings, general settings, and slice number.
471 Parameters
472 ----------
473 step : tbt.Step
474 The step object containing the operation settings.
475 fib_settings : tbt.FIBSettings
476 The FIB settings object containing the microscope and pattern settings.
477 general_settings : tbt.GeneralSettings
478 The general settings object.
479 slice_number : int
480 The slice number for the operation.
482 Returns
483 -------
484 bool
485 True if the milling operation is successful.
487 Raises
488 ------
489 ValueError
490 If the ion image for selected area milling is not found.
491 """
492 microscope = fib_settings.microscope
494 shutter_control(microscope=microscope)
495 # prepare beam
496 img.imaging_device(microscope=microscope, beam=fib_settings.mill_beam)
497 # set milling application and device
499 prepare_milling(
500 microscope=microscope,
501 application=fib_settings.pattern.application,
502 )
504 # get expected path of the fib image
505 if fib_settings.pattern.type != tbt.FIBPatternType.SELECTED_AREA:
506 input_image_path = None
507 else:
508 input_image_path = Path.join(
509 general_settings.exp_dir,
510 step.name,
511 f"{slice_number:04}.tif",
512 )
513 if not input_image_path.exists():
514 raise ValueError(
515 f"Ion image for selected area milling was not found at '{input_image_path}'."
516 )
518 # make the pattern
519 pattern = create_pattern(
520 fib_settings.pattern.geometry,
521 microscope=microscope,
522 kwargs={"input_image_path": input_image_path},
523 )
525 microscope.patterning.run()
527 return True