Skip to content

API Reference

Acquire

apply_clahe(image, which_package='skimage', clip_limit_cv2=15, tile_grid_size=8, clip_limit_skimage=0.02, kernel_size=None)

Applies Contrast Limited Adaptive Histogram Equalisation correction to the input FibsemImage. image is divided into small blocks called "tiles" (tileSize is 8x8 by default in OpenCV). Then each of these blocks are histogram equalized as usual. So in a small area, histogram would confine to a small region (unless there is noise). If noise is there, it will be amplified. To avoid this, contrast limiting is applied. If any histogram bin is above the specified contrast limit (by default 40 in OpenCV), those pixels are clipped and distributed uniformly to other bins before applying histogram equalization. After equalization, to remove artifacts in tile borders, bilinear interpolation is applied.

In OpenCV tileGridSize (tile_grid_size) is by default 8x8, clipLimit (clip_limit_cv2) is by default 40

In skimage kernel_size (int or array_like), is optional. It defines the shape of contextual regions used in the algorithm. By default, kernel_size is 1/8 of image height by 1/8 of its width. clip_limit float, optional. Clipping limit, normalized between 0 and 1 (higher values give more contrast).

Parameters:

Name Type Description Default
image FibsemImage

The input FibsemImage to apply gamma correction to.

required
clip_limit_cv2 float

used if which_package=="OpenCV". Defaults to 15. (by default 40 in OpenCV)

15
tile_grid_size int

used if which_package=='OpenCV'. Defaults to 8x8 pixels.

8
clip_limit_skimage float

used if which_package=="skimage". Defaults to 0.01. Clipping limit, normalised between 0 and 1 (higher values give more contrast).

0.02
tile_grid_size int

used if which_package=='skimage'. if None, defaults to kernel_size is 1/8 of image height by 1/8 of its width.

8

Returns:

Type Description
FibsemImage

A new FibsemImage object containing the clahe-enhanced image data, with the same metadata

FibsemImage

as the input image.

Notes
  • This function applies gamma correction to the input image using either the cv2.createCLAHE or skimage.exposure.equalize_adapthist function
  • The FibsemImage object in the returned list contains the clahe-enhanced image data as a numpy array, as well as other image metadata.
Source code in fibsem/acquire.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def apply_clahe(
    image: FibsemImage,
    which_package: str = "skimage",
    clip_limit_cv2: float = 15,
    tile_grid_size: int = 8,
    clip_limit_skimage: float = 0.02,
    kernel_size = None
) -> FibsemImage:
    """
    Applies Contrast Limited Adaptive Histogram Equalisation correction to the input `FibsemImage`.
    image is divided into small blocks called "tiles" (tileSize is 8x8 by default in OpenCV). Then each of these
    blocks are histogram equalized as usual. So in a small area, histogram would confine to a small region
    (unless there is noise). If noise is there, it will be amplified. To avoid this, contrast limiting is applied.
    If any histogram bin is above the specified contrast limit (by default 40 in OpenCV), those pixels are clipped and
    distributed uniformly to other bins before applying histogram equalization. After equalization, to remove artifacts
    in tile borders, bilinear interpolation is applied.

    In OpenCV tileGridSize (tile_grid_size) is by default 8x8, clipLimit (clip_limit_cv2) is by default 40

    In skimage kernel_size (int or array_like), is optional. It defines the shape of contextual regions used in the
    algorithm. By default, kernel_size is 1/8 of image height by 1/8 of its width.
    clip_limit float, optional. Clipping limit, normalized between 0 and 1 (higher values give more contrast).

    Args:
        image (FibsemImage): The input `FibsemImage` to apply gamma correction to.

        type (str) Either "skimage" or "OpenCV" to apply the filter from the corresponding library

        clip_limit_cv2 (float): used if which_package=="OpenCV". Defaults to 15. (by default 40 in OpenCV)
        tile_grid_size (int): used if which_package=='OpenCV'. Defaults to 8x8 pixels.

        clip_limit_skimage (float): used if which_package=="skimage". Defaults to 0.01. Clipping limit, normalised between 0 and 1 (higher values give more contrast).
        tile_grid_size (int): used if which_package=='skimage'. if None, defaults to kernel_size is 1/8 of image height by 1/8 of its width.

    Returns:
        A new `FibsemImage` object containing the clahe-enhanced image data, with the same metadata
        as the input image.

    Notes:
        - This function applies gamma correction to the input image using either the `cv2.createCLAHE` or `skimage.exposure.equalize_adapthist` function
        - The `FibsemImage` object in the returned list contains the clahe-enhanced image data as a
          numpy array, as well as other image metadata.
    """

    """
        OpenCV requires 8-bit images for CLAHE, skimage requires either 8-bit images or arrays with values between [0,1]
        Here, we convert the raw data into an 8-bit image to proceed
    """

    temp = image.data
    temp = temp / temp.max()
    temp = (temp * 2**8).astype(np.uint8)

    if which_package=='OpenCV':
        import cv2
        tile_grid_size = int(tile_grid_size)
        clahe = cv2.createCLAHE(clipLimit=clip_limit_cv2,
                                tileGridSize=(tile_grid_size,tile_grid_size))
        image_data = clahe.apply(temp)

    else: # default filter
        # nbin = 256 default, for 8-bit images
        image_data = exposure.equalize_adapthist(temp,
                                                 kernel_size=kernel_size,
                                                 clip_limit=clip_limit_skimage, nbins=256)
        import skimage
        image_data = skimage.img_as_ubyte(image_data)

    return FibsemImage(data=image_data, metadata=image.metadata)

auto_gamma(image, min_gamma=0.15, max_gamma=1.8, scale_factor=0.01, gamma_threshold=45, method='autogamma')

Applies automatic gamma correction to the input FibsemImage.

Parameters:

Name Type Description Default
image FibsemImage

The input FibsemImage to apply gamma correction to.

required
min_gamma float

The minimum gamma value allowed in the correction. Defaults to 0.15.

0.15
max_gamma float

The maximum gamma value allowed in the correction. Defaults to 1.8.

1.8
scale_factor float

A scaling factor to adjust the gamma correction range based on the image brightness. Defaults to 0.01.

0.01
gamma_threshold int

The maximum threshold of brightness difference from the mid-gray value (i.e., 128) before the gamma value is forced to 1.0. Defaults to 45.

45

Returns:

Type Description
FibsemImage

A new FibsemImage object containing the gamma-corrected image data, with the same metadata

FibsemImage

as the input image.

Notes
  • This function applies gamma correction to the input image using the skimage.exposure.adjust_gamma function, with the gamma value computed based on the mean intensity of the image.
  • If the difference between the mean image intensity and the mid-gray value (i.e., 128) is greater than the specified gamma_threshold, the gamma value is forced to 1.0 to avoid over-correction.
  • The FibsemImage object in the returned list contains the gamma-corrected image data as a numpy array, as well as other image metadata.
Source code in fibsem/acquire.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def auto_gamma(
    image: FibsemImage,
    min_gamma: float = 0.15,
    max_gamma: float = 1.8,
    scale_factor: float = 0.01,
    gamma_threshold: int = 45,
    method: str = "autogamma",
) -> FibsemImage:
    """
    Applies automatic gamma correction to the input `FibsemImage`.

    Args:
        image (FibsemImage): The input `FibsemImage` to apply gamma correction to.
        min_gamma (float): The minimum gamma value allowed in the correction. Defaults to 0.15.
        max_gamma (float): The maximum gamma value allowed in the correction. Defaults to 1.8.
        scale_factor (float): A scaling factor to adjust the gamma correction range based on the image
            brightness. Defaults to 0.01.
        gamma_threshold (int): The maximum threshold of brightness difference from the mid-gray value
            (i.e., 128) before the gamma value is forced to 1.0. Defaults to 45.

    Returns:
        A new `FibsemImage` object containing the gamma-corrected image data, with the same metadata
        as the input image.

    Notes:
        - This function applies gamma correction to the input image using the `skimage.exposure.adjust_gamma`
          function, with the gamma value computed based on the mean intensity of the image.
        - If the difference between the mean image intensity and the mid-gray value (i.e., 128) is greater
          than the specified `gamma_threshold`, the gamma value is forced to 1.0 to avoid over-correction.
        - The `FibsemImage` object in the returned list contains the gamma-corrected image data as a
          numpy array, as well as other image metadata.
    """

    if method == "autogamma":
        std = np.std(image.data)  # unused variable?
        mean = np.mean(image.data)
        diff = mean - 255 / 2.0
        gam = np.clip(
            min_gamma, 1 + diff * scale_factor, max_gamma
        )
        if abs(diff) < gamma_threshold:
            gam = 1.0
        if image.metadata is not None:
            logging.debug(
                f"AUTO_GAMMA | {image.metadata.image_settings.beam_type} | {diff:.3f} | {gam:.3f}"
            )
        image_data = exposure.adjust_gamma(image.data, gam)

        image = FibsemImage(data=image_data, metadata=image.metadata)

    if method == "autoclahe":
        image = apply_clahe(image)

    return image

last_image(microscope, beam_type)

summary

Parameters:

Name Type Description Default
microscope FibsemMicroscope

microscope instance

required
beam_type BeamType

beam type for image

required

Returns:

Name Type Description
FibsemImage FibsemImage

last image acquired by the microscope

Source code in fibsem/acquire.py
277
278
279
280
281
282
283
284
285
286
287
def last_image(microscope: FibsemMicroscope, beam_type: BeamType) -> FibsemImage:
    """_summary_

    Args:
        microscope (FibsemMicroscope): microscope instance
        beam_type (BeamType): beam type for image

    Returns:
        FibsemImage: last image acquired by the microscope
    """
    return microscope.last_image(beam_type=beam_type)

new_image(microscope, settings)

Apply the given image settings and acquire a new image.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

The FibsemMicroscope instance used to acquire the image.

required
settings ImageSettings

The image settings used to acquire the image.

required

Returns:

Name Type Description
FibsemImage FibsemImage

The acquired image.

Source code in fibsem/acquire.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def new_image(
    microscope: FibsemMicroscope,
    settings: ImageSettings,
) -> FibsemImage:
    """Apply the given image settings and acquire a new image.

    Args:
        microscope (FibsemMicroscope): The FibsemMicroscope instance used to acquire the image.
        settings (ImageSettings): The image settings used to acquire the image.

    Returns:
        FibsemImage: The acquired image.
    """

    # set label
    if settings.beam_type is BeamType.ELECTRON:
        label = f"{settings.label}_eb"

    if settings.beam_type is BeamType.ION:
        label = f"{settings.label}_ib"

    # run autocontrast
    if settings.autocontrast:
        microscope.autocontrast(beam_type=settings.beam_type)

    # acquire the image
    image = microscope.acquire_image(
        image_settings=settings,
    )

    if settings.gamma_enabled:
        image = auto_gamma(image)

    # save image
    if settings.save:
        filename = os.path.join(settings.save_path, label)
        image.save(save_path=filename)

    return image

take_reference_images(microscope, image_settings)

Acquires a pair of electron and ion reference images using the specified imaging settings and a FibsemMicroscope instance.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

A FibsemMicroscope instance for imaging.

required
image_settings ImageSettings

An ImageSettings object with the desired imaging parameters.

required

Returns:

Type Description
FibsemImage

A tuple containing a pair of FibsemImage objects, representing the electron and ion reference

FibsemImage

images acquired using the specified microscope and image settings.

Notes
  • This function temporarily changes the image_settings.beam_type to BeamType.ELECTRON and then BeamType.ION to acquire the electron and ion reference images, respectively. It resets the image_settings.beam_type to the original value after acquiring the images.
  • The FibsemImage objects in the returned tuple contain the image data as numpy arrays, as well as other image metadata.
Source code in fibsem/acquire.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def take_reference_images(
    microscope: FibsemMicroscope, image_settings: ImageSettings
) -> tuple[FibsemImage, FibsemImage]:
    """
    Acquires a pair of electron and ion reference images using the specified imaging settings and
    a FibsemMicroscope instance.

    Args:
        microscope (FibsemMicroscope): A FibsemMicroscope instance for imaging.
        image_settings (ImageSettings): An ImageSettings object with the desired imaging parameters.

    Returns:
        A tuple containing a pair of FibsemImage objects, representing the electron and ion reference
        images acquired using the specified microscope and image settings.

    Notes:
        - This function temporarily changes the `image_settings.beam_type` to `BeamType.ELECTRON`
          and then `BeamType.ION` to acquire the electron and ion reference images, respectively.
          It resets the `image_settings.beam_type` to the original value after acquiring the images.
        - The `FibsemImage` objects in the returned tuple contain the image data as numpy arrays,
          as well as other image metadata.
    """
    import time 
    from fibsem.microscope import TescanMicroscope
    tmp_beam_type = image_settings.beam_type
    image_settings.beam_type = BeamType.ELECTRON
    eb_image = new_image(microscope, image_settings)
    image_settings.beam_type = BeamType.ION
    if isinstance(microscope, TescanMicroscope):
        time.sleep(3)
    ib_image = new_image(microscope, image_settings)
    image_settings.beam_type = tmp_beam_type  # reset to original beam type

    return eb_image, ib_image

take_set_of_reference_images(microscope, image_settings, hfws, label='ref_image')

Takes a set of reference images at low and high magnification using a FibsemMicroscope. The image settings and half-field widths for the low- and high-resolution images are specified using an ImageSettings object and a tuple of two floats, respectively. The optional label parameter can be used to customize the image labels.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

A FibsemMicroscope object to acquire the images from.

required
image_settings ImageSettings

An ImageSettings object with the desired imaging parameters.

required
hfws Tuple[float, float]

A tuple of two floats specifying the half-field widths (in microns) for the low- and high-resolution images, respectively.

required
label str

A label to be included in the image filenames. Defaults to "ref_image".

'ref_image'

Returns:

Type Description
ReferenceImages

A ReferenceImages object containing the low- and high-resolution electron and ion beam images.

Notes

This function sets image_settings.save to True before taking the images. The returned ReferenceImages object contains the electron and ion beam images as FibsemImage objects.

Source code in fibsem/acquire.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def take_set_of_reference_images(
    microscope: FibsemMicroscope,
    image_settings: ImageSettings,
    hfws: tuple[float],
    label: str = "ref_image",
) -> ReferenceImages:
    """
    Takes a set of reference images at low and high magnification using a FibsemMicroscope.
    The image settings and half-field widths for the low- and high-resolution images are
    specified using an ImageSettings object and a tuple of two floats, respectively.
    The optional label parameter can be used to customize the image labels.

    Args:
        microscope (FibsemMicroscope): A FibsemMicroscope object to acquire the images from.
        image_settings (ImageSettings): An ImageSettings object with the desired imaging parameters.
        hfws (Tuple[float, float]): A tuple of two floats specifying the half-field widths (in microns)
            for the low- and high-resolution images, respectively.
        label (str, optional): A label to be included in the image filenames. Defaults to "ref_image".

    Returns:
        A ReferenceImages object containing the low- and high-resolution electron and ion beam images.

    Notes:
        This function sets image_settings.save to True before taking the images.
        The returned ReferenceImages object contains the electron and ion beam images as FibsemImage objects.
    """
    # force save
    image_settings.save = True

    image_settings.hfw = hfws[0]
    image_settings.label = f"{label}_low_res"
    low_eb, low_ib = take_reference_images(microscope, image_settings)

    image_settings.hfw = hfws[1]
    image_settings.label = f"{label}_high_res"
    high_eb, high_ib = take_reference_images(microscope, image_settings)

    reference_images = ReferenceImages(low_eb, high_eb, low_ib, high_ib)


    # more flexible version
    # reference_images = []
    # for i, hfw in enumerate(hfws):
    #     image_settings.hfw = hfw
    #     image_settings.label = f"{label}_res_{i:02d}"
    #     eb_image, ib_image = take_reference_images(microscope, image_settings)
    #     reference_images.append([eb_image, ib_image])

    return reference_images

Alignment

align_using_reference_images(microscope, settings, ref_image, new_image, ref_mask=None, xcorr_limit=None, constrain_vertical=False, use_beam_shift=False)

Uses cross-correlation to align a new image to a reference image.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

A FibsemMicroscope instance representing the microscope being used.

required
settings MicroscopeSettings

A MicroscopeSettings instance representing the settings for the imaging session.

required
ref_image FibsemImage

A FibsemImage instance representing the reference image to which the new image will be aligned.

required
new_image FibsemImage

A FibsemImage instance representing the new image that will be aligned to the reference image.

required
ref_mask ndarray

A numpy array representing a mask to apply to the reference image during alignment. Default is None.

None
xcorr_limit int

An integer representing the limit for the cross-correlation coefficient. If the coefficient is below this limit, alignment will fail. Default is None.

None
constrain_vertical bool

A boolean indicating whether to constrain movement to the vertical axis. If True, movement will be restricted to the vertical axis, which is useful for eucentric movement. If False, movement will be allowed on both the X and Y axes. Default is False.

False

Returns:

Type Description
bool

A boolean indicating whether the alignment was successful. True if the alignment was successful, False otherwise.

Source code in fibsem/alignment.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def align_using_reference_images(
    microscope: FibsemMicroscope,
    settings: MicroscopeSettings,
    ref_image: FibsemImage,
    new_image: FibsemImage,
    ref_mask: np.ndarray = None,
    xcorr_limit: int = None,
    constrain_vertical: bool = False,
    use_beam_shift: bool = False,
) -> bool:
    """
    Uses cross-correlation to align a new image to a reference image.

    Args:
        microscope: A FibsemMicroscope instance representing the microscope being used.
        settings: A MicroscopeSettings instance representing the settings for the imaging session.
        ref_image: A FibsemImage instance representing the reference image to which the new image will be aligned.
        new_image: A FibsemImage instance representing the new image that will be aligned to the reference image.
        ref_mask: A numpy array representing a mask to apply to the reference image during alignment. Default is None.
        xcorr_limit: An integer representing the limit for the cross-correlation coefficient. If the coefficient is below
            this limit, alignment will fail. Default is None.
        constrain_vertical: A boolean indicating whether to constrain movement to the vertical axis. If True, movement
            will be restricted to the vertical axis, which is useful for eucentric movement. If False, movement will be
            allowed on both the X and Y axes. Default is False.

    Returns:
        A boolean indicating whether the alignment was successful. True if the alignment was successful, False otherwise.
    """
    # get beam type
    ref_beam_type = BeamType[ref_image.metadata.image_settings.beam_type.name.upper()]
    new_beam_type = BeamType[new_image.metadata.image_settings.beam_type.name.upper()]

    logging.info(
        f"aligning {ref_beam_type.name} reference image to {new_beam_type.name}."
    )
    sigma = 6
    hp_px = 8
    lp_px = 128  # MAGIC_NUMBER

    dx, dy, xcorr = shift_from_crosscorrelation(
        ref_image,
        new_image,
        lowpass=lp_px,
        highpass=hp_px,
        sigma=sigma,
        use_rect_mask=True,
        ref_mask=ref_mask,
        xcorr_limit=xcorr_limit,
    )

    shift_within_tolerance = (
        validation.check_shift_within_tolerance(  # TODO: Abstract validation.py
            dx=dx, dy=dy, ref_image=ref_image, limit=0.5
        )
    )

    if shift_within_tolerance:

        # vertical constraint = eucentric movement
        if constrain_vertical:
            microscope.eucentric_move(
                settings=settings, dx=0, dy=-dy
            )  # FLAG_TEST
        else:
            if use_beam_shift:
                # move the beam shift
                microscope.beam_shift(dx=-dx, dy=dy, beam_type=new_beam_type)
            else:
                # move the stage
                microscope.stable_move(
                    settings=settings,
                    dx=dx,
                    dy=-dy,
                    beam_type=new_beam_type,
                )

    return shift_within_tolerance

beam_shift_alignment(microscope, image_settings, ref_image, reduced_area=None)

Aligns the images by adjusting the beam shift instead of moving the stage.

This method uses cross-correlation between the reference image and a new image to calculate the optimal beam shift for alignment. This approach offers increased precision, but a lower range compared to stage movement.

NOTE: Only shift the ion beam, never the electron beam.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

An OpenFIBSEM microscope client.

required
image_settings ImageSettings

Settings for taking the image.

required
ref_image FibsemImage

The reference image to align to.

required
reduced_area FibseRectangle

The reduced area to image with.

None

Raises:

Type Description
ValueError

If image_settings.beam_type is not set to BeamType.ION.

Source code in fibsem/alignment.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def beam_shift_alignment(
    microscope: FibsemMicroscope,
    image_settings: ImageSettings,
    ref_image: FibsemImage,
    reduced_area: Optional[FibsemRectangle] = None,
):
    """Aligns the images by adjusting the beam shift instead of moving the stage.

    This method uses cross-correlation between the reference image and a new image to calculate the
    optimal beam shift for alignment. This approach offers increased precision, but a lower range
    compared to stage movement.

    NOTE: Only shift the ion beam, never the electron beam.

    Args:
        microscope (FibsemMicroscope): An OpenFIBSEM microscope client.
        image_settings (acquire.ImageSettings): Settings for taking the image.
        ref_image (FibsemImage): The reference image to align to.
        reduced_area (FibseRectangle): The reduced area to image with.

    Raises:
        ValueError: If `image_settings.beam_type` is not set to `BeamType.ION`.

    """
    import time
    time.sleep(3) # threading is too fast?
    image_settings = ImageSettings.fromFibsemImage(ref_image)
    image_settings.beam_type = BeamType.ION
    image_settings.reduced_area = reduced_area
    new_image = acquire.new_image(
        microscope, settings=image_settings
    )
    dx, dy, _ = shift_from_crosscorrelation(
        ref_image, new_image, lowpass=50, highpass=4, sigma=5, use_rect_mask=True
    )

    # adjust beamshift 
    microscope.beam_shift(dx, dy, image_settings.beam_type)

correct_stage_drift(microscope, settings, reference_images, alignment=(BeamType.ELECTRON, BeamType.ELECTRON), rotate=False, ref_mask_rad=512, xcorr_limit=None, constrain_vertical=False, use_beam_shift=False)

Corrects the stage drift by aligning low- and high-resolution reference images using cross-correlation.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

The microscope used for image acquisition.

required
settings MicroscopeSettings

The settings used for image acquisition.

required
reference_images ReferenceImages

A container of low- and high-resolution reference images.

required
alignment tuple[BeamType, BeamType]

A tuple of two BeamType objects, specifying the beam types used for the alignment of low- and high-resolution images, respectively. Defaults to (BeamType.ELECTRON, BeamType.ELECTRON).

(ELECTRON, ELECTRON)
rotate bool

Whether to rotate the reference images before alignment. Defaults to False.

False
ref_mask_rad int

The radius of the circular mask used for reference

512
xcorr_limit tuple[int, int] | None

A tuple of two integers that represent the minimum and maximum cross-correlation values allowed for the alignment. If not specified, the values are set to (None, None), which means there are no limits. Defaults to None.

None
constrain_vertical bool

Whether to constrain the alignment to the vertical axis. Defaults to False.

False

Returns:

Name Type Description
bool bool

True if the stage drift correction was successful, False otherwise.

Source code in fibsem/alignment.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def correct_stage_drift(
    microscope: FibsemMicroscope,
    settings: MicroscopeSettings,
    reference_images: ReferenceImages,
    alignment: tuple[BeamType, BeamType] = (BeamType.ELECTRON, BeamType.ELECTRON),
    rotate: bool = False,
    ref_mask_rad: int = 512,
    xcorr_limit: Union[tuple[int, int], None] = None,
    constrain_vertical: bool = False,
    use_beam_shift: bool = False,
) -> bool:
    """Corrects the stage drift by aligning low- and high-resolution reference images
    using cross-correlation.

    Args:
        microscope (FibsemMicroscope): The microscope used for image acquisition.
        settings (MicroscopeSettings): The settings used for image acquisition.
        reference_images (ReferenceImages): A container of low- and high-resolution
            reference images.
        alignment (tuple[BeamType, BeamType], optional): A tuple of two `BeamType`
            objects, specifying the beam types used for the alignment of low- and
            high-resolution images, respectively. Defaults to (BeamType.ELECTRON,
            BeamType.ELECTRON).
        rotate (bool, optional): Whether to rotate the reference images before
            alignment. Defaults to False.
        ref_mask_rad (int, optional): The radius of the circular mask used for reference
        xcorr_limit (tuple[int, int] | None, optional): A tuple of two integers that
            represent the minimum and maximum cross-correlation values allowed for the
            alignment. If not specified, the values are set to (None, None), which means
            there are no limits. Defaults to None.
        constrain_vertical (bool, optional): Whether to constrain the alignment to the
            vertical axis. Defaults to False.

    Returns:
        bool: True if the stage drift correction was successful, False otherwise.
    """

    # set reference images
    if alignment[0] is BeamType.ELECTRON:
        ref_lowres, ref_highres = (
            reference_images.low_res_eb,
            reference_images.high_res_eb,
        )
    if alignment[0] is BeamType.ION:
        ref_lowres, ref_highres = (
            reference_images.low_res_ib,
            reference_images.high_res_ib,
        )

    if xcorr_limit is None:
        xcorr_limit = (None, None)

    # rotate reference
    if rotate:
        ref_lowres = image_utils.rotate_image(ref_lowres)
        ref_highres = image_utils.rotate_image(ref_highres)

    # align lowres, then highres
    for i, ref_image in enumerate([ref_lowres, ref_highres]):

        ref_mask = masks.create_circle_mask(ref_image.data.shape, ref_mask_rad)

        # take new images
        # set new image settings (same as reference)
        settings.image = ImageSettings.fromFibsemImage(ref_image)
        settings.image.beam_type = alignment[1]
        new_image = acquire.new_image(microscope, settings.image)

        # crosscorrelation alignment
        ret = align_using_reference_images(
            microscope,
            settings,
            ref_image,
            new_image,
            ref_mask=ref_mask,
            xcorr_limit=xcorr_limit[i],
            constrain_vertical=constrain_vertical,
            use_beam_shift=use_beam_shift,
        )

        if ret is False:
            break  # cross correlation has failed...

    return ret

crosscorrelation_v2(img1, img2, bandpass=None)

Cross-correlate two images using Fourier convolution matching.

Parameters:

Name Type Description Default
img1 ndarray

The reference image.

required
img2 ndarray

The new image to be cross-correlated with the reference.

required
bandpass ndarray

A bandpass mask to apply to both images before cross-correlation. Defaults to None.

None

Returns:

Type Description
ndarray

np.ndarray: The cross-correlation map between the two images.

Source code in fibsem/alignment.py
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
def crosscorrelation_v2(
    img1: np.ndarray, img2: np.ndarray, bandpass: np.ndarray = None
) -> np.ndarray:
    """
    Cross-correlate two images using Fourier convolution matching.

    Args:
        img1 (np.ndarray): The reference image.
        img2 (np.ndarray): The new image to be cross-correlated with the reference.
        bandpass (np.ndarray, optional): A bandpass mask to apply to both images before cross-correlation. Defaults to None.

    Returns:
        np.ndarray: The cross-correlation map between the two images.
    """
    if img1.shape != img2.shape:
        err = (
            f"Image 1 {img1.shape} and Image 2 {img2.shape} need to have the same shape"
        )
        logging.error(err)
        raise ValueError(err)

    if bandpass is None:
        bandpass = np.ones_like(img1)

    n_pixels = img1.shape[0] * img1.shape[1]

    img1ft = np.fft.ifftshift(bandpass * np.fft.fftshift(np.fft.fft2(img1)))
    tmp = img1ft * np.conj(img1ft)
    img1ft = n_pixels * img1ft / np.sqrt(tmp.sum())

    img2ft = np.fft.ifftshift(bandpass * np.fft.fftshift(np.fft.fft2(img2)))
    img2ft[0, 0] = 0
    tmp = img2ft * np.conj(img2ft)

    img2ft = n_pixels * img2ft / np.sqrt(tmp.sum())

    # import matplotlib.pyplot as plt
    # fig, ax = plt.subplots(1, 2, figsize=(15, 15))
    # ax[0].imshow(np.fft.ifft2(img1ft).real)
    # ax[1].imshow(np.fft.ifft2(img2ft).real)
    # plt.show()

    # plt.title("Power Spectra")
    # plt.imshow(np.log(np.abs(np.fft.fftshift(np.fft.fft2(img1)))))
    # plt.show()

    xcorr = np.real(np.fft.fftshift(np.fft.ifft2(img1ft * np.conj(img2ft))))

    return xcorr

shift_from_crosscorrelation(ref_image, new_image, lowpass=128, highpass=6, sigma=6, use_rect_mask=False, ref_mask=None, xcorr_limit=None)

Calculates the shift between two images by cross-correlating them and finding the position of maximum correlation.

Parameters:

Name Type Description Default
ref_image FibsemImage

The reference image.

required
new_image FibsemImage

The new image to align to the reference.

required
lowpass int

The low-pass filter frequency (in pixels) for the bandpass filter used to enhance the correlation signal. Defaults to 128.

128
highpass int

The high-pass filter frequency (in pixels) for the bandpass filter used to enhance the correlation signal. Defaults to 6.

6
sigma int

The standard deviation (in pixels) of the Gaussian filter used to create the bandpass mask. Defaults to 6.

6
use_rect_mask bool

Whether to use a rectangular mask for the correlation. If True, the correlation is performed only inside a rectangle that covers most of the image, to reduce the effect of noise at the edges. Defaults to False.

False
ref_mask ndarray

A mask to apply to the reference image before correlation. If not None, it should be a binary array with the same shape as the images. Pixels with value 0 will be ignored in the correlation. Defaults to None.

None
xcorr_limit int

If not None, the correlation map will be circularly masked to a square with sides of length 2 * xcorr_limit + 1, centred on the maximum correlation peak. This can be used to limit the search range and improve the accuracy of the shift. Defaults to None.

None

Returns:

Type Description
float

A tuple (x_shift, y_shift, xcorr), where x_shift and y_shift are the shifts along x and y (in meters),

float

and xcorr is the cross-correlation map between the images.

Source code in fibsem/alignment.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
def shift_from_crosscorrelation(
    ref_image: FibsemImage,
    new_image: FibsemImage,
    lowpass: int = 128,
    highpass: int = 6,
    sigma: int = 6,
    use_rect_mask: bool = False,
    ref_mask: np.ndarray = None,
    xcorr_limit: int = None,
) -> tuple[float, float, np.ndarray]:
    """Calculates the shift between two images by cross-correlating them and finding the position of maximum correlation.

    Args:
        ref_image (FibsemImage): The reference image.
        new_image (FibsemImage): The new image to align to the reference.
        lowpass (int, optional): The low-pass filter frequency (in pixels) for the bandpass filter used to
            enhance the correlation signal. Defaults to 128.
        highpass (int, optional): The high-pass filter frequency (in pixels) for the bandpass filter used to
            enhance the correlation signal. Defaults to 6.
        sigma (int, optional): The standard deviation (in pixels) of the Gaussian filter used to create the bandpass
            mask. Defaults to 6.
        use_rect_mask (bool, optional): Whether to use a rectangular mask for the correlation. If True, the correlation
            is performed only inside a rectangle that covers most of the image, to reduce the effect of noise at the
            edges. Defaults to False.
        ref_mask (np.ndarray, optional): A mask to apply to the reference image before correlation. If not None,
            it should be a binary array with the same shape as the images. Pixels with value 0 will be ignored in the
            correlation. Defaults to None.
        xcorr_limit (int, optional): If not None, the correlation map will be circularly masked to a square
            with sides of length 2 * xcorr_limit + 1, centred on the maximum correlation peak. This can be used to
            limit the search range and improve the accuracy of the shift. Defaults to None.

    Returns:
        A tuple (x_shift, y_shift, xcorr), where x_shift and y_shift are the shifts along x and y (in meters),
        and xcorr is the cross-correlation map between the images.
    """
    # get pixel_size
    pixelsize_x = new_image.metadata.pixel_size.x
    pixelsize_y = new_image.metadata.pixel_size.y

    # normalise both images
    ref_data_norm = image_utils.normalise_image(ref_image)
    new_data_norm = image_utils.normalise_image(new_image)

    # cross-correlate normalised images
    if use_rect_mask:
        rect_mask = masks._mask_rectangular(new_data_norm.shape)
        ref_data_norm = rect_mask * ref_data_norm
        new_data_norm = rect_mask * new_data_norm

    if ref_mask is not None:
        ref_data_norm = ref_mask * ref_data_norm  # mask the reference

    # bandpass mask
    bandpass = masks.create_bandpass_mask(
        shape=ref_data_norm.shape, lp=lowpass, hp=highpass, sigma=sigma
    )

    # crosscorrelation
    xcorr = crosscorrelation_v2(ref_data_norm, new_data_norm, bandpass=bandpass)

    # limit xcorr range
    if xcorr_limit:
        xcorr = masks.apply_circular_mask(xcorr, xcorr_limit)

    # calculate maximum crosscorrelation
    maxX, maxY = np.unravel_index(np.argmax(xcorr), xcorr.shape)  # TODO: backwards
    cen = np.asarray(xcorr.shape) / 2
    err = np.array(cen - [maxX, maxY], int)

    # calculate shift in metres
    dx = err[1] * pixelsize_x
    dy = err[0] * pixelsize_y  # this could be the issue?

    logging.debug(f"cross-correlation:")
    logging.debug(f"pixelsize: x: {pixelsize_x:.2e}, y: {pixelsize_y:.2e}")
    logging.debug(f"maxX: {maxX}, {maxY}, centre: {cen}")
    logging.debug(f"x: {err[1]}px, y: {err[0]}px")
    logging.debug(f"x: {dx:.2e}m, y: {dy:.2e} meters")

    # save data
    _save_alignment_data(
        ref_image=ref_image,
        new_image=new_image,
        bandpass=bandpass,
        xcorr=xcorr,
        use_rect_mask=use_rect_mask,
        ref_mask=ref_mask,
        xcorr_limit=xcorr_limit,
        lowpass=lowpass,
        highpass=highpass,
        sigma=sigma,
        dx=dx,
        dy=dy,
        pixelsize_x=pixelsize_x,
        pixelsize_y=pixelsize_y,
    )

    # metres
    return dx, dy, xcorr

Calibration

align_needle_to_eucentric_position(microscope, settings, validate=False)

Move the needle to the eucentric position, and save the updated position to disk

Parameters:

Name Type Description Default
microscope FibsemMicroscope

OpenFIBSEM microscope instance

required
settings MicroscopeSettings

microscope settings

required
validate bool

validate the alignment. Defaults to False.

False
Source code in fibsem/calibration.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def align_needle_to_eucentric_position(
    microscope: FibsemMicroscope,
    settings: MicroscopeSettings,
    validate: bool = False,
) -> None:
    """Move the needle to the eucentric position, and save the updated position to disk

    Args:
        microscope (FibsemMicroscope): OpenFIBSEM microscope instance
        settings (MicroscopeSettings): microscope settings
        validate (bool, optional): validate the alignment. Defaults to False.
    """

    from fibsem.ui import windows as fibsem_ui_windows
    from fibsem.detection import detection

    # take reference images
    settings.image.save = False
    settings.image.beam_type = BeamType.ELECTRON

    det = fibsem_ui_windows.detect_features_v2(
        microscope=microscope,
        settings=settings,
        features=[
            NeedleTip(),
            ImageCentre(),
        ],
        validate=validate,
    )
    detection.move_based_on_detection(
        microscope, settings, det, beam_type=settings.image.beam_type
    )

    # take reference images
    settings.image.save = False
    settings.image.beam_type = BeamType.ION

    image = acquire.new_image(microscope, settings.image)

    det = fibsem_ui_windows.detect_features_v2(
        microscope=microscope,
        settings=settings,
        features=[
            NeedleTip(),
            ImageCentre(),
        ],
        validate=validate,
    )
    detection.move_based_on_detection(
        microscope, settings, det, beam_type=settings.image.beam_type, move_x=False
    )

    # take image
    acquire.take_reference_images(microscope, settings.image)

Conversions

convert_metres_to_pixels(distance, pixelsize)

Convert a distance in metres to pixels based on a given pixel size.

Parameters:

Name Type Description Default
distance float

The distance to convert, in metres.

required
pixelsize float

The size of a pixel in metres.

required

Returns:

Name Type Description
int int

The distance converted to pixels, as an integer.

Source code in fibsem/conversions.py
70
71
72
73
74
75
76
77
78
79
80
81
def convert_metres_to_pixels(distance: float, pixelsize: float) -> int:
    """
    Convert a distance in metres to pixels based on a given pixel size.

    Args:
        distance (float): The distance to convert, in metres.
        pixelsize (float): The size of a pixel in metres.

    Returns:
        int: The distance converted to pixels, as an integer.
    """
    return int(distance / pixelsize)

convert_pixels_to_metres(pixels, pixelsize)

Convert a distance in pixels to metres based on a given pixel size.

Parameters:

Name Type Description Default
pixels int

The number of pixels to convert.

required
pixelsize float

The size of a pixel in metres.

required

Returns:

Name Type Description
float float

The distance converted to metres.

Source code in fibsem/conversions.py
84
85
86
87
88
89
90
91
92
93
94
95
def convert_pixels_to_metres(pixels: int, pixelsize: float) -> float:
    """
    Convert a distance in pixels to metres based on a given pixel size.

    Args:
        pixels (int): The number of pixels to convert.
        pixelsize (float): The size of a pixel in metres.

    Returns:
        float: The distance converted to metres.
    """
    return float(pixels * pixelsize)

convert_point_from_metres_to_pixel(point, pixelsize)

Convert a Point object from metre coordinates to pixel coordinates, based on a given pixel size.

Parameters:

Name Type Description Default
point Point

The Point object to convert.

required
pixelsize float

The size of a pixel in metres.

required

Returns:

Name Type Description
Point Point

The converted Point object, with its x and y values in pixel coordinates.

Source code in fibsem/conversions.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def convert_point_from_metres_to_pixel(point: Point, pixelsize: float) -> Point:
    """
    Convert a Point object from metre coordinates to pixel coordinates, based on a given pixel size.

    Args:
        point (Point): The Point object to convert.
        pixelsize (float): The size of a pixel in metres.

    Returns:
        Point: The converted Point object, with its x and y values in pixel coordinates.
    """
    point_px = Point(
        x=convert_metres_to_pixels(point.x, pixelsize),
        y=convert_metres_to_pixels(point.y, pixelsize),
    )
    return point_px

convert_point_from_pixel_to_metres(point, pixelsize)

Convert a Point object from pixel coordinates to metre coordinates, based on a given pixel size.

Parameters:

Name Type Description Default
point Point

The Point object to convert.

required
pixelsize float

The size of a pixel in metres.

required

Returns:

Name Type Description
Point Point

The converted Point object, with its x and y values in metre coordinates.

Source code in fibsem/conversions.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def convert_point_from_pixel_to_metres(point: Point, pixelsize: float) -> Point:
    """
    Convert a Point object from pixel coordinates to metre coordinates, based on a given pixel size.

    Args:
        point (Point): The Point object to convert.
        pixelsize (float): The size of a pixel in metres.

    Returns:
        Point: The converted Point object, with its x and y values in metre coordinates.
    """
    point_m = Point(
        x=convert_pixels_to_metres(point.x, pixelsize),
        y=convert_pixels_to_metres(point.y, pixelsize),
    )

    return point_m

distance_between_points(p1, p2)

Calculate the Euclidean distance between two points, returning a Point object representing the result.

Parameters:

Name Type Description Default
p1 Point

The first point.

required
p2 Point

The second point.

required

Returns:

Name Type Description
Point Point

A Point object representing the distance between the two points.

Source code in fibsem/conversions.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def distance_between_points(p1: Point, p2: Point) -> Point:
    """
    Calculate the Euclidean distance between two points, returning a Point object representing the result.

    Args:
        p1 (Point): The first point.
        p2 (Point): The second point.

    Returns:
        Point: A Point object representing the distance between the two points.
    """

    return Point(x=(p2.x - p1.x), y=(p2.y - p1.y))

get_lamella_size_in_pixels(img, protocol, use_trench_height=False)

Get the relative size of the lamella in pixels based on the hfw of the image.

Parameters:

Name Type Description Default
img FibsemImage

A reference image.

required
protocol dict

A dictionary containing the protocol information.

required
use_trench_height bool

If True, returns the height of the trench instead of the lamella. Default is False.

False

Returns:

Type Description
tuple[int]

tuple[int]: A tuple containing the height and width of the lamella in pixels.

Source code in fibsem/conversions.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def get_lamella_size_in_pixels(
    img: FibsemImage, protocol: dict, use_trench_height: bool = False
) -> tuple[int]:
    """Get the relative size of the lamella in pixels based on the hfw of the image.

    Args:
        img (FibsemImage): A reference image.
        protocol (dict): A dictionary containing the protocol information.
        use_trench_height (bool, optional): If True, returns the height of the trench instead of the lamella. Default is False.

    Returns:
        tuple[int]: A tuple containing the height and width of the lamella in pixels.
    """
    # get real size from protocol
    lamella_width = protocol["lamella_width"]
    lamella_height = protocol["lamella_height"]

    total_height = lamella_height
    if use_trench_height:
        trench_height = protocol["stages"][0]["trench_height"]
        total_height += 2 * trench_height

    # convert to m
    pixelsize = img.metadata.pixel_size.x
    width, height = img.metadata.image_settings.resolution
    vfw = convert_pixels_to_metres(height, pixelsize)
    hfw = convert_pixels_to_metres(width, pixelsize)

    # lamella size in px (% of image)
    lamella_height_px = int((total_height / vfw) * height)
    lamella_width_px = int((lamella_width / hfw) * width)

    return (lamella_height_px, lamella_width_px)

image_to_microscope_image_coordinates(coord, image, pixelsize)

Convert an image pixel coordinate to a microscope image coordinate.

The microscope image coordinate system is centered on the image with positive Y-axis pointing upwards.

Parameters:

Name Type Description Default
coord Point

A Point object representing the pixel coordinates in the original image.

required
image ndarray

A numpy array representing the image.

required
pixelsize float

The pixel size in meters.

required

Returns:

Name Type Description
Point Point

A Point object representing the corresponding microscope image coordinates in meters.

Source code in fibsem/conversions.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def image_to_microscope_image_coordinates(
    coord: Point, image: np.ndarray, pixelsize: float
) -> Point:
    """
    Convert an image pixel coordinate to a microscope image coordinate.

    The microscope image coordinate system is centered on the image with positive Y-axis pointing upwards.

    Args:
        coord (Point): A Point object representing the pixel coordinates in the original image.
        image (np.ndarray): A numpy array representing the image.
        pixelsize (float): The pixel size in meters.

    Returns:
        Point: A Point object representing the corresponding microscope image coordinates in meters.
    """
    # convert from image pixel coord (0, 0) top left to microscope image (0, 0) mid

    # shape
    cy, cx = np.asarray(image.shape) // 2

    # distance from centre?
    dy = float(-(coord.y - cy))  # neg = down
    dx = float(coord.x - cx)  # neg = left

    point_m = convert_point_from_pixel_to_metres(Point(dx, dy), pixelsize)

    return point_m

Milling

draw_bitmap(microscope, pattern_settings, path)

Draw a butmap milling pattern from settings

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem

required
mill_settings MillingSettings

milling pattern settings

required
Source code in fibsem/milling.py
154
155
156
157
158
159
160
161
162
def draw_bitmap(microscope: FibsemMicroscope, pattern_settings: FibsemPatternSettings, path: str):
    """Draw a butmap milling pattern from settings

    Args:
        microscope (FibsemMicroscope): Fibsem
        mill_settings (MillingSettings): milling pattern settings
    """
    path = convert_to_bitmap_format(path)
    microscope.draw_bitmap_pattern(pattern_settings, path)

draw_circle(microscope, pattern_settings)

Draw a circular milling pattern from settings

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
mill_settings MillingSettings

milling pattern settings

required
Source code in fibsem/milling.py
136
137
138
139
140
141
142
143
def draw_circle(microscope: FibsemMicroscope, pattern_settings: FibsemPatternSettings):
    """Draw a circular milling pattern from settings

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        mill_settings (MillingSettings): milling pattern settings
    """
    microscope.draw_circle(pattern_settings)

draw_fiducial(microscope, pattern_settings)

draw the fiducial milling patterns

Parameters:

Name Type Description Default
microscope FibsemMicroscope

OpenFIBSEM microscope instance

required
mill_settings dict

fiducial milling settings

required
point Point

centre x, y coordinate

required

Returns

patterns : list
    List of rectangular patterns used to create the fiducial marker.
Source code in fibsem/milling.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def draw_fiducial(
    microscope: FibsemMicroscope,
    pattern_settings: FibsemPatternSettings,
):
    """draw the fiducial milling patterns

    Args:
        microscope (FibsemMicroscope): OpenFIBSEM microscope instance
        mill_settings (dict): fiducial milling settings
        point (Point): centre x, y coordinate
    Returns
    -------
        patterns : list
            List of rectangular patterns used to create the fiducial marker.
    """
    pattern_settings.rotation += np.deg2rad(45)
    pattern_1 = draw_rectangle(microscope, pattern_settings)
    pattern_settings.rotation = pattern_settings.rotation + np.deg2rad(90)
    pattern_2 = draw_rectangle(microscope, pattern_settings)

    return [pattern_1, pattern_2]

draw_line(microscope, pattern_settings)

Draw a line milling pattern from settings

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
mill_settings MillingSettings

milling pattern settings

required
Source code in fibsem/milling.py
127
128
129
130
131
132
133
134
def draw_line(microscope: FibsemMicroscope, pattern_settings: FibsemPatternSettings):
    """Draw a line milling pattern from settings

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        mill_settings (MillingSettings): milling pattern settings
    """
    microscope.draw_line(pattern_settings)

draw_pattern(microscope, pattern)

Draw a milling pattern from settings

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
pattern_settings FibsemPatternSettings

pattern settings

required
mill_settings FibsemMillingSettings

milling settings

required
Source code in fibsem/milling.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def draw_pattern(microscope: FibsemMicroscope, pattern: FibsemPatternSettings):
    """Draw a milling pattern from settings

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        pattern_settings (FibsemPatternSettings): pattern settings
        mill_settings (FibsemMillingSettings): milling settings
    """
    if pattern.pattern is FibsemPattern.Rectangle:
        microscope.draw_rectangle(pattern)

    elif pattern.pattern is FibsemPattern.Line:
        microscope.draw_line(pattern)

    elif pattern.pattern is FibsemPattern.Circle:
        microscope.draw_circle(pattern)

    elif pattern.pattern is FibsemPattern.Bitmap:
        microscope.draw_bitmap_pattern(pattern, pattern.path)

    elif pattern.pattern is FibsemPattern.Annulus:
        microscope.draw_annulus(pattern)

draw_patterns(microscope, patterns)

Draw a milling pattern from settings Args: microscope (FibsemMicroscope): Fibsem microscope instance

Source code in fibsem/milling.py
79
80
81
82
83
84
85
86
def draw_patterns(microscope: FibsemMicroscope, patterns: list[FibsemPatternSettings]) -> None:
    """Draw a milling pattern from settings
    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
    """

    for pattern in patterns:
        draw_pattern(microscope, pattern)

draw_rectangle(microscope, pattern_settings)

Draw a rectangular milling pattern from settings

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
pattern_settings FibsemPatternSettings

pattern settings

required
mill_settings FibsemMillingSettings

milling settings

required
Source code in fibsem/milling.py
114
115
116
117
118
119
120
121
122
123
124
def draw_rectangle(
    microscope: FibsemMicroscope, pattern_settings: FibsemPatternSettings
):
    """Draw a rectangular milling pattern from settings

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        pattern_settings (FibsemPatternSettings): pattern settings
        mill_settings (FibsemMillingSettings): milling settings
    """
    microscope.draw_rectangle(pattern_settings)

draw_stress_relief(microscope, microexpansion_protocol, lamella_protocol, centre_point=Point(0, 0), scan_direction=['LeftToRight', 'RightToLeft'])

Draw the microexpansion joints for stress relief of lamella.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

OpenFIBSEM microscope instance

required
microexpansion_protocol dict

Contains a dictionary of the necessary values for drawing the joints.

required
lamella_protocol dict

Lamella protocol

required

Returns:

Name Type Description
patterns

list

Source code in fibsem/milling.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def draw_stress_relief(
    microscope: FibsemMicroscope,
    microexpansion_protocol: dict,
    lamella_protocol: dict,
    centre_point: Point = Point(0, 0),
    scan_direction: list[str] = ["LeftToRight", "RightToLeft"], 
):
    """
    Draw the microexpansion joints for stress relief of lamella.

    Args:
        microscope (FibsemMicroscope): OpenFIBSEM microscope instance
        microexpansion_protocol (dict): Contains a dictionary of the necessary values for drawing the joints.
        lamella_protocol (dict): Lamella protocol

    Returns:
        patterns: list
    """
    width = microexpansion_protocol["width"]
    height = microexpansion_protocol["height"]
    depth = lamella_protocol["depth"]
    if scan_direction is None:
        scan_direction = ["LeftToRight", "RightToLeft"]

    left_pattern_settings = FibsemPatternSettings(
        width=width,
        height=height,
        depth=depth,
        centre_x=centre_point.x
        - lamella_protocol["lamella_width"] / 2
        - microexpansion_protocol["distance"],
        centre_y=centre_point.y,
        cleaning_cross_section=True,
        scan_direction=scan_direction[0],
    )

    right_pattern_settings = FibsemPatternSettings(
        width=width,
        height=height,
        depth=depth,
        centre_x=centre_point.x
        + lamella_protocol["lamella_width"] / 2
        + microexpansion_protocol["distance"],
        centre_y=centre_point.y,
        cleaning_cross_section=True,
        scan_direction=scan_direction[1],
    )

    left_pattern = draw_rectangle(
        microscope=microscope, pattern_settings=left_pattern_settings
    )
    right_pattern = draw_rectangle(
        microscope=microscope, pattern_settings=right_pattern_settings
    )

    return [left_pattern, right_pattern]

draw_trench(microscope, protocol, point=Point())

Calculate the trench milling patterns

Source code in fibsem/milling.py
200
201
202
203
204
205
206
207
208
209
210
def draw_trench(microscope: FibsemMicroscope, protocol: dict, point: Point = Point()):
    """Calculate the trench milling patterns"""


    lower_pattern_settings, upper_pattern_settings = extract_trench_parameters(protocol, point)

    # draw patterns
    lower_pattern = draw_rectangle(microscope, lower_pattern_settings)
    upper_pattern = draw_rectangle(microscope, upper_pattern_settings)

    return [lower_pattern, upper_pattern]

finish_milling(microscope, imaging_current=2e-11)

Clear milling patterns, and restore to the imaging current.

Parameters:

Name Type Description Default
microscope FIbsemMicroscope

Fibsem microscope instance

required
imaging_current float

Imaging Current. Defaults to 20e-12.

2e-11
Source code in fibsem/milling.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def finish_milling(
    microscope: FibsemMicroscope, imaging_current: float = 20e-12
) -> None:
    """Clear milling patterns, and restore to the imaging current.

    Args:
        microscope (FIbsemMicroscope): Fibsem microscope instance
        imaging_current (float, optional): Imaging Current. Defaults to 20e-12.

    """
    # restore imaging current
    logging.info(f"Changing to Imaging Current: {imaging_current:.2e}")
    microscope.finish_milling(imaging_current)
    logging.info("Finished Ion Beam Milling.")

read_protocol_dictionary(protocol, stage_name)

Read the milling protocol settings dictionary into a structured format

Parameters:

Name Type Description Default
protocol dict

protocol dictionary

required
stage_name str

milling stage name

required

Returns:

Type Description
list[dict]

list[dict]: milling protocol stages

Source code in fibsem/milling.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def read_protocol_dictionary(protocol: dict, stage_name: str) -> list[dict]:
    """Read the milling protocol settings dictionary into a structured format

    Args:
        protocol (dict): protocol dictionary
        stage_name (str): milling stage name

    Returns:
        list[dict]: milling protocol stages
    """
    # multi-stage
    if "protocol_stages" in protocol[stage_name]:
        protocol_stages = []
        for stage_settings in protocol[stage_name]["protocol_stages"]:
            tmp_settings = protocol[stage_name].copy()
            tmp_settings.update(stage_settings)
            protocol_stages.append(tmp_settings)
    # single-stage
    else:
        protocol_stages = [protocol[stage_name]]

    return protocol_stages

run_milling(microscope, milling_current, asynch=False)

Run Ion Beam Milling.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
milling_current float

ion beam milling current. Defaults to None.

required
asynch bool

flag to run milling asynchronously. Defaults to False.

False
Source code in fibsem/milling.py
50
51
52
53
54
55
56
57
58
59
60
61
62
def run_milling(
    microscope: FibsemMicroscope,
    milling_current: float,
    asynch: bool = False,
) -> None:
    """Run Ion Beam Milling.

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        milling_current (float, optional): ion beam milling current. Defaults to None.
        asynch (bool, optional): flag to run milling asynchronously. Defaults to False.
    """
    microscope.run_milling(milling_current, asynch)

run_milling_drift_corrected(microscope, milling_current, image_settings, ref_image, reduced_area=None)

Run Ion Beam Milling.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
milling_current float

ion beam milling current. Defaults to None.

required
asynch bool

flag to run milling asynchronously. Defaults to False.

required
Source code in fibsem/milling.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def run_milling_drift_corrected(
    microscope: FibsemMicroscope, 
    milling_current: float,  
    image_settings: ImageSettings, 
    ref_image: FibsemImage, 
    reduced_area: FibsemRectangle = None,
) -> None:
    """Run Ion Beam Milling.

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        milling_current (float, optional): ion beam milling current. Defaults to None.
        asynch (bool, optional): flag to run milling asynchronously. Defaults to False.
    """
    microscope.run_milling_drift_corrected(milling_current, image_settings, ref_image, reduced_area)

setup_milling(microscope, mill_settings=None)

Setup Microscope for Ion Beam Milling.

Parameters:

Name Type Description Default
microscope FibsemMicroscope

Fibsem microscope instance

required
patterning_mode str

Ion beam milling patterning mode. Defaults to "Serial".

required
hfw float

horizontal field width for milling. Defaults to 100e-6.

required
Source code in fibsem/milling.py
21
22
23
24
25
26
27
28
29
30
31
32
def setup_milling(
    microscope: FibsemMicroscope,
    mill_settings: FibsemMillingSettings = None,
):
    """Setup Microscope for Ion Beam Milling.

    Args:
        microscope (FibsemMicroscope): Fibsem microscope instance
        patterning_mode (str, optional): Ion beam milling patterning mode. Defaults to "Serial".
        hfw (float, optional): horizontal field width for milling. Defaults to 100e-6.
    """
    microscope.setup_milling(mill_settings = mill_settings)

Movement

angle_difference(angle1, angle2)

Return the difference between two angles, accounting for greater than 360, less than 0 angles

Parameters:

Name Type Description Default
angle1 float

angle1 (radians)

required
angle2 float

angle2 (radians)

required

Returns:

Name Type Description
float float

description

Source code in fibsem/movement.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def angle_difference(angle1: float, angle2: float) -> float:
    """Return the difference between two angles, accounting for greater than 360, less than 0 angles

    Args:
        angle1 (float): angle1 (radians)
        angle2 (float): angle2 (radians)

    Returns:
        float: _description_
    """
    angle1 %= 2 * np.pi
    angle2 %= 2 * np.pi

    large_angle = np.max([angle1, angle2])
    small_angle = np.min([angle1, angle2])

    return min((large_angle - small_angle), ((2 * np.pi + small_angle - large_angle)))

rotation_angle_is_larger(angle1, angle2, atol=90)

Check the rotation angles are large

Parameters:

Name Type Description Default
angle1 float

angle1 (radians)

required
angle2 float

angle2 (radians)

required
atol

tolerance (degrees)

90

Returns:

Name Type Description
bool bool

rotation angle is larger than atol

Source code in fibsem/movement.py
39
40
41
42
43
44
45
46
47
48
49
50
51
def rotation_angle_is_larger(angle1: float, angle2: float, atol: float = 90) -> bool:
    """Check the rotation angles are large

    Args:
        angle1 (float): angle1 (radians)
        angle2 (float): angle2 (radians)
        atol : tolerance (degrees)

    Returns:
        bool: rotation angle is larger than atol
    """

    return angle_difference(angle1, angle2) > (np.deg2rad(atol))

rotation_angle_is_smaller(angle1, angle2, atol=5)

Check the rotation angles are large

Parameters:

Name Type Description Default
angle1 float

angle1 (radians)

required
angle2 float

angle2 (radians)

required
atol

tolerance (degrees)

5

Returns:

Name Type Description
bool bool

rotation angle is smaller than atol

Source code in fibsem/movement.py
54
55
56
57
58
59
60
61
62
63
64
65
66
def rotation_angle_is_smaller(angle1: float, angle2: float, atol: float = 5) -> bool:
    """Check the rotation angles are large

    Args:
        angle1 (float): angle1 (radians)
        angle2 (float): angle2 (radians)
        atol : tolerance (degrees)

    Returns:
        bool: rotation angle is smaller than atol
    """

    return angle_difference(angle1, angle2) < (np.deg2rad(atol))

Structures

BeamSettings dataclass

Dataclass representing the beam settings for an imaging session.

Attributes:

Name Type Description
beam_type BeamType

The type of beam to use for imaging.

working_distance float

The working distance for the microscope, in meters.

beam_current float

The beam current for the microscope, in amps.

hfw float

The horizontal field width for the microscope, in meters.

resolution list

The desired resolution for the image.

dwell_time float

The dwell time for the microscope.

stigmation Point

The point for stigmation correction.

shift Point

The point for shift correction.

Methods:

Name Description
__to_dict__

Returns a dictionary representation of the object.

__from_dict__

dict) -> BeamSettings: Returns a new BeamSettings object created from a dictionary.

Source code in fibsem/structures.py
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
@dataclass
class BeamSettings:
    """
    Dataclass representing the beam settings for an imaging session.

    Attributes:
        beam_type (BeamType): The type of beam to use for imaging.
        working_distance (float): The working distance for the microscope, in meters.
        beam_current (float): The beam current for the microscope, in amps.
        hfw (float): The horizontal field width for the microscope, in meters.
        resolution (list): The desired resolution for the image.
        dwell_time (float): The dwell time for the microscope.
        stigmation (Point): The point for stigmation correction.
        shift (Point): The point for shift correction.

    Methods:
        __to_dict__(): Returns a dictionary representation of the object.
        __from_dict__(state_dict: dict) -> BeamSettings: Returns a new BeamSettings object created from a dictionary.

    """
    beam_type: BeamType
    working_distance: float = None
    beam_current: float = None
    voltage: float = None
    hfw: float = None
    resolution: list = None
    dwell_time: float = None
    stigmation: Point = None 
    shift: Point = None 
    scan_rotation: float = None


    def __post_init__(self):

        assert self.beam_type in [BeamType.ELECTRON,BeamType.ION] or self.beam_type is None, f"beam_type must be instance of BeamType, currently {type(self.beam_type)}"
        assert isinstance(self.working_distance,(float,int)) or self.working_distance is None, f"Working distance must be float or int, currently is {type(self.working_distance)}"
        assert isinstance(self.beam_current,(float,int)) or self.beam_current is None, f"beam current must be float or int, currently is {type(self.beam_current)}"
        assert isinstance(self.voltage,(float,int)) or self.voltage is None, f"voltage must be float or int, currently is {type(self.voltage)}"
        assert isinstance(self.hfw,(float,int)) or self.hfw is None, f"horizontal field width (HFW) must be float or int, currently is {type(self.hfw)}"
        assert isinstance(self.resolution,list) or self.resolution is None, f"resolution must be a list, currently is {type(self.resolution)}"
        assert isinstance(self.dwell_time,(float,int)) or self.dwell_time is None, f"dwell_time must be float or int, currently is {type(self.dwell_time)}"
        assert isinstance(self.stigmation,Point) or self.stigmation is None, f"stigmation must be a Point instance, currently is {type(self.stigmation)}"
        assert isinstance(self.shift,Point) or self.shift is None, f"shift must be a Point instance, currently is {type(self.shift)}"

    def __to_dict__(self) -> dict:

        state_dict = {
            "beam_type": self.beam_type.name,
            "working_distance": self.working_distance,
            "beam_current": self.beam_current,
            "voltage": self.voltage,
            "hfw": self.hfw,
            "resolution": self.resolution,
            "dwell_time": self.dwell_time,
            "stigmation": self.stigmation.__to_dict__() if self.stigmation is not None else None,
            "shift": self.shift.__to_dict__() if self.shift is not None else None,
            "scan_rotation": self.scan_rotation,
        }

        return state_dict

    @staticmethod
    def __from_dict__(state_dict: dict) -> "BeamSettings":

        if "stigmation" in state_dict and state_dict["stigmation"] is not None:
            stigmation = Point.__from_dict__(state_dict["stigmation"])
        else:
            stigmation = Point()
        if "shift" in state_dict and state_dict["shift"] is not None:
            shift = Point.__from_dict__(state_dict["shift"])
        else:
            shift = Point()


        beam_settings = BeamSettings(
            beam_type=BeamType[state_dict["beam_type"].upper()],
            working_distance=state_dict["working_distance"],
            beam_current=state_dict["beam_current"],
            voltage=state_dict["voltage"],
            hfw=state_dict["hfw"],
            resolution=state_dict["resolution"],
            dwell_time=state_dict["dwell_time"],
            stigmation=stigmation,
            shift=shift,
            scan_rotation=state_dict.get("scan_rotation", 0.0),
            )

        return beam_settings

BeamSystemSettings dataclass

A data class that represents the settings of a beam system.

Attributes:

Name Type Description
beam_type BeamType

The type of beam used in the system (Electron or Ion).

voltage float

The voltage used in the system.

current float

The current used in the system.

detector_type str

The type of detector used in the system.

detector_mode str

The mode of the detector used in the system.

eucentric_height float

The eucentric height of the system.

plasma_gas str

The type of plasma gas used in the system.

Methods:

Name Description
__to_dict__

Converts the instance variables to a dictionary.

__from_dict__

dict, beam_type: BeamType) -> BeamSystemSettings: Creates an instance of the class from a dictionary and a beam type.

Source code in fibsem/structures.py
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
@dataclass
class BeamSystemSettings:
    """
    A data class that represents the settings of a beam system.

    Attributes:
        beam_type (BeamType): The type of beam used in the system (Electron or Ion).
        voltage (float): The voltage used in the system.
        current (float): The current used in the system.
        detector_type (str): The type of detector used in the system.
        detector_mode (str): The mode of the detector used in the system.
        eucentric_height (float): The eucentric height of the system.
        plasma_gas (str, optional): The type of plasma gas used in the system.

    Methods:
        __to_dict__(self) -> dict:
            Converts the instance variables to a dictionary.
        __from_dict__(settings: dict, beam_type: BeamType) -> BeamSystemSettings:
            Creates an instance of the class from a dictionary and a beam type.
    """

    beam_type: BeamType
    voltage: float
    current: float
    detector_type: str
    detector_mode: str
    eucentric_height: float
    plasma_gas: str = None

    def __to_dict__(self) -> dict:

        settings_dict = {
            "voltage": self.voltage,
            "current": self.current,
            "detector_type": self.detector_type,
            "detector_mode": self.detector_mode,
            "eucentric_height": self.eucentric_height,
            "plasma_gas": self.plasma_gas,
        }

        return settings_dict

    @staticmethod
    def __from_dict__(settings: dict, beam_type: BeamType) -> "BeamSystemSettings":

        if "plasma_gas" not in settings:
            settings["plasma_gas"] = "NULL"

        system_settings = BeamSystemSettings(
            beam_type=beam_type,
            voltage=settings["voltage"],
            current=settings["current"],
            detector_type=settings["detector_type"],
            detector_mode=settings["detector_mode"],
            eucentric_height=settings["eucentric_height"],
            plasma_gas=settings["plasma_gas"].capitalize(),
        )

        return system_settings

BeamType

Bases: Enum

Enumerator Class for Beam Type 1: Electron Beam 2: Ion Beam

Source code in fibsem/structures.py
101
102
103
104
105
106
107
108
class BeamType(Enum):
    """Enumerator Class for Beam Type
        1: Electron Beam
        2: Ion Beam

    """
    ELECTRON = 1  # Electron
    ION = 2  # Ion

FibsemDetectorSettings dataclass

Source code in fibsem/structures.py
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
@dataclass
class FibsemDetectorSettings:
    type: str = None
    mode: str = None
    brightness: float = 0.5
    contrast: float = 0.5

    def __post_init__(self):

        assert isinstance(self.type,str) or self.type is None, f"type must be input as str, currently is {type(self.type)}"
        assert isinstance(self.mode,str) or self.mode is None, f"mode must be input as str, currently is {type(self.mode)}"
        assert isinstance(self.brightness,(float,int)) or self.brightness is None, f"brightness must be int or float value, currently is {type(self.brightness)}"
        assert isinstance(self.contrast,(float,int)) or self.contrast is None, f"contrast must be int or float value, currently is {type(self.contrast)}"

    if TESCAN:
        def to_tescan(self):
            """Converts to tescan format."""
            tescan_brightness = self.brightness * 100
            tescan_contrast = self.contrast * 100
            return tescan_brightness, tescan_contrast

    def __to_dict__(self) -> dict:
        """Converts to a dictionary."""
        return {
            "type": self.type,
            "mode": self.mode,
            "brightness": self.brightness,
            "contrast": self.contrast,
        }

    @staticmethod
    def __from_dict__(settings: dict) -> "FibsemDetectorSettings":
        """Converts from a dictionary."""
        return FibsemDetectorSettings(
            type = settings.get("type", "Unknown"),
            mode = settings.get("mode", "Unknown"),
            brightness = settings.get("brightness", 0.0),
            contrast = settings.get("contrast", 0.0),
        )

__from_dict__(settings) staticmethod

Converts from a dictionary.

Source code in fibsem/structures.py
786
787
788
789
790
791
792
793
794
@staticmethod
def __from_dict__(settings: dict) -> "FibsemDetectorSettings":
    """Converts from a dictionary."""
    return FibsemDetectorSettings(
        type = settings.get("type", "Unknown"),
        mode = settings.get("mode", "Unknown"),
        brightness = settings.get("brightness", 0.0),
        contrast = settings.get("contrast", 0.0),
    )

__to_dict__()

Converts to a dictionary.

Source code in fibsem/structures.py
777
778
779
780
781
782
783
784
def __to_dict__(self) -> dict:
    """Converts to a dictionary."""
    return {
        "type": self.type,
        "mode": self.mode,
        "brightness": self.brightness,
        "contrast": self.contrast,
    }

to_tescan()

Converts to tescan format.

Source code in fibsem/structures.py
771
772
773
774
775
def to_tescan(self):
    """Converts to tescan format."""
    tescan_brightness = self.brightness * 100
    tescan_contrast = self.contrast * 100
    return tescan_brightness, tescan_contrast

FibsemExperiment dataclass

Source code in fibsem/structures.py
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
@dataclass
class FibsemExperiment:
    id: str = None
    method: str = None
    date: float = datetime.timestamp(datetime.now())
    application: str = "OpenFIBSEM"
    fibsem_version: str = fibsem.__version__
    application_version: str = None


    def __to_dict__(self) -> dict:
        """Converts to a dictionary."""
        return {
            "id": self.id,
            "method": self.method,
            "date": self.date,
            "application": self.application,
            "fibsem_version": self.fibsem_version,
            "application_version": self.application_version,
        }

    @staticmethod
    def __from_dict__(settings: dict) -> "FibsemExperiment":
        """Converts from a dictionary."""
        return FibsemExperiment(
            id = settings.get("id", "Unknown"),
            method = settings.get("method", "Unknown"),
            date = settings.get("date", "Unknown"),
            application= settings.get("application", "OpenFIBSEM"),
            fibsem_version= settings.get("fibsem_version", fibsem.__version__),
            application_version= settings.get("application_version", None),
        )

__from_dict__(settings) staticmethod

Converts from a dictionary.

Source code in fibsem/structures.py
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
@staticmethod
def __from_dict__(settings: dict) -> "FibsemExperiment":
    """Converts from a dictionary."""
    return FibsemExperiment(
        id = settings.get("id", "Unknown"),
        method = settings.get("method", "Unknown"),
        date = settings.get("date", "Unknown"),
        application= settings.get("application", "OpenFIBSEM"),
        fibsem_version= settings.get("fibsem_version", fibsem.__version__),
        application_version= settings.get("application_version", None),
    )

__to_dict__()

Converts to a dictionary.

Source code in fibsem/structures.py
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
def __to_dict__(self) -> dict:
    """Converts to a dictionary."""
    return {
        "id": self.id,
        "method": self.method,
        "date": self.date,
        "application": self.application,
        "fibsem_version": self.fibsem_version,
        "application_version": self.application_version,
    }

FibsemHardware dataclass

Data class for storing hardware information. Attributes:

Source code in fibsem/structures.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
@dataclass
class FibsemHardware:
    """Data class for storing hardware information.
Attributes:

    """
    electron_beam: bool = True
    ion_beam: bool = True
    can_select_plasma_gas: bool = True
    stage_enabled: bool = True
    stage_rotation: bool = True
    stage_tilt: bool = True
    manipulator_enabled: bool = True
    manipulator_rotation: bool = True
    manipulator_tilt: bool = True
    gis_enabled: bool = True
    gis_multichem: bool = True
    manipulator_positions: dict = None

    def __post_init__(self):
        attributes = [
            "electron_beam",
            "ion_beam",
            "can_select_plasma_gas",
            "stage_enabled",
            "stage_rotation",
            "stage_tilt",
            "manipulator_enabled",
            "manipulator_rotation",
            "manipulator_tilt",
            "gis_enabled",
            "gis_multichem",
            "manipulator_positions",
        ]

        for attribute in attributes:
            object_attribute = getattr(self,attribute)
            if attribute in ["manipulator_positions"]:
                continue
            assert isinstance(object_attribute,bool)

    @classmethod
    def __from_dict__(cls, hardware_dict: dict) -> "FibsemHardware":

        return cls(
            electron_beam=bool(hardware_dict["electron"]["enabled"]),
            ion_beam=bool(hardware_dict["ion"]["enabled"]),
            can_select_plasma_gas=bool(hardware_dict["ion"]["can_select_plasma_gas"]),
            stage_enabled=bool(hardware_dict["stage"]["enabled"]),
            stage_rotation=bool(hardware_dict["stage"]["rotation"]),
            stage_tilt=bool(hardware_dict["stage"]["tilt"]),
            manipulator_enabled=bool(hardware_dict["manipulator"]["enabled"]),
            manipulator_rotation=bool(hardware_dict["manipulator"]["rotation"]),
            manipulator_tilt=bool(hardware_dict["manipulator"]["tilt"]),
            gis_enabled=bool(hardware_dict["gis"]["enabled"]),
            gis_multichem=bool(hardware_dict["gis"]["multichem"]),
            manipulator_positions={
                "parking":{"x":hardware_dict["manipulator"]["positions"]["parking"]["x"],
                           "y":hardware_dict["manipulator"]["positions"]["parking"]["y"],
                           "z":hardware_dict["manipulator"]["positions"]["parking"]["z"],
                },
                "standby":{ "x":hardware_dict["manipulator"]["positions"]["standby"]["x"],
                            "y":hardware_dict["manipulator"]["positions"]["standby"]["y"],
                            "z":hardware_dict["manipulator"]["positions"]["standby"]["z"],
                },
                "working":{ "x":hardware_dict["manipulator"]["positions"]["working"]["x"],
                            "y":hardware_dict["manipulator"]["positions"]["working"]["y"],
                            "z":hardware_dict["manipulator"]["positions"]["working"]["z"],
                },
                "calibrated":hardware_dict["manipulator"]["positions"]["calibrated"]}, 
        )

    def __to_dict__(self) -> dict:

            hardware_dict = {}

            hardware_dict["electron"] = {}
            hardware_dict["electron"]["enabled"] = self.electron_beam

            hardware_dict["ion"] = {}
            hardware_dict["ion"]["enabled"] = self.ion_beam
            hardware_dict["ion"]["can_select_plasma_gas"] = self.can_select_plasma_gas

            hardware_dict["stage"] = {}
            hardware_dict["stage"]["enabled"] = self.stage_enabled
            hardware_dict["stage"]["rotation"] = self.stage_rotation
            hardware_dict["stage"]["tilt"] = self.stage_tilt

            hardware_dict["manipulator"] = {}
            hardware_dict["manipulator"]["enabled"] = self.manipulator_enabled
            hardware_dict["manipulator"]["rotation"] = self.manipulator_rotation
            hardware_dict["manipulator"]["tilt"] = self.manipulator_tilt

            hardware_dict["gis"] = {}
            hardware_dict["gis"]["enabled"] = self.gis_enabled
            hardware_dict["gis"]["multichem"] = self.gis_multichem

            hardware_dict["manipulator"]["positions"] = {}

            hardware_dict["manipulator"]["positions"]["calibrated"] = self.manipulator_positions["calibrated"]

            hardware_dict["manipulator"]["positions"]["parking"] = {}
            hardware_dict["manipulator"]["positions"]["parking"]["x"] = self.manipulator_positions["parking"]["x"]
            hardware_dict["manipulator"]["positions"]["parking"]["y"] = self.manipulator_positions["parking"]["y"]
            hardware_dict["manipulator"]["positions"]["parking"]["z"] = self.manipulator_positions["parking"]["z"]

            hardware_dict["manipulator"]["positions"]["standby"] = {}
            hardware_dict["manipulator"]["positions"]["standby"]["x"] = self.manipulator_positions["standby"]["x"]
            hardware_dict["manipulator"]["positions"]["standby"]["y"] = self.manipulator_positions["standby"]["y"] 
            hardware_dict["manipulator"]["positions"]["standby"]["z"] = self.manipulator_positions["standby"]["z"]

            hardware_dict["manipulator"]["positions"]["working"] = {}
            hardware_dict["manipulator"]["positions"]["working"]["x"] = self.manipulator_positions["working"]["x"]
            hardware_dict["manipulator"]["positions"]["working"]["y"] = self.manipulator_positions["working"]["y"]
            hardware_dict["manipulator"]["positions"]["working"]["z"] = self.manipulator_positions["working"]["z"]


            return hardware_dict

FibsemImage

Class representing a FibsemImage and its associated metadata. Has in built methods to deal with image types of TESCAN and ThermoFisher API

Parameters:

Name Type Description Default
data ndarray

The image data stored in a numpy array.

required
metadata FibsemImageMetadata

The metadata associated with the image. Defaults to None.

None

Methods:

Name Description
load

str) -> "FibsemImage": Loads a FibsemImage from a tiff file.

Args: tiff_path (path): path to the tif* file

Returns: FibsemImage: instance of FibsemImage

save

Path) -> None: Saves a FibsemImage to a tiff file.

Inputs: save_path (path): path to save directory and filename

Source code in fibsem/structures.py
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
class FibsemImage:

    """
    Class representing a FibsemImage and its associated metadata. 
    Has in built methods to deal with image types of TESCAN and ThermoFisher API 

    Args:
        data (np.ndarray): The image data stored in a numpy array.
        metadata (FibsemImageMetadata, optional): The metadata associated with the image. Defaults to None.

    Methods:
        load(cls, tiff_path: str) -> "FibsemImage":
            Loads a FibsemImage from a tiff file.

            Args:
                tiff_path (path): path to the tif* file

            Returns:
                FibsemImage: instance of FibsemImage

        save(self, save_path: Path) -> None:
            Saves a FibsemImage to a tiff file.

            Inputs:
                save_path (path): path to save directory and filename
    """

    def __init__(self, data: np.ndarray, metadata: FibsemImageMetadata = None):

        if check_data_format(data):
            if data.ndim == 3 and data.shape[2] == 1:
                data = data[:, :, 0]
            self.data = data
        else:
            raise Exception("Invalid Data format for Fibsem Image")
        if metadata is not None:
            self.metadata = metadata
        else:
            self.metadata = None

    @classmethod
    def load(cls, tiff_path: str) -> "FibsemImage":
        """Loads a FibsemImage from a tiff file.

        Args:
            tiff_path (path): path to the tif* file

        Returns:
            FibsemImage: instance of FibsemImage
        """
        with tff.TiffFile(tiff_path) as tiff_image:
            data = tiff_image.asarray()
            try:
                metadata = json.loads(
                    tiff_image.pages[0].tags["ImageDescription"].value
                )
                metadata = FibsemImageMetadata.__from_dict__(metadata)
            except Exception as e:
                metadata = None
                print(f"Error: {e}")
                import traceback
                traceback.print_exc()
        return cls(data=data, metadata=metadata)

    def save(self, save_path: Path = None) -> None:
        """Saves a FibsemImage to a tiff file.

        Inputs:
            save_path (path): path to save directory and filename
        """
        if save_path is None:
            save_path = os.path.join(self.metadata.image_settings.save_path, self.metadata.image_settings.label)
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        save_path = Path(save_path).with_suffix(".tif")

        if self.metadata is not None:
            metadata_dict = self.metadata.__to_dict__()
        else:
            metadata_dict = None
        tff.imwrite(
            save_path,
            self.data,
            metadata=metadata_dict,
        )

    if THERMO:

        @classmethod
        def fromAdornedImage(
            cls,
            adorned: AdornedImage,
            image_settings: ImageSettings,
            state: MicroscopeState = None,
            detector: FibsemDetectorSettings = None,
        ) -> "FibsemImage":
            """Creates FibsemImage from an AdornedImage (microscope output format).

            Args:
                adorned (AdornedImage): Adorned Image from microscope
                metadata (FibsemImageMetadata, optional): metadata extracted from microscope output. Defaults to None.

            Returns:
                FibsemImage: instance of FibsemImage from AdornedImage
            """
            if state is None:
                state = MicroscopeState(
                    timestamp=adorned.metadata.acquisition.acquisition_datetime,
                    absolute_position=FibsemStagePosition(
                        adorned.metadata.stage_settings.stage_position.x,
                        adorned.metadata.stage_settings.stage_position.y,
                        adorned.metadata.stage_settings.stage_position.z,
                        adorned.metadata.stage_settings.stage_position.r,
                        adorned.metadata.stage_settings.stage_position.t,
                    ),
                    eb_settings=BeamSettings(beam_type=BeamType.ELECTRON),
                    ib_settings=BeamSettings(beam_type=BeamType.ION),
                )
            else:
                state.timestamp = adorned.metadata.acquisition.acquisition_datetime

            pixel_size = Point(
                adorned.metadata.binary_result.pixel_size.x,
                adorned.metadata.binary_result.pixel_size.y,
            )

            metadata = FibsemImageMetadata(
                image_settings=image_settings,
                pixel_size=pixel_size,
                microscope_state=state,
                detector_settings= detector, 
            )
            return cls(data=adorned.data, metadata=metadata)

    if TESCAN:

        @classmethod
        def fromTescanImage(
            cls,
            image: Document,
            image_settings: ImageSettings,
            state: MicroscopeState,
            detector: FibsemDetectorSettings,
        ) -> "FibsemImage":
            """Creates FibsemImage from a tescan (microscope output format).

            Args:
                image (Tescan): Adorned Image from microscope
                metadata (FibsemImageMetadata, optional): metadata extracted from microscope output. Defaults to None.

            Returns:
                FibsemImage: instance of FibsemImage from AdornedImage
            """

            pixel_size = Point(
                float(image.Header["MAIN"]["PixelSizeX"]),
                float(image.Header["MAIN"]["PixelSizeY"]),
            )
            metadata = FibsemImageMetadata(
                image_settings=image_settings,
                pixel_size=pixel_size,
                microscope_state=state,
                detector_settings= detector,
            )

            return cls(data=np.array(image.Image), metadata=metadata)

        @classmethod
        def fromTescanFile(
            cls,
            image_path: str,
            metadata_path: str,
            beam_type: BeamType,
        ) -> "FibsemImage":

            with tff.TiffFile(image_path) as tiff_image:
                data = tiff_image.asarray()

            stage = 0
            dictionary = {"MAIN": {}, "SEM": {}, "FIB": {}}
            with open(metadata_path, "r") as file:
                for line in file:
                    if line.startswith('['):
                        stage +=1 
                        continue 

                    line = line.strip()
                    if not line:
                        continue  # Skip empty lines

                    key, value = line.split('=')
                    key = key.strip()
                    value = value.strip()
                    if stage == 1:
                        dictionary["MAIN"][key] = value
                    if stage == 2 and beam_type.name == "ELECTRON":
                        dictionary["SEM"][key] = value
                    if stage == 2 and beam_type.name == "ION":
                        dictionary["FIB"][key] = value

            if beam_type.name == "ELECTRON":  
                image_settings = ImageSettings(
                    resolution = [data.shape[0], data.shape[1]],
                    dwell_time= float(dictionary["SEM"]["DwellTime"]),
                    hfw= data.shape[0]*float(dictionary["MAIN"]["PixelSizeX"]),
                    beam_type=BeamType.ELECTRON,
                    label=Path(image_path).stem,
                    save_path=Path(image_path).parent,
                )
                pixel_size = Point(float(dictionary["MAIN"]["PixelSizeX"]), float(dictionary["MAIN"]["PixelSizeY"]))
                microscope_state = MicroscopeState(
                    timestamp= datetime.strptime(dictionary["MAIN"]["Date"] + " " + dictionary["MAIN"]["Time"], "%Y-%m-%d %H:%M:%S"),
                    eb_settings=BeamSettings(
                        beam_type=BeamType.ELECTRON,
                        working_distance= float(dictionary["SEM"]["WD"]),
                        beam_current= float(dictionary["SEM"]["PredictedBeamCurrent"]),
                        voltage= float(dictionary["SEM"]["TubeVoltage"]),
                        hfw= data.shape[0]*float(dictionary["MAIN"]["PixelSizeX"]),
                        resolution= [data.shape[0], data.shape[1]],
                        dwell_time= float(dictionary["SEM"]["DwellTime"]),
                        shift = Point(float(dictionary["SEM"]["ImageShiftX"]), float(dictionary["SEM"]["ImageShiftY"])),
                        stigmation= Point(float(dictionary["SEM"]["StigmatorX"]), float(dictionary["SEM"]["StigmatorY"])),
                        ),
                    ib_settings = BeamSettings(beam_type = BeamType.ION)
                )
                detector_settings = FibsemDetectorSettings(
                    type = dictionary["SEM"]["Detector"],
                    brightness= float(dictionary["SEM"]["Detector0Offset"]),
                    contrast= float(dictionary["SEM"]["Detector0Gain"]),
                )

            if beam_type.name == "ION":
                image_settings = ImageSettings(
                    resolution = [data.shape[0], data.shape[1]],
                    dwell_time= float(dictionary["FIB"]["DwellTime"]),
                    hfw= data.shape[0]*float(dictionary["MAIN"]["PixelSizeX"]),
                    beam_type=BeamType.ELECTRON,
                    label=Path(image_path).stem,
                    save_path=Path(image_path).parent,
                )
                pixel_size = Point(float(dictionary["MAIN"]["PixelSizeX"]), float(dictionary["MAIN"]["PixelSizeY"]))
                microscope_state = MicroscopeState(
                    timestamp= datetime.strptime(dictionary["MAIN"]["Date"] + " " + dictionary["MAIN"]["Time"], "%Y-%m-%d %H:%M:%S"),
                    eb_settings = BeamSettings(beam_type = BeamType.ELECTRON),
                    ib_settings = BeamSettings(
                        beam_type=BeamType.ION,
                        working_distance= float(dictionary["FIB"]["WD"]),
                        beam_current= float(dictionary["FIB"]["PredictedBeamCurrent"]),
                        hfw= data.shape[0]*float(dictionary["MAIN"]["PixelSizeX"]),
                        resolution= [data.shape[0], data.shape[1]],
                        dwell_time= float(dictionary["FIB"]["DwellTime"]),
                        shift = Point(float(dictionary["FIB"]["ImageShiftX"]), float(dictionary["FIB"]["ImageShiftY"])),
                        stigmation= Point(float(dictionary["FIB"]["StigmatorX"]), float(dictionary["FIB"]["StigmatorY"])),
                        ),
                )
                detector_settings = FibsemDetectorSettings(
                    type = dictionary["FIB"]["Detector"],
                    brightness= float(dictionary["FIB"]["Detector0Offset"])/100,
                    contrast= float(dictionary["FIB"]["Detector0Gain"])/100,
                )

            metadata = FibsemImageMetadata(
                image_settings=image_settings,
                pixel_size=pixel_size,
                microscope_state=microscope_state,
                detector_settings= detector_settings,
                version=METADATA_VERSION,
            )
            return cls(data=data, metadata=metadata)

fromAdornedImage(adorned, image_settings, state=None, detector=None) classmethod

Creates FibsemImage from an AdornedImage (microscope output format).

Parameters:

Name Type Description Default
adorned AdornedImage

Adorned Image from microscope

required
metadata FibsemImageMetadata

metadata extracted from microscope output. Defaults to None.

required

Returns:

Name Type Description
FibsemImage FibsemImage

instance of FibsemImage from AdornedImage

Source code in fibsem/structures.py
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
@classmethod
def fromAdornedImage(
    cls,
    adorned: AdornedImage,
    image_settings: ImageSettings,
    state: MicroscopeState = None,
    detector: FibsemDetectorSettings = None,
) -> "FibsemImage":
    """Creates FibsemImage from an AdornedImage (microscope output format).

    Args:
        adorned (AdornedImage): Adorned Image from microscope
        metadata (FibsemImageMetadata, optional): metadata extracted from microscope output. Defaults to None.

    Returns:
        FibsemImage: instance of FibsemImage from AdornedImage
    """
    if state is None:
        state = MicroscopeState(
            timestamp=adorned.metadata.acquisition.acquisition_datetime,
            absolute_position=FibsemStagePosition(
                adorned.metadata.stage_settings.stage_position.x,
                adorned.metadata.stage_settings.stage_position.y,
                adorned.metadata.stage_settings.stage_position.z,
                adorned.metadata.stage_settings.stage_position.r,
                adorned.metadata.stage_settings.stage_position.t,
            ),
            eb_settings=BeamSettings(beam_type=BeamType.ELECTRON),
            ib_settings=BeamSettings(beam_type=BeamType.ION),
        )
    else:
        state.timestamp = adorned.metadata.acquisition.acquisition_datetime

    pixel_size = Point(
        adorned.metadata.binary_result.pixel_size.x,
        adorned.metadata.binary_result.pixel_size.y,
    )

    metadata = FibsemImageMetadata(
        image_settings=image_settings,
        pixel_size=pixel_size,
        microscope_state=state,
        detector_settings= detector, 
    )
    return cls(data=adorned.data, metadata=metadata)

fromTescanImage(image, image_settings, state, detector) classmethod

Creates FibsemImage from a tescan (microscope output format).

Parameters:

Name Type Description Default
image Tescan

Adorned Image from microscope

required
metadata FibsemImageMetadata

metadata extracted from microscope output. Defaults to None.

required

Returns:

Name Type Description
FibsemImage FibsemImage

instance of FibsemImage from AdornedImage

Source code in fibsem/structures.py
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
@classmethod
def fromTescanImage(
    cls,
    image: Document,
    image_settings: ImageSettings,
    state: MicroscopeState,
    detector: FibsemDetectorSettings,
) -> "FibsemImage":
    """Creates FibsemImage from a tescan (microscope output format).

    Args:
        image (Tescan): Adorned Image from microscope
        metadata (FibsemImageMetadata, optional): metadata extracted from microscope output. Defaults to None.

    Returns:
        FibsemImage: instance of FibsemImage from AdornedImage
    """

    pixel_size = Point(
        float(image.Header["MAIN"]["PixelSizeX"]),
        float(image.Header["MAIN"]["PixelSizeY"]),
    )
    metadata = FibsemImageMetadata(
        image_settings=image_settings,
        pixel_size=pixel_size,
        microscope_state=state,
        detector_settings= detector,
    )

    return cls(data=np.array(image.Image), metadata=metadata)

load(tiff_path) classmethod

Loads a FibsemImage from a tiff file.

Parameters:

Name Type Description Default
tiff_path path

path to the tif* file

required

Returns:

Name Type Description
FibsemImage FibsemImage

instance of FibsemImage

Source code in fibsem/structures.py
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
@classmethod
def load(cls, tiff_path: str) -> "FibsemImage":
    """Loads a FibsemImage from a tiff file.

    Args:
        tiff_path (path): path to the tif* file

    Returns:
        FibsemImage: instance of FibsemImage
    """
    with tff.TiffFile(tiff_path) as tiff_image:
        data = tiff_image.asarray()
        try:
            metadata = json.loads(
                tiff_image.pages[0].tags["ImageDescription"].value
            )
            metadata = FibsemImageMetadata.__from_dict__(metadata)
        except Exception as e:
            metadata = None
            print(f"Error: {e}")
            import traceback
            traceback.print_exc()
    return cls(data=data, metadata=metadata)

save(save_path=None)

Saves a FibsemImage to a tiff file.

Inputs

save_path (path): path to save directory and filename

Source code in fibsem/structures.py
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
def save(self, save_path: Path = None) -> None:
    """Saves a FibsemImage to a tiff file.

    Inputs:
        save_path (path): path to save directory and filename
    """
    if save_path is None:
        save_path = os.path.join(self.metadata.image_settings.save_path, self.metadata.image_settings.label)
    os.makedirs(os.path.dirname(save_path), exist_ok=True)
    save_path = Path(save_path).with_suffix(".tif")

    if self.metadata is not None:
        metadata_dict = self.metadata.__to_dict__()
    else:
        metadata_dict = None
    tff.imwrite(
        save_path,
        self.data,
        metadata=metadata_dict,
    )

FibsemImageMetadata dataclass

Metadata for a FibsemImage.

Source code in fibsem/structures.py
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
@dataclass
class FibsemImageMetadata:
    """Metadata for a FibsemImage."""

    image_settings: ImageSettings
    pixel_size: Point
    microscope_state: MicroscopeState
    detector_settings: FibsemDetectorSettings
    version: str = METADATA_VERSION
    user: FibsemUser = FibsemUser()
    experiment: FibsemExperiment = FibsemExperiment()
    system: FibsemSystem = FibsemSystem()



    def __to_dict__(self) -> dict:
        """Converts metadata to a dictionary.

        Returns:
            dictionary: self as a dictionary
        """
        if self.image_settings is not None:
            settings_dict = self.image_settings.__to_dict__() # TODO: gracefully depreceate this
            settings_dict["image"] = self.image_settings.__to_dict__()
        if self.version is not None:
            settings_dict["version"] = self.version
        if self.pixel_size is not None:
            settings_dict["pixel_size"] = self.pixel_size.__to_dict__()
        if self.microscope_state is not None:
            settings_dict["microscope_state"] = self.microscope_state.__to_dict__()
        if self.detector_settings is not None:
            settings_dict["detector_settings"] = self.detector_settings.__to_dict__()
        settings_dict["user"] = self.user.__to_dict__()
        settings_dict["experiment"] = self.experiment.__to_dict__()
        settings_dict["system"] = self.system.__to_dict__()

        return settings_dict

    @staticmethod
    def __from_dict__(settings: dict) -> "ImageSettings":
        """Converts a dictionary to metadata."""

        image_settings = ImageSettings.__from_dict__(settings)
        if settings["version"] is not None:
            version = settings["version"]
        if settings["pixel_size"] is not None:
            pixel_size = Point.__from_dict__(settings["pixel_size"])
        if settings["microscope_state"] is not None:
            microscope_state = MicroscopeState.__from_dict__(settings["microscope_state"])

        detector_dict = settings.get("detector_settings", {"type": "Unknown", "mode": "Unknown", "brightness": 0.0, "contrast": 0.0})
        detector_settings = FibsemDetectorSettings.__from_dict__(detector_dict)

        metadata = FibsemImageMetadata(
            image_settings=image_settings,
            version=version,
            pixel_size=pixel_size,
            microscope_state=microscope_state,
            detector_settings=detector_settings,
            user = FibsemUser.__from_dict__(settings.get("user", {})),
            experiment = FibsemExperiment.__from_dict__(settings.get("experiment", {})),
            system = FibsemSystem.__from_dict__(settings.get("system", {})),
        )
        return metadata

    if THERMO:

        def image_settings_from_adorned(
            image=AdornedImage, beam_type: BeamType = BeamType.ELECTRON
        ) -> ImageSettings:

            from fibsem.utils import current_timestamp

            image_settings = ImageSettings(
                resolution=[image.width, image.height],
                dwell_time=image.metadata.scan_settings.dwell_time,
                hfw=image.width * image.metadata.binary_result.pixel_size.x,
                autocontrast=True,
                beam_type=beam_type,
                gamma_enabled=True,
                save=False,
                save_path="path",
                label=current_timestamp(),
                reduced_area=None,
            )
            return image_settings

    def compare_image_settings(self, image_settings: ImageSettings) -> bool:
        """Compares image settings to the metadata image settings.

        Args:
            image_settings (ImageSettings): Image settings to compare to.

        Returns:
            bool: True if the image settings match the metadata image settings.
        """
        assert (
            self.image_settings.resolution[0] == image_settings.resolution[0] and self.image_settings.resolution[1] == image_settings.resolution[1]
        ), f"resolution: {self.image_settings.resolution} != {image_settings.resolution}"
        assert (
            self.image_settings.dwell_time == image_settings.dwell_time
        ), f"dwell_time: {self.image_settings.dwell_time} != {image_settings.dwell_time}"
        assert (
            self.image_settings.hfw == image_settings.hfw
        ), f"hfw: {self.image_settings.hfw} != {image_settings.hfw}"
        assert (
            self.image_settings.autocontrast == image_settings.autocontrast
        ), f"autocontrast: {self.image_settings.autocontrast} != {image_settings.autocontrast}"
        assert (
            self.image_settings.beam_type.value == image_settings.beam_type.value
        ), f"beam_type: {self.image_settings.beam_type.value} != {image_settings.beam_type.value}"
        assert (
            self.image_settings.gamma_enabled== image_settings.gamma_enabled
        ), f"gamma: {self.image_settings.gamma_enabled} != {image_settings.gamma_enabled}"
        assert (
            self.image_settings.save == image_settings.save
        ), f"save: {self.image_settings.save} != {image_settings.save}"
        assert (
            self.image_settings.save_path == image_settings.save_path
        ), f"save_path: {self.image_settings.save_path} != {image_settings.save_path}"
        assert (
            self.image_settings.label == image_settings.label
        ), f"label: {self.image_settings.label} != {image_settings.label}"
        assert (
            self.image_settings.reduced_area == image_settings.reduced_area
        ), f"reduced_area: {self.image_settings.reduced_area} != {image_settings.reduced_area}"

        return True

__from_dict__(settings) staticmethod

Converts a dictionary to metadata.

Source code in fibsem/structures.py
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
@staticmethod
def __from_dict__(settings: dict) -> "ImageSettings":
    """Converts a dictionary to metadata."""

    image_settings = ImageSettings.__from_dict__(settings)
    if settings["version"] is not None:
        version = settings["version"]
    if settings["pixel_size"] is not None:
        pixel_size = Point.__from_dict__(settings["pixel_size"])
    if settings["microscope_state"] is not None:
        microscope_state = MicroscopeState.__from_dict__(settings["microscope_state"])

    detector_dict = settings.get("detector_settings", {"type": "Unknown", "mode": "Unknown", "brightness": 0.0, "contrast": 0.0})
    detector_settings = FibsemDetectorSettings.__from_dict__(detector_dict)

    metadata = FibsemImageMetadata(
        image_settings=image_settings,
        version=version,
        pixel_size=pixel_size,
        microscope_state=microscope_state,
        detector_settings=detector_settings,
        user = FibsemUser.__from_dict__(settings.get("user", {})),
        experiment = FibsemExperiment.__from_dict__(settings.get("experiment", {})),
        system = FibsemSystem.__from_dict__(settings.get("system", {})),
    )
    return metadata

__to_dict__()

Converts metadata to a dictionary.

Returns:

Name Type Description
dictionary dict

self as a dictionary

Source code in fibsem/structures.py
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
def __to_dict__(self) -> dict:
    """Converts metadata to a dictionary.

    Returns:
        dictionary: self as a dictionary
    """
    if self.image_settings is not None:
        settings_dict = self.image_settings.__to_dict__() # TODO: gracefully depreceate this
        settings_dict["image"] = self.image_settings.__to_dict__()
    if self.version is not None:
        settings_dict["version"] = self.version
    if self.pixel_size is not None:
        settings_dict["pixel_size"] = self.pixel_size.__to_dict__()
    if self.microscope_state is not None:
        settings_dict["microscope_state"] = self.microscope_state.__to_dict__()
    if self.detector_settings is not None:
        settings_dict["detector_settings"] = self.detector_settings.__to_dict__()
    settings_dict["user"] = self.user.__to_dict__()
    settings_dict["experiment"] = self.experiment.__to_dict__()
    settings_dict["system"] = self.system.__to_dict__()

    return settings_dict

compare_image_settings(image_settings)

Compares image settings to the metadata image settings.

Parameters:

Name Type Description Default
image_settings ImageSettings

Image settings to compare to.

required

Returns:

Name Type Description
bool bool

True if the image settings match the metadata image settings.

Source code in fibsem/structures.py
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
def compare_image_settings(self, image_settings: ImageSettings) -> bool:
    """Compares image settings to the metadata image settings.

    Args:
        image_settings (ImageSettings): Image settings to compare to.

    Returns:
        bool: True if the image settings match the metadata image settings.
    """
    assert (
        self.image_settings.resolution[0] == image_settings.resolution[0] and self.image_settings.resolution[1] == image_settings.resolution[1]
    ), f"resolution: {self.image_settings.resolution} != {image_settings.resolution}"
    assert (
        self.image_settings.dwell_time == image_settings.dwell_time
    ), f"dwell_time: {self.image_settings.dwell_time} != {image_settings.dwell_time}"
    assert (
        self.image_settings.hfw == image_settings.hfw
    ), f"hfw: {self.image_settings.hfw} != {image_settings.hfw}"
    assert (
        self.image_settings.autocontrast == image_settings.autocontrast
    ), f"autocontrast: {self.image_settings.autocontrast} != {image_settings.autocontrast}"
    assert (
        self.image_settings.beam_type.value == image_settings.beam_type.value
    ), f"beam_type: {self.image_settings.beam_type.value} != {image_settings.beam_type.value}"
    assert (
        self.image_settings.gamma_enabled== image_settings.gamma_enabled
    ), f"gamma: {self.image_settings.gamma_enabled} != {image_settings.gamma_enabled}"
    assert (
        self.image_settings.save == image_settings.save
    ), f"save: {self.image_settings.save} != {image_settings.save}"
    assert (
        self.image_settings.save_path == image_settings.save_path
    ), f"save_path: {self.image_settings.save_path} != {image_settings.save_path}"
    assert (
        self.image_settings.label == image_settings.label
    ), f"label: {self.image_settings.label} != {image_settings.label}"
    assert (
        self.image_settings.reduced_area == image_settings.reduced_area
    ), f"reduced_area: {self.image_settings.reduced_area} != {image_settings.reduced_area}"

    return True

FibsemManipulatorPosition dataclass

Data class for storing manipulator position data.

Attributes:

Name Type Description
x float

The X position of the manipulator in meters.

y float

The Y position of the manipulator in meters.

z float

The Z position of the manipulator in meters.

r float

The Rotation of the manipulator in radians.

t float

The Tilt of the manipulator in radians.

coordinate_system str

The coordinate system used for the manipulator position.

Methods:

Name Description
__to_dict__

Convert the manipulator position object to a dictionary.

__from_dict__

dict): Create a new manipulator position object from a dictionary.

to_autoscript_position

Convert the manipulator position to a ManipulatorPosition object that is compatible with Autoscript.

from_autoscript_position

ManipulatorPosition) -> None: Create a new FibsemManipulatorPosition object from a ManipulatorPosition object that is compatible with Autoscript.

to_tescan_position

Convert the manipulator position to a format that is compatible with Tescan.

from_tescan_position

Create a new FibsemManipulatorPosition object from a Tescan-compatible manipulator position.

Source code in fibsem/structures.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
@dataclass
class FibsemManipulatorPosition:
    """Data class for storing manipulator position data.

Attributes:
    x (float): The X position of the manipulator in meters.
    y (float): The Y position of the manipulator in meters.
    z (float): The Z position of the manipulator in meters.
    r (float): The Rotation of the manipulator in radians.
    t (float): The Tilt of the manipulator in radians.
    coordinate_system (str): The coordinate system used for the manipulator position.

Methods:
    __to_dict__(): Convert the manipulator position object to a dictionary.
    __from_dict__(data: dict): Create a new manipulator position object from a dictionary.
    to_autoscript_position() -> ManipulatorPosition: Convert the manipulator position to a ManipulatorPosition object that is compatible with Autoscript.
    from_autoscript_position(position: ManipulatorPosition) -> None: Create a new FibsemManipulatorPosition object from a ManipulatorPosition object that is compatible with Autoscript.
    to_tescan_position(): Convert the manipulator position to a format that is compatible with Tescan.
    from_tescan_position(): Create a new FibsemManipulatorPosition object from a Tescan-compatible manipulator position.

"""

    x: float = 0.0
    y: float = 0.0
    z: float = 0.0
    r: float = 0.0
    t: float = 0.0
    coordinate_system: str = "RAW"

    def __post_init__(self):

        # attributes = ["x","y","z","r","t"]
        # for attribute in attributes:
            # output = getattr(self,attribute)
            # assert isinstance(output,float) or isinstance(output,int), f"Unsupported type {type(output)} for coordinate {attribute}"
        assert isinstance(self.coordinate_system,str) or self.coordinate_system is None, f"unsupported type {type(self.coordinate_system)} for coorindate system"
        assert self.coordinate_system in SUPPORTED_COORDINATE_SYSTEMS or self.coordinate_system is None, f"coordinate system value {self.coordinate_system} is unsupported or invalid syntax. Must be RAW or SPECIMEN"

    def __to_dict__(self) -> dict:
        position_dict = {}
        position_dict["x"] = self.x
        position_dict["y"] = self.y
        position_dict["z"] = self.z
        position_dict["r"] = self.r
        position_dict["t"] = self.t
        position_dict["coordinate_system"] = self.coordinate_system.upper()

        return position_dict

    @classmethod
    def __from_dict__(cls, data: dict) -> "FibsemManipulatorPosition":

        items = ["x","y","z","r","t"]

        for item in items:

            value = data[item]

            assert isinstance(value,float) or isinstance(value,int) or value is None


        return cls(
            x=data["x"],
            y=data["y"],
            z=data["z"],
            r=data["r"],
            t=data["t"],
            coordinate_system=data["coordinate_system"],
        )

    if THERMO:

            def to_autoscript_position(self) -> ManipulatorPosition:
                if self.coordinate_system == "RAW":
                    coordinate_system = "Raw"
                elif self.coordinate_system == "STAGE":
                    coordinate_system = "Stage"
                return ManipulatorPosition(
                    x=self.x,
                    y=self.y,
                    z=self.z,
                    r=None, # TODO figure this out, do we need it for real micrscope or just simulator ? 
                    #r=None,
                    coordinate_system=coordinate_system,
                )

            @classmethod
            def from_autoscript_position(cls, position: ManipulatorPosition) -> None:
                return cls(
                    x=position.x,
                    y=position.y,
                    z=position.z,
                    coordinate_system=position.coordinate_system.upper(),
                )


    if TESCAN:

            def to_tescan_position(self):
                pass

            @classmethod
            def from_tescan_position(self):
                pass

    def __add__(self, other:'FibsemManipulatorPosition') -> 'FibsemManipulatorPosition':

        return FibsemManipulatorPosition(
            self.x + other.x,
            self.y + other.y,
            self.z + other.z,
            self.r + other.r,
            self.t + other.t,
            self.coordinate_system,
        )

FibsemMillingSettings dataclass

This class is used to store and retrieve settings for FIBSEM milling.

Attributes: milling_current (float): The current used in the FIBSEM milling process. Default value is 20.0e-12 A. spot_size (float): The size of the beam spot used in the FIBSEM milling process. Default value is 5.0e-8 m. rate (float): The milling rate of the FIBSEM process. Default value is 3.0e-3 m^3/A/s. dwell_time (float): The dwell time of the beam at each point during the FIBSEM milling process. Default value is 1.0e-6 s. hfw (float): The high voltage field width used in the FIBSEM milling process. Default value is 150e-6 m.

Methods: to_dict(): Converts the object attributes into a dictionary. from_dict(settings: dict) -> "FibsemMillingSettings": Creates a FibsemMillingSettings object from a dictionary of settings.

Source code in fibsem/structures.py
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
@dataclass
class FibsemMillingSettings:
    """
    This class is used to store and retrieve settings for FIBSEM milling.

    Attributes:
    milling_current (float): The current used in the FIBSEM milling process. Default value is 20.0e-12 A.
    spot_size (float): The size of the beam spot used in the FIBSEM milling process. Default value is 5.0e-8 m.
    rate (float): The milling rate of the FIBSEM process. Default value is 3.0e-3 m^3/A/s.
    dwell_time (float): The dwell time of the beam at each point during the FIBSEM milling process. Default value is 1.0e-6 s.
    hfw (float): The high voltage field width used in the FIBSEM milling process. Default value is 150e-6 m.

    Methods:
    to_dict(): Converts the object attributes into a dictionary.
    from_dict(settings: dict) -> "FibsemMillingSettings": Creates a FibsemMillingSettings object from a dictionary of settings.
    """

    milling_current: float = 20.0e-12
    spot_size: float = 5.0e-8
    rate: float = 3.0e-11 # m3/A/s
    dwell_time: float = 1.0e-6 # s
    hfw: float = 150e-6
    patterning_mode: str = "Serial" 
    application_file: str = "Si"
    preset: str = "30 keV; UHR imaging"
    spacing: float = 1.0

    def __post_init__(self):

        assert isinstance(self.milling_current,(float,int)), f"invalid type for milling_current, must be int or float, currently {type(self.milling_current)}"
        assert isinstance(self.spot_size,(float,int)), f"invalid type for spot_size, must be int or float, currently {type(self.spot_size)}"
        assert isinstance(self.rate,(float,int)), f"invalid type for rate, must be int or float, currently {type(self.rate)}"
        assert isinstance(self.dwell_time,(float,int)), f"invalid type for dwell_time, must be int or float, currently {type(self.dwell_time)}"
        assert isinstance(self.hfw,(float,int)), f"invalid type for hfw, must be int or float, currently {type(self.hfw)}"
        assert isinstance(self.patterning_mode,str), f"invalid type for value for patterning_mode, must be str, currently {type(self.patterning_mode)}"
        assert isinstance(self.application_file,(str)), f"invalid type for value for application_file, must be str, currently {type(self.application_file)}"
        assert isinstance(self.spacing,(float,int)), f"invalid type for value for spacing, must be int or float, currently {type(self.spacing)}"
        # assert isinstance(self.preset,(str)), f"invalid type for value for preset, must be str, currently {type(self.preset)}"

    def __to_dict__(self) -> dict:

        settings_dict = {
            "milling_current": self.milling_current,
            "spot_size": self.spot_size,
            "rate": self.rate,
            "dwell_time": self.dwell_time,
            "hfw": self.hfw,
            "patterning_mode": self.patterning_mode,
            "application_file": self.application_file,
            "preset": self.preset,
        }

        return settings_dict

    @staticmethod
    def __from_dict__(settings: dict) -> "FibsemMillingSettings":

        milling_settings = FibsemMillingSettings(
            milling_current=settings.get("milling_current", 20.0e-12),
            spot_size=settings.get("spot_size", 5.0e-8),
            rate=settings.get("rate", 3.0e-11),
            dwell_time=settings.get("dwell_time", 1.0e-6),
            hfw=settings.get("hfw", 150e-6),
            patterning_mode=settings.get("patterning_mode", "Serial"),
            application_file=settings.get("application_file", "Si"),
            preset=settings.get("preset", "30 keV; UHR imaging"),
            spacing=settings.get("spacing", 1.0),
        )

        return milling_settings

FibsemPatternSettings

FibsemPatternSettings is used to store all of the possible settings related to each pattern that may be drawn.

Parameters:

Name Type Description Default
pattern FibsemPattern

Used to indicate which pattern is utilised. Currently either Rectangle or Line.

Rectangle
**kwargs

If FibsemPattern.Rectangle width: float (m), height: float (m), depth: float (m), rotation: float = 0.0 (m), centre_x: float = 0.0 (m), centre_y: float = 0.0 (m), passes: float = 1.0,

If FibsemPattern.Line
    start_x: float (m), 
    start_y: float (m), 
    end_x: float (m), 
    end_y: float (m), 
    depth: float (m),

If FibsemPattern.Circle
    centre_x: float (m),
    centre_y: float (m),
    radius: float (m),
    depth: float (m),
    start_angle: float = 0.0 (degrees),
    end_angle: float = 360.0 (degrees),

if FibsemPattern.Bitmap
    centre_x: float (m),
    centre_y: float (m),
    width: float (m),
    height: float (m),
    rotation: float = 0.0 (degrees),
    depth: float (m),
    path: str = path to image,
{}
Source code in fibsem/structures.py
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
class FibsemPatternSettings:  # FibsemBasePattern
    '''
    FibsemPatternSettings is used to store all of the possible settings related to each pattern that may be drawn.

    Args:
        pattern (FibsemPattern): Used to indicate which pattern is utilised. Currently either Rectangle or Line.
        **kwargs: If FibsemPattern.Rectangle
                    width: float (m),
                    height: float (m), 
                    depth: float (m),
                    rotation: float = 0.0 (m), 
                    centre_x: float = 0.0 (m), 
                    centre_y: float = 0.0 (m),
                    passes: float = 1.0,

                If FibsemPattern.Line
                    start_x: float (m), 
                    start_y: float (m), 
                    end_x: float (m), 
                    end_y: float (m), 
                    depth: float (m),

                If FibsemPattern.Circle
                    centre_x: float (m),
                    centre_y: float (m),
                    radius: float (m),
                    depth: float (m),
                    start_angle: float = 0.0 (degrees),
                    end_angle: float = 360.0 (degrees),

                if FibsemPattern.Bitmap
                    centre_x: float (m),
                    centre_y: float (m),
                    width: float (m),
                    height: float (m),
                    rotation: float = 0.0 (degrees),
                    depth: float (m),
                    path: str = path to image,
    '''
    def __init__(self, pattern: FibsemPattern = FibsemPattern.Rectangle, **kwargs):
        self.pattern = pattern
        if pattern == FibsemPattern.Rectangle:
            self.width = kwargs["width"]
            self.height = kwargs["height"]
            self.depth = kwargs["depth"]
            self.rotation = kwargs["rotation"] if "rotation" in kwargs else 0.0
            self.centre_x = kwargs["centre_x"] if "centre_x" in kwargs else 0.0
            self.centre_y = kwargs["centre_y"] if "centre_y" in kwargs else 0.0
            self.scan_direction= kwargs["scan_direction"] if "scan_direction" in kwargs else "TopToBottom"
            self.cleaning_cross_section= kwargs["cleaning_cross_section"] if "cleaning_cross_section" in kwargs else False
            self.passes = kwargs["passes"] if "passes" in kwargs else None
        elif pattern == FibsemPattern.Line:
            self.start_x = kwargs["start_x"]
            self.start_y = kwargs["start_y"]
            self.end_x = kwargs["end_x"]
            self.end_y = kwargs["end_y"]
            self.depth = kwargs["depth"]
            self.rotation = kwargs["rotation"] if "rotation" in kwargs else 0.0
            self.scan_direction= kwargs["scan_direction"] if "scan_direction" in kwargs else "TopToBottom"
            self.cleaning_cross_section= kwargs["cleaning_cross_section"] if "cleaning_cross_section" in kwargs else False
        elif pattern == FibsemPattern.Circle:
            self.centre_x = kwargs["centre_x"]
            self.centre_y = kwargs["centre_y"]
            self.radius = kwargs["radius"]
            self.depth = kwargs["depth"]
            self.start_angle = kwargs["start_angle"] if "start_angle" in kwargs else 0.0
            self.end_angle = kwargs["end_angle"] if "end_angle" in kwargs else 360.0
            self.rotation = kwargs["rotation"] if "rotation" in kwargs else 0.0
            self.scan_direction= kwargs["scan_direction"] if "scan_direction" in kwargs else "TopToBottom"
            self.cleaning_cross_section= kwargs["cleaning_cross_section"] if "cleaning_cross_section" in kwargs else False
        elif pattern == FibsemPattern.Bitmap:
            self.centre_x = kwargs["centre_x"]
            self.centre_y = kwargs["centre_y"]
            self.width = kwargs["width"]
            self.height = kwargs["height"]
            self.rotation = kwargs["rotation"] if "rotation" in kwargs else 0.0
            self.depth = kwargs["depth"]
            self.scan_direction= kwargs["scan_direction"] if "scan_direction" in kwargs else "TopToBottom"
            self.cleaning_cross_section= kwargs["cleaning_cross_section"] if "cleaning_cross_section" in kwargs else False
            self.path = kwargs["path"]
        elif pattern == FibsemPattern.Annulus:
            self.centre_x = kwargs["centre_x"]
            self.centre_y = kwargs["centre_y"]
            self.radius = kwargs["radius"]
            self.thickness = kwargs["thickness"]
            self.depth = kwargs["depth"]
            self.start_angle = kwargs["start_angle"] if "start_angle" in kwargs else 0.0
            self.end_angle = kwargs["end_angle"] if "end_angle" in kwargs else 360.0
            self.scan_direction= kwargs["scan_direction"] if "scan_direction" in kwargs else "TopToBottom"
            self.cleaning_cross_section= kwargs["cleaning_cross_section"] if "cleaning_cross_section" in kwargs else False

    def __repr__(self) -> str:
        if self.pattern == FibsemPattern.Rectangle:
            return f"FibsemPatternSettings(pattern={self.pattern}, width={self.width}, height={self.height}, depth={self.depth}, rotation={self.rotation}, centre_x={self.centre_x}, centre_y={self.centre_y}, scan_direction={self.scan_direction}, cleaning_cross_section={self.cleaning_cross_section}, passes={self.passes})"
        if self.pattern == FibsemPattern.Line:
            return f"FibsemPatternSettings(pattern={self.pattern}, start_x={self.start_x}, start_y={self.start_y}, end_x={self.end_x}, end_y={self.end_y}, depth={self.depth}, rotation={self.rotation}, scan_direction={self.scan_direction}, cleaning_cross_section={self.cleaning_cross_section})"
        if self.pattern is FibsemPattern.Circle:
            return f"FibsemPatternSettings(pattern={self.pattern}, centre_x={self.centre_x}, centre_y={self.centre_y}, radius={self.radius}, depth={self.depth}, start_angle={self.start_angle}, end_angle={self.end_angle}, rotation={self.rotation}, scan_direction={self.scan_direction}, cleaning_cross_section={self.cleaning_cross_section})"
        if self.pattern is FibsemPattern.Bitmap:
            return f"FibsemPatternSettings(pattern={self.pattern}, centre_x={self.centre_x}, centre_y={self.centre_y}, width={self.width}, height={self.height}, depth={self.depth}, path={self.path})"
        if self.pattern is FibsemPattern.Annulus:
            return f'FibsemPatternSettings(pattern={self.pattern}, centre_x={self.centre_x}, centre_y={self.centre_y}, radius={self.radius}, thickness={self.thickness}, depth={self.depth}, start_angle={self.start_angle}, end_angle={self.end_angle}, scan_direction={self.scan_direction}, cleaning_cross_section={self.cleaning_cross_section})'

    @staticmethod
    def __from_dict__(state_dict: dict) -> "FibsemPatternSettings":

        if state_dict["pattern"] == "Rectangle":
            passes = state_dict.get("passes", 0)
            passes = passes if passes is not None else 0
            return FibsemPatternSettings(
                pattern=FibsemPattern.Rectangle,
                width=state_dict["width"],
                height=state_dict["height"],
                depth=state_dict["depth"],
                rotation=state_dict["rotation"],
                centre_x=state_dict["centre_x"],
                centre_y=state_dict["centre_y"],
                scan_direction=state_dict["scan_direction"],
                cleaning_cross_section=state_dict["cleaning_cross_section"],
                passes=int(passes), 
            )
        elif state_dict["pattern"] == "Line":
            return FibsemPatternSettings(
                pattern=FibsemPattern.Line,
                start_x=state_dict["start_x"],
                start_y=state_dict["start_y"],
                end_x=state_dict["end_x"],
                end_y=state_dict["end_y"],
                depth=state_dict["depth"],
                rotation=state_dict.get("rotation", 0.0),
                scan_direction=state_dict["scan_direction"],
                cleaning_cross_section=state_dict["cleaning_cross_section"],
            )
        elif state_dict["pattern"] == "Circle":
            return FibsemPatternSettings(
                pattern=FibsemPattern.Circle,
                centre_x=state_dict["centre_x"],
                centre_y=state_dict["centre_y"],
                radius=state_dict["radius"],
                depth=state_dict["depth"],
                start_angle=state_dict["start_angle"],
                end_angle=state_dict["end_angle"],
                rotation=state_dict["rotation"],
                scan_direction=state_dict["scan_direction"],
                cleaning_cross_section=state_dict["cleaning_cross_section"],
            )
        elif state_dict["pattern"] == "BitmapPattern":
            return FibsemPatternSettings(
                pattern=FibsemPattern.Bitmap,
                centre_x=state_dict["centre_x"],
                centre_y=state_dict["centre_y"],
                width=state_dict["width"],
                height=state_dict["height"],
                depth=state_dict["depth"],
                rotation=state_dict["rotation"],
                path=state_dict["path"],
            )
        elif state_dict["pattern"] == "Annulus":
            return FibsemPatternSettings(
                pattern=FibsemPattern.Annulus,
                centre_x=state_dict["centre_x"],
                centre_y=state_dict["centre_y"],
                radius=state_dict["radius"],
                thickness=state_dict["thickness"],
                depth=state_dict["depth"],
                start_angle=state_dict["start_angle"],
                end_angle=state_dict["end_angle"],
                scan_direction=state_dict["scan_direction"],
                cleaning_cross_section=state_dict["cleaning_cross_section"],
            )

    def __to_dict__(self) -> dict:
        if self.pattern == FibsemPattern.Rectangle:
            return {
                "pattern": "Rectangle",
                "width": self.width,
                "height": self.height,
                "depth": self.depth,
                "rotation": self.rotation,
                "centre_x": self.centre_x,
                "centre_y": self.centre_y,
                "scan_direction": self.scan_direction,
                "cleaning_cross_section": self.cleaning_cross_section,
                "passes": self.passes,
            }
        elif self.pattern == FibsemPattern.Line:
            return {
                "pattern": "Line",
                "start_x": self.start_x,
                "start_y": self.start_y,
                "end_x": self.end_x,
                "end_y": self.end_y,
                "depth": self.depth,
                "rotation": self.rotation,
                "scan_direction": self.scan_direction,
                "cleaning_cross_section": self.cleaning_cross_section,
            }
        elif self.pattern == FibsemPattern.Circle:
            return {
                "pattern": "Circle",
                "centre_x": self.centre_x,
                "centre_y": self.centre_y,
                "radius": self.radius,
                "depth": self.depth,
                "start_angle": self.start_angle,
                "end_angle": self.end_angle,
                "rotation": self.rotation,
                "scan_direction": self.scan_direction,
                "cleaning_cross_section": self.cleaning_cross_section,
            }
        elif self.pattern == FibsemPattern.Bitmap:
            return {
                "pattern": "BitmapPattern",
                "centre_x": self.centre_x,
                "centre_y": self.centre_y,
                "width": self.width,
                "height": self.height,
                "depth": self.depth,
                "rotation": self.rotation,
                "scan_direction": self.scan_direction,
                "cleaning_cross_section": self.cleaning_cross_section,
                "path": self.path,
            }
        elif self.pattern == FibsemPattern.Annulus:
            return {
                "pattern": "Annulus",
                "centre_x": self.centre_x,
                "centre_y": self.centre_y,
                "radius": self.radius,
                "thickness": self.thickness,
                "depth": self.depth,
                "start_angle": self.start_angle,
                "end_angle": self.end_angle,
                "scan_direction": self.scan_direction,
                "cleaning_cross_section": self.cleaning_cross_section,
            }

FibsemRectangle dataclass

Universal Rectangle class used for ReducedArea

Source code in fibsem/structures.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
@dataclass
class FibsemRectangle:
    """Universal Rectangle class used for ReducedArea"""

    left: float = 0.0
    top: float = 0.0
    width: float = 0.0
    height: float = 0.0

    def __post_init__(self):

        assert isinstance(self.left,float) or isinstance(self.left,int), f"type {type(self.left)} is unsupported for left, must be int or floar"
        assert isinstance(self.top,float) or isinstance(self.top,int), f"type {type(self.top)} is unsupported for top, must be int or floar"
        assert isinstance(self.width,float) or isinstance(self.width,int), f"type {type(self.width)} is unsupported for width, must be int or floar"
        assert isinstance(self.height,float) or isinstance(self.height,int), f"type {type(self.height)} is unsupported for height, must be int or floar"

    def __from_dict__(settings: dict) -> "FibsemRectangle":
        if settings is None:
            return None
        points = ["left","top","width","height"]

        for point in points:

            value = settings[point]

            assert isinstance(value,float) or isinstance(value,int) or value is None

        return FibsemRectangle(
            left=settings["left"],
            top=settings["top"],
            width=settings["width"],
            height=settings["height"],
        )

    def __to_dict__(self) -> dict:
        return {
            "left": float(self.left),
            "top": float(self.top),
            "width": float(self.width),
            "height": float(self.height),
        }

    if THERMO:

        def __to_FEI__(self) -> Rectangle:
            return Rectangle(self.left, self.top, self.width, self.height)

        @classmethod
        def __from_FEI__(cls, rect: Rectangle) -> "FibsemRectangle":
            return cls(rect.left, rect.top, rect.width, rect.height)

FibsemStagePosition dataclass

Data class for storing stage position data.

Attributes:

Name Type Description
x float

The X position of the stage in meters.

y float

The Y position of the stage in meters.

z float

The Z position of the stage in meters.

r float

The Rotation of the stage in radians.

t float

The Tilt of the stage in radians.

coordinate_system str

The coordinate system used for the stage position.

Methods:

Name Description
__to_dict__

Convert the stage position object to a dictionary.

__from_dict__

dict): Create a new stage position object from a dictionary.

to_autoscript_position

float = 0.0) -> StagePosition: Convert the stage position to a StagePosition object that is compatible with Autoscript.

from_autoscript_position

StagePosition, stage_tilt: float = 0.0) -> None: Create a new FibsemStagePosition object from a StagePosition object that is compatible with Autoscript.

to_tescan_position

float = 0.0): Convert the stage position to a format that is compatible with Tescan.

from_tescan_position

Create a new FibsemStagePosition object from a Tescan-compatible stage position.

Source code in fibsem/structures.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
@dataclass
class FibsemStagePosition:
    """Data class for storing stage position data.

Attributes:
    x (float): The X position of the stage in meters.
    y (float): The Y position of the stage in meters.
    z (float): The Z position of the stage in meters.
    r (float): The Rotation of the stage in radians.
    t (float): The Tilt of the stage in radians.
    coordinate_system (str): The coordinate system used for the stage position.

Methods:
    __to_dict__(): Convert the stage position object to a dictionary.
    __from_dict__(data: dict): Create a new stage position object from a dictionary.
    to_autoscript_position(stage_tilt: float = 0.0) -> StagePosition: Convert the stage position to a StagePosition object that is compatible with Autoscript.
    from_autoscript_position(position: StagePosition, stage_tilt: float = 0.0) -> None: Create a new FibsemStagePosition object from a StagePosition object that is compatible with Autoscript.
    to_tescan_position(stage_tilt: float = 0.0): Convert the stage position to a format that is compatible with Tescan.
    from_tescan_position(): Create a new FibsemStagePosition object from a Tescan-compatible stage position.
"""
    name: str = None
    x: float = None
    y: float = None
    z: float = None
    r: float = None
    t: float = None
    coordinate_system: str = None

    # def __post_init__(self):

    #     coordinates = ["x","y","z","r","t"]
    #     for coordinate in coordinates:
    #         attribute = getattr(self,coordinate)
    #         assert isinstance(attribute,float) or isinstance(attribute,int)

    #     assert isinstance(self.coordinate_system,str) or self.coordinate_system is None

    def __to_dict__(self) -> dict:
        position_dict = {}

        position_dict["name"] = self.name if self.name is not None else None
        position_dict["x"] = float(self.x) if self.x is not None else None
        position_dict["y"] = float(self.y) if self.y is not None else None
        position_dict["z"] = float(self.z) if self.z is not None else None
        position_dict["r"] = float(self.r) if self.r is not None else None
        position_dict["t"] = float(self.t) if self.t is not None else None
        position_dict["coordinate_system"] = self.coordinate_system

        return position_dict

    @classmethod
    def __from_dict__(cls, data: dict) -> "FibsemStagePosition":

        items = ["x","y","z","r","t"]

        for item in items:

            value = data[item]

            assert isinstance(value,float) or isinstance(value,int) or value is None


        return cls(
            name=data.get("name", None),
            x=data["x"],
            y=data["y"],
            z=data["z"],
            r=data["r"],
            t=data["t"],
            coordinate_system=data["coordinate_system"],
        )

    if THERMO:

        def to_autoscript_position(self, stage_tilt: float = 0.0) -> StagePosition:
            return StagePosition(
                x=self.x,
                y=self.y, #/ np.cos(stage_tilt),
                z=self.z, #/ np.cos(stage_tilt),
                r=self.r,
                t=self.t,
                coordinate_system=self.coordinate_system,
            )

        @classmethod
        def from_autoscript_position(cls, position: StagePosition, stage_tilt: float = 0.0) -> None:
            return cls(
                x=position.x,
                y=position.y, # * np.cos(stage_tilt),
                z=position.z, # * np.cos(stage_tilt),
                r=position.r,
                t=position.t,
                coordinate_system=position.coordinate_system.upper(),
            )

    if TESCAN:

        def to_tescan_position(self, stage_tilt: float = 0.0):
            self.y=self.y #/ np.cos(stage_tilt),

        @classmethod
        def from_tescan_position(self, stage_tilt: float = 0.0):
            self.y = self.y #* np.cos(stage_tilt)


    def __add__(self, other:'FibsemStagePosition') -> 'FibsemStagePosition':
        return FibsemStagePosition(
            x = self.x + other.x if other.x is not None else self.x,
            y = self.y + other.y if other.y is not None else self.y,
            z = self.z + other.z if other.z is not None else self.z,
            r = self.r + other.r if other.r is not None else self.r,
            t = self.t + other.t if other.t is not None else self.t,
            coordinate_system = self.coordinate_system,
        )

    def __sub__(self, other:'FibsemStagePosition') -> 'FibsemStagePosition':
        return FibsemStagePosition(
            x = self.x - other.x,
            y = self.y - other.y,
            z = self.z - other.z,
            r = self.r - other.r,
            t = self.t - other.t,
            coordinate_system = self.coordinate_system,
        )

    def _scale_repr(self, scale: float, precision: int = 2):
        return f"x:{self.x*scale:.{precision}f}, y:{self.y*scale:.{precision}f}, z:{self.z*scale:.{precision}f}"

FibsemState dataclass

FibsemState data class that represents the current state of FIBSEM system

Attributes: stage (FibsemStage): The current stage of the autoliftout workflow, as a FibsemStage enum member. microscope_state (MicroscopeState): The current state of the microscope, as a MicroscopeState object. start_timestamp (float): The timestamp when the autoliftout workflow began, as a Unix timestamp. end_timestamp (float): The timestamp when the autoliftout workflow ended, as a Unix timestamp.

Methods: to_dict(): Serializes the FibsemState object to a dictionary. from_dict(state_dict: dict) -> FibsemState: Deserializes a dictionary to a FibsemState object.

Source code in fibsem/structures.py
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
@dataclass
class FibsemState:
    """
    FibsemState data class that represents the current state of FIBSEM system 

    Attributes:
    stage (FibsemStage): The current stage of the autoliftout workflow, as a `FibsemStage` enum member.
    microscope_state (MicroscopeState): The current state of the microscope, as a `MicroscopeState` object.
    start_timestamp (float): The timestamp when the autoliftout workflow began, as a Unix timestamp.
    end_timestamp (float): The timestamp when the autoliftout workflow ended, as a Unix timestamp.

    Methods:
    __to_dict__(): Serializes the `FibsemState` object to a dictionary.
    __from_dict__(state_dict: dict) -> FibsemState: Deserializes a dictionary to a `FibsemState` object.

    """

    stage: FibsemStage = FibsemStage.Base
    microscope_state: MicroscopeState = MicroscopeState()
    start_timestamp: float = None
    end_timestamp: float = None

    def __to_dict__(self) -> dict:

        state_dict = {
            "stage": self.stage.name,
            "microscope_state": self.microscope_state.__to_dict__(),
            "start_timestamp": self.start_timestamp,
            "end_timestamp": self.end_timestamp,
        }

        return state_dict

    @staticmethod
    def __from_dict__(state_dict: dict) -> "FibsemState":

        autoliftout_state = FibsemState(
            stage=FibsemStage[state_dict["stage"]],
            microscope_state=MicroscopeState.__from_dict__(
                state_dict["microscope_state"]
            ),
            start_timestamp=state_dict["start_timestamp"],
            end_timestamp=state_dict["end_timestamp"],
        )

        return autoliftout_state

FibsemSystem dataclass

Source code in fibsem/structures.py
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
@dataclass
class FibsemSystem:
    manufacturer: str = None
    model: str = None
    serial_number: str = None
    software_version: str = None
    hardware_settings: FibsemHardware = None

    def __to_dict__(self) -> dict:
        """Converts to a dictionary."""
        return {
            "manufacturer": self.manufacturer,
            "model": self.model,
            "serial_number": self.serial_number,
            "software_version": self.software_version,
            "hardware_settings": self.hardware_settings.__to_dict__() if self.hardware_settings is not None else None,
        }

    @staticmethod
    def __from_dict__(settings: dict) -> "FibsemSystem":
        """Converts from a dictionary."""
        if "hardware_settings" not in settings:
            hardware = None
        else:
            hardware = FibsemHardware.__from_dict__(settings["hardware_settings"])

        return FibsemSystem(
            manufacturer = settings.get("manufacturer", "Unknown"),
            model = settings.get("model", "Unknown"),
            serial_number = settings.get("serial_number", "Unknown"),
            software_version = settings.get("software_version", "Unknown"),
            hardware_settings = hardware,

        )

__from_dict__(settings) staticmethod

Converts from a dictionary.

Source code in fibsem/structures.py
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
@staticmethod
def __from_dict__(settings: dict) -> "FibsemSystem":
    """Converts from a dictionary."""
    if "hardware_settings" not in settings:
        hardware = None
    else:
        hardware = FibsemHardware.__from_dict__(settings["hardware_settings"])

    return FibsemSystem(
        manufacturer = settings.get("manufacturer", "Unknown"),
        model = settings.get("model", "Unknown"),
        serial_number = settings.get("serial_number", "Unknown"),
        software_version = settings.get("software_version", "Unknown"),
        hardware_settings = hardware,

    )

__to_dict__()

Converts to a dictionary.

Source code in fibsem/structures.py
1609
1610
1611
1612
1613
1614
1615
1616
1617
def __to_dict__(self) -> dict:
    """Converts to a dictionary."""
    return {
        "manufacturer": self.manufacturer,
        "model": self.model,
        "serial_number": self.serial_number,
        "software_version": self.software_version,
        "hardware_settings": self.hardware_settings.__to_dict__() if self.hardware_settings is not None else None,
    }

FibsemUser dataclass

Source code in fibsem/structures.py
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
@dataclass
class FibsemUser:
    name: str = None
    email: str = None
    organization: str = None
    computer: str = None

    def __to_dict__(self) -> dict:
        """Converts to a dictionary."""
        return {
            "name": self.name,
            "email": self.email,
            "organization": self.organization,
            "computer": self.computer,
        }

    @staticmethod
    def __from_dict__(settings: dict) -> "FibsemUser":
        """Converts from a dictionary."""
        return FibsemUser(
            name = settings.get("name", "Unknown"),
            email = settings.get("email", "Unknown"),
            organization = settings.get("organization", "Unknown"),
            computer = settings.get("computer", "Unknown"),
        )

__from_dict__(settings) staticmethod

Converts from a dictionary.

Source code in fibsem/structures.py
1591
1592
1593
1594
1595
1596
1597
1598
1599
@staticmethod
def __from_dict__(settings: dict) -> "FibsemUser":
    """Converts from a dictionary."""
    return FibsemUser(
        name = settings.get("name", "Unknown"),
        email = settings.get("email", "Unknown"),
        organization = settings.get("organization", "Unknown"),
        computer = settings.get("computer", "Unknown"),
    )

__to_dict__()

Converts to a dictionary.

Source code in fibsem/structures.py
1582
1583
1584
1585
1586
1587
1588
1589
def __to_dict__(self) -> dict:
    """Converts to a dictionary."""
    return {
        "name": self.name,
        "email": self.email,
        "organization": self.organization,
        "computer": self.computer,
    }

ImageSettings dataclass

A data class representing the settings for an image acquisition.

Attributes:

Name Type Description
resolution list of int

The resolution of the acquired image in pixels, [x, y].

dwell_time float

The time spent per pixel during image acquisition, in seconds.

hfw float

The horizontal field width of the acquired image, in microns.

autocontrast bool

Whether or not to apply automatic contrast enhancement to the acquired image.

beam_type BeamType

The type of beam to use for image acquisition.

save bool

Whether or not to save the acquired image to disk.

label str

The label to use when saving the acquired image.

gamma_enabled bool

Whether or not to apply gamma correction to the acquired image.

save_path Path

The path to the directory where the acquired image should be saved.

reduced_area FibsemRectangle

The rectangular region of interest within the acquired image, if any.

Methods:

Name Description
__from_dict__

dict) -> ImageSettings: Converts a dictionary of image settings to an ImageSettings object.

__to_dict__

Converts the ImageSettings object to a dictionary of image settings.

Source code in fibsem/structures.py
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
@dataclass
class ImageSettings:
    """A data class representing the settings for an image acquisition.

    Attributes:
        resolution (list of int): The resolution of the acquired image in pixels, [x, y].
        dwell_time (float): The time spent per pixel during image acquisition, in seconds.
        hfw (float): The horizontal field width of the acquired image, in microns.
        autocontrast (bool): Whether or not to apply automatic contrast enhancement to the acquired image.
        beam_type (BeamType): The type of beam to use for image acquisition.
        save (bool): Whether or not to save the acquired image to disk.
        label (str): The label to use when saving the acquired image.
        gamma_enabled (bool): Whether or not to apply gamma correction to the acquired image.
        save_path (Path): The path to the directory where the acquired image should be saved.
        reduced_area (FibsemRectangle): The rectangular region of interest within the acquired image, if any.

    Methods:
        __from_dict__(settings: dict) -> ImageSettings:
            Converts a dictionary of image settings to an ImageSettings object.
        __to_dict__() -> dict:
            Converts the ImageSettings object to a dictionary of image settings.
    """

    resolution: list = None
    dwell_time: float = None
    hfw: float = None
    autocontrast: bool = None
    beam_type: BeamType = None
    save: bool = None
    label: str = None
    gamma_enabled: bool = None
    save_path: Path = None
    reduced_area: FibsemRectangle = None
    line_integration: int = None # (int32) 2 - 255
    scan_interlacing: int = None # (int32) 2 - 8
    frame_integration: int  = None # (int32) 2 - 512
    drift_correction: bool = False # (bool) # requires frame_integration > 1

    def __post_init__(self):

        assert isinstance(self.resolution,list) or self.resolution is None, f"resolution must be a list, currently is {type(self.resolution)}"
        assert isinstance(self.dwell_time,float) or self.dwell_time is None, f"dwell time must be of type float, currently is {type(self.dwell_time)}"
        assert isinstance(self.hfw, float) or isinstance(self.hfw,int) or self.hfw is None, f"hfw must be int or float, currently is {type(self.hfw)}"
        assert isinstance(self.autocontrast, bool) or self.autocontrast is None, f"autocontrast setting must be bool, currently is {type(self.autocontrast)}"
        assert isinstance(self.beam_type,BeamType) or self.beam_type is None, f"beam type must be a BeamType object, currently is {type(self.beam_type)}"
        assert isinstance(self.save,bool) or self.save is None, f"save option must be a bool, currently is {type(self.save)}"
        assert isinstance(self.label,str) or self.label is None, f"label must b str, currently is {type(self.label)}"
        assert isinstance(self.gamma_enabled,bool) or self.gamma_enabled is None, f"gamma enabled setting must be bool, currently is {type(self.gamma_enabled)}"
        assert isinstance(self.save_path,(Path,str)) or self.save_path is None, f"save path must be Path or str, currently is {type(self.save_path)}"
        assert isinstance(self.reduced_area,FibsemRectangle) or self.reduced_area is None, f"reduced area must be a fibsemRectangle object, currently is {type(self.reduced_area)}"


    @staticmethod
    def __from_dict__(settings: dict) -> "ImageSettings":


        if "reduced_area" in settings and settings["reduced_area"] is not None:
            reduced_area = FibsemRectangle.__from_dict__(settings["reduced_area"])
        else:
            reduced_area = None


        image_settings = ImageSettings(
            resolution=settings.get("resolution", (1536, 1024)),
            dwell_time=settings.get("dwell_time", 1.0e-6),
            hfw=settings.get("hfw", 150e-6),
            autocontrast=settings.get("autocontrast", False),
            beam_type=BeamType[settings.get("beam_type", "Electron").upper()],
            gamma_enabled=settings.get("gamma_enabled", False),
            save=settings.get("save", False),
            save_path=settings.get("save_path", os.getcwd()),
            label=settings.get("label", "default_image"),
            reduced_area=reduced_area,
            line_integration=settings.get("line_integration", None),
            scan_interlacing=settings.get("scan_interlacing", None),
            frame_integration=settings.get("frame_integration", None),
            drift_correction=settings.get("drift_correction", False),
        )

        return image_settings

    def __to_dict__(self) -> dict:

        settings_dict = {
            "beam_type": self.beam_type.name if self.beam_type is not None else None,
            "resolution": self.resolution if self.resolution is not None else None,
            "dwell_time": self.dwell_time if self.dwell_time is not None else None,
            "hfw": self.hfw if self.hfw is not None else None,
            "autocontrast": self.autocontrast
            if self.autocontrast is not None
            else None,
            "gamma_enabled": self.gamma_enabled if self.gamma_enabled is not None else None,
            "save": self.save if self.save is not None else None,
            "save_path": str(self.save_path) if self.save_path is not None else None,
            "label": self.label if self.label is not None else None,
            "reduced_area": {
                "left": self.reduced_area.left,
                "top": self.reduced_area.top,
                "width": self.reduced_area.width,
                "height": self.reduced_area.height,
            }
            if self.reduced_area is not None
            else None,
            "line_integration": self.line_integration,
            "scan_interlacing": self.scan_interlacing,
            "frame_integration": self.frame_integration,
            "drift_correction": self.drift_correction,
        }

        return settings_dict

    @staticmethod
    def fromFibsemImage(image: 'FibsemImage') -> "ImageSettings":
        """Returns the image settings for a FibsemImage object.

        Args:
            image (FibsemImage): The FibsemImage object to get the image settings from.

        Returns:
            ImageSettings: The image settings for the given FibsemImage object.
        """
        from fibsem import utils
        from copy import deepcopy
        image_settings = deepcopy(image.metadata.image_settings)
        image_settings.label = utils.current_timestamp()
        image_settings.save = True

        return image_settings

fromFibsemImage(image) staticmethod

Returns the image settings for a FibsemImage object.

Parameters:

Name Type Description Default
image FibsemImage

The FibsemImage object to get the image settings from.

required

Returns:

Name Type Description
ImageSettings ImageSettings

The image settings for the given FibsemImage object.

Source code in fibsem/structures.py
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
@staticmethod
def fromFibsemImage(image: 'FibsemImage') -> "ImageSettings":
    """Returns the image settings for a FibsemImage object.

    Args:
        image (FibsemImage): The FibsemImage object to get the image settings from.

    Returns:
        ImageSettings: The image settings for the given FibsemImage object.
    """
    from fibsem import utils
    from copy import deepcopy
    image_settings = deepcopy(image.metadata.image_settings)
    image_settings.label = utils.current_timestamp()
    image_settings.save = True

    return image_settings

MicroscopeSettings dataclass

A data class representing the settings for a microscope system.

Attributes:

Name Type Description
system SystemSettings

An instance of the SystemSettings class that holds the system settings.

image ImageSettings

An instance of the ImageSettings class that holds the image settings.

protocol dict

A dictionary representing the protocol settings. Defaults to None.

milling FibsemMillingSettings

An instance of the FibsemMillingSettings class that holds the fibsem milling settings. Defaults to None.

Methods:

Name Description
__to_dict__

Returns a dictionary representation of the MicroscopeSettings object.

__from_dict__

dict, protocol: dict = None) -> "MicroscopeSettings": Returns an instance of the MicroscopeSettings class from a dictionary.

Source code in fibsem/structures.py
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
@dataclass
class MicroscopeSettings:

    """
    A data class representing the settings for a microscope system.

    Attributes:
        system (SystemSettings): An instance of the `SystemSettings` class that holds the system settings.
        image (ImageSettings): An instance of the `ImageSettings` class that holds the image settings.
        protocol (dict, optional): A dictionary representing the protocol settings. Defaults to None.
        milling (FibsemMillingSettings, optional): An instance of the `FibsemMillingSettings` class that holds the fibsem milling settings. Defaults to None.

    Methods:
        __to_dict__(): Returns a dictionary representation of the `MicroscopeSettings` object.
        __from_dict__(settings: dict, protocol: dict = None) -> "MicroscopeSettings": Returns an instance of the `MicroscopeSettings` class from a dictionary.
    """

    system: SystemSettings
    image: ImageSettings
    protocol: dict = None
    milling: FibsemMillingSettings = None
    hardware: FibsemHardware = None


    def __to_dict__(self) -> dict:

        settings_dict = {
            "system": self.system.__to_dict__(),
            "user": self.image.__to_dict__(),
            "protocol": self.protocol,
            "milling": self.milling.__to_dict__(),
            "hardware": self.hardware.__to_dict__(),

        }

        return settings_dict

    @staticmethod
    def __from_dict__(settings: dict, protocol: dict = None, hardware: dict = None) -> "MicroscopeSettings":

        return MicroscopeSettings(
            system=SystemSettings.__from_dict__(settings["system"]),
            image=ImageSettings.__from_dict__(settings["user"]),
            protocol=protocol if protocol is not None else settings["protocol"],
            milling=FibsemMillingSettings.__from_dict__(settings["milling"]),
            hardware=FibsemHardware.__from_dict__(hardware) if hardware is not None else FibsemHardware.__from_dict__(settings["hardware"]),

        )

MicroscopeState dataclass

Data Class representing the state of a microscope with various parameters.

Attributes:

timestamp (float): A float representing the timestamp at which the state of the microscope was recorded. Defaults to the timestamp of the current datetime.
absolute_position (FibsemStagePosition): An instance of FibsemStagePosition representing the current absolute position of the stage. Defaults to an empty instance of FibsemStagePosition.
eb_settings (BeamSettings): An instance of BeamSettings representing the electron beam settings. Defaults to an instance of BeamSettings with beam_type set to BeamType.ELECTRON.
ib_settings (BeamSettings): An instance of BeamSettings representing the ion beam settings. Defaults to an instance of BeamSettings with beam_type set to BeamType.ION.

Methods:

to_dict(self) -> dict: Converts the current state of the Microscope to a dictionary and returns it.
from_dict(state_dict: dict) -> "MicroscopeState": Returns a new instance of MicroscopeState with attributes created from the passed dictionary.
Source code in fibsem/structures.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
@dataclass
class MicroscopeState:

    """Data Class representing the state of a microscope with various parameters.

    Attributes:

        timestamp (float): A float representing the timestamp at which the state of the microscope was recorded. Defaults to the timestamp of the current datetime.
        absolute_position (FibsemStagePosition): An instance of FibsemStagePosition representing the current absolute position of the stage. Defaults to an empty instance of FibsemStagePosition.
        eb_settings (BeamSettings): An instance of BeamSettings representing the electron beam settings. Defaults to an instance of BeamSettings with beam_type set to BeamType.ELECTRON.
        ib_settings (BeamSettings): An instance of BeamSettings representing the ion beam settings. Defaults to an instance of BeamSettings with beam_type set to BeamType.ION.

    Methods:

        to_dict(self) -> dict: Converts the current state of the Microscope to a dictionary and returns it.
        from_dict(state_dict: dict) -> "MicroscopeState": Returns a new instance of MicroscopeState with attributes created from the passed dictionary.
    """

    timestamp: float = datetime.timestamp(datetime.now())
    absolute_position: FibsemStagePosition = FibsemStagePosition()
    eb_settings: BeamSettings = BeamSettings(beam_type=BeamType.ELECTRON)
    ib_settings: BeamSettings = BeamSettings(beam_type=BeamType.ION)
    eb_detector: FibsemDetectorSettings = FibsemDetectorSettings()
    ib_detector: FibsemDetectorSettings = FibsemDetectorSettings()

    def __post_init__(self):
        assert isinstance(self.absolute_position,FibsemStagePosition) or self.absolute_position is None, f"absolute position must be of type FibsemStagePosition, currently is {type(self.absolute_position)}"
        assert isinstance(self.eb_settings,BeamSettings) or self.eb_settings is None, f"eb_settings must be of type BeamSettings, currently is {type(self.eb_settings)}"
        assert isinstance(self.ib_settings,BeamSettings) or self.ib_settings is None, f"ib_settings must be of type BeamSettings, currently us {type(self.ib_settings)}"
        assert isinstance(self.eb_detector,FibsemDetectorSettings) or self.eb_detector is None, f"eb_detector must be of type FibsemDetectorSettings, currently is {type(self.eb_detector)}"
        assert isinstance(self.ib_detector,FibsemDetectorSettings) or self.ib_detector is None, f"ib_detector must be of type FibsemDetectorSettings, currently is {type(self.ib_detector)}"


    def __to_dict__(self) -> dict:

        state_dict = {
            "timestamp": self.timestamp,
            "absolute_position": self.absolute_position.__to_dict__() if self.absolute_position is not None else "Not defined",
            "eb_settings": self.eb_settings.__to_dict__() if self.eb_settings is not None else "Not defined",
            "ib_settings": self.ib_settings.__to_dict__() if self.ib_settings is not None else "Not defined",
            "eb_detector": self.eb_detector.__to_dict__() if self.eb_detector is not None else "Not defined",
            "ib_detector": self.ib_detector.__to_dict__() if self.ib_detector is not None else "Not defined",
        }

        return state_dict

    @staticmethod
    def __from_dict__(state_dict: dict) -> "MicroscopeState":
        microscope_state = MicroscopeState(
            timestamp=state_dict["timestamp"],
            absolute_position=FibsemStagePosition.__from_dict__(state_dict["absolute_position"]),
            eb_settings=BeamSettings.__from_dict__(state_dict["eb_settings"]),
            ib_settings=BeamSettings.__from_dict__(state_dict["ib_settings"]),
            eb_detector=FibsemDetectorSettings.__from_dict__(state_dict.get("eb_detector",{})),
            ib_detector=FibsemDetectorSettings.__from_dict__(state_dict.get("ib_detector",{})),
        )

        return microscope_state

Point dataclass

Source code in fibsem/structures.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@dataclass
class Point:
    x: float = 0.0
    y: float = 0.0
    name: Optional[str] = None

    def __to_dict__(self) -> dict:
        return {"x": self.x, "y": self.y}

    @staticmethod
    def __from_dict__(d: dict) -> "Point":
        x = float(d["x"])
        y = float(d["y"])
        return Point(x, y)


    def __to_list__(self) -> list:
        return [self.x, self.y]

    @staticmethod
    def __from_list__(l: list) -> "Point":
        x = float(l[0])
        y = float(l[1])
        return Point(x, y)

    def __add__(self, other) -> 'Point':
        return Point(self.x + other.x, self.y + other.y)

    def __sub__(self, other) -> 'Point':
        return Point(self.x - other.x, self.y - other.y)

    def __len__(self) -> int:
        return 2

    def __getitem__(self, key: int) -> float:
        if key == 0:
            return self.x
        elif key == 1:
            return self.y
        else:
            raise IndexError("Index out of range")

    def _to_metres(self, pixel_size: float) -> 'Point':
        return Point(self.x * pixel_size, self.y * pixel_size)

    def _to_pixels(self, pixel_size: float) -> 'Point':
        return Point(self.x / pixel_size, self.y / pixel_size)

    def distance(self, other: 'Point') -> 'Point':
        """Calculate the distance between two points. (other - self)"""
        return Point(x=(other.x - self.x), y=(other.y - self.y))

    def euclidean(self, other: 'Point') -> float:
        """Calculate the euclidean distance between two points."""
        return np.linalg.norm(self.distance(other).__to_list__())

distance(other)

Calculate the distance between two points. (other - self)

Source code in fibsem/structures.py
91
92
93
def distance(self, other: 'Point') -> 'Point':
    """Calculate the distance between two points. (other - self)"""
    return Point(x=(other.x - self.x), y=(other.y - self.y))

euclidean(other)

Calculate the euclidean distance between two points.

Source code in fibsem/structures.py
95
96
97
def euclidean(self, other: 'Point') -> float:
    """Calculate the euclidean distance between two points."""
    return np.linalg.norm(self.distance(other).__to_list__())

StageSettings dataclass

A data class representing the settings for the stage.

Attributes: rotation_flat_to_electron (float): The rotation from flat to electron in degrees. rotation_flat_to_ion (float): The rotation from flat to ion in degrees. tilt_flat_to_electron (float): The tilt from flat to electron in degrees. tilt_flat_to_ion (float): The tilt from flat to ion in degrees. pre_tilt (float): The pre-tilt in degrees. needle_stage_height_limit (float): The height limit of the needle stage in meters.

Methods: to_dict() -> dict: Returns the settings as a dictionary. from_dict(settings: dict) -> "StageSettings": Returns an instance of StageSettings from a dictionary of its settings.

Source code in fibsem/structures.py
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
@dataclass
class StageSettings:
    """
    A data class representing the settings for the stage.

    Attributes:
    rotation_flat_to_electron (float): The rotation from flat to electron in degrees.
    rotation_flat_to_ion (float): The rotation from flat to ion in degrees.
    tilt_flat_to_electron (float): The tilt from flat to electron in degrees.
    tilt_flat_to_ion (float): The tilt from flat to ion in degrees.
    pre_tilt (float): The pre-tilt in degrees.
    needle_stage_height_limit (float): The height limit of the needle stage in meters.

    Methods:
    __to_dict__() -> dict: Returns the settings as a dictionary.
    __from_dict__(settings: dict) -> "StageSettings": Returns an instance of StageSettings from a dictionary of its settings.
    """
    rotation_flat_to_electron: float = 50  # degrees
    rotation_flat_to_ion: float = 230  # degrees
    tilt_flat_to_electron: float = 27  # degrees (pre_tilt)
    tilt_flat_to_ion: float = 52  # degrees
    pre_tilt: float = 35  # degrees
    needle_stage_height_limit: float = 3.7e-3

    def __to_dict__(self) -> dict:

        settings = {
            "rotation_flat_to_electron": self.rotation_flat_to_electron,
            "rotation_flat_to_ion": self.rotation_flat_to_ion,
            "tilt_flat_to_electron": self.tilt_flat_to_electron,
            "tilt_flat_to_ion": self.tilt_flat_to_ion,
            "pre_tilt": self.pre_tilt,
            "needle_stage_height_limit": self.needle_stage_height_limit,
        }
        return settings

    @staticmethod
    def __from_dict__(settings: dict) -> "StageSettings":

        stage_settings = StageSettings(
            rotation_flat_to_electron=settings["rotation_flat_to_electron"],
            rotation_flat_to_ion=settings["rotation_flat_to_ion"],
            tilt_flat_to_electron=settings["tilt_flat_to_electron"],
            tilt_flat_to_ion=settings["tilt_flat_to_ion"],
            pre_tilt=settings["pre_tilt"],
            needle_stage_height_limit=settings["needle_stage_height_limit"],
        )

        return stage_settings

SystemSettings dataclass

Dataclass representing the system settings for the FIB-SEM instrument.

:param ip_address: IP address of the instrument. :param stage: settings for the stage. :param ion: settings for the ion beam. :param electron: settings for the electron beam. :param manufacturer: name of the instrument manufacturer.

:return: a new instance of SystemSettings.

Source code in fibsem/structures.py
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
@dataclass
class SystemSettings:

    """
    Dataclass representing the system settings for the FIB-SEM instrument.

    :param ip_address: IP address of the instrument.
    :param stage: settings for the stage.
    :param ion: settings for the ion beam.
    :param electron: settings for the electron beam.
    :param manufacturer: name of the instrument manufacturer.

    :return: a new instance of `SystemSettings`.
    """

    ip_address: str = "10.0.0.1"
    stage: StageSettings = None
    ion: BeamSystemSettings = None
    electron: BeamSystemSettings = None
    manufacturer: str = None
    system_info: dict = None

    def __to_dict__(self) -> dict:

        settings_dict = {
            "ip_address": self.ip_address,
            "stage": self.stage.__to_dict__(),
            "ion": self.ion.__to_dict__(),
            "electron": self.electron.__to_dict__(),
            "manufacturer": self.manufacturer,
        }
        settings_dict["name"] = self.system_info["name"]
        settings_dict["manufacturer"] = self.system_info["manufacturer"]
        settings_dict["description"] = self.system_info["description"]
        settings_dict["version"] = self.system_info["version"]
        settings_dict["id"] = self.system_info["id"]

        return settings_dict

    @staticmethod
    def __from_dict__(settings: dict) -> "SystemSettings":

        system_settings = SystemSettings(
            ip_address=settings["ip_address"],
            stage=StageSettings.__from_dict__(settings["stage"]),
            ion=BeamSystemSettings.__from_dict__(settings["ion"], BeamType.ION),
            electron=BeamSystemSettings.__from_dict__(
                settings["electron"], BeamType.ELECTRON
            ),
            manufacturer=settings["manufacturer"],
            system_info = {
                "name":settings["name"],
                "manufacturer":settings["manufacturer"],
                "description":settings["description"],
                "version":settings["version"],
                "id":settings["id"],
            }
        )

        return system_settings

check_data_format(data)

Checks that data is in the correct format.

Source code in fibsem/structures.py
2105
2106
2107
2108
2109
2110
2111
def check_data_format(data: np.ndarray) -> bool:
    """Checks that data is in the correct format."""
    # assert data.ndim == 2  # or data.ndim == 3
    # assert data.dtype in [np.uint8, np.uint16]
    if data.ndim == 3 and data.shape[2] == 1:
        data = data[:, :, 0]
    return data.ndim == 2 and data.dtype in [np.uint8, np.uint16]

load_needle_yaml(path)

Load the manipulator position from disk

Source code in fibsem/structures.py
1187
1188
1189
1190
1191
1192
1193
1194
def load_needle_yaml(path: Path) -> ManipulatorPosition:
    """Load the manipulator position from disk"""
    from fibsem.structures import manipulator_position_from_dict

    position_dict = load_yaml(os.path.join(path, "needle.yaml"))
    position = manipulator_position_from_dict(position_dict)

    return position

save_needle_yaml(path, position)

Save the manipulator position from disk

Source code in fibsem/structures.py
1180
1181
1182
1183
1184
1185
def save_needle_yaml(path: Path, position: ManipulatorPosition) -> None:
    """Save the manipulator position from disk"""
    from fibsem.structures import manipulator_position_to_dict

    with open(os.path.join(path, "needle.yaml"), "w") as f:
        yaml.dump(manipulator_position_to_dict(position), f, indent=4)

stage_position_from_dict(state_dict)

Converts a dictionary object to a fibsem stage position, dictionary must have correct keys

Source code in fibsem/structures.py
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
def stage_position_from_dict(state_dict: dict) -> FibsemStagePosition:
    """Converts a dictionary object to a fibsem stage position,
        dictionary must have correct keys"""

    stage_position = FibsemStagePosition(
        x=state_dict["x"],
        y=state_dict["y"],
        z=state_dict["z"],
        r=state_dict["r"],
        t=state_dict["t"],
        coordinate_system=state_dict["coordinate_system"],
    )

    return stage_position

stage_position_to_dict(stage_position)

Converts the FibsemStagePosition Object into a dictionary

Source code in fibsem/structures.py
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
def stage_position_to_dict(stage_position: FibsemStagePosition) -> dict:
    """Converts the FibsemStagePosition Object into a dictionary"""

    # attributes = ["x","y","z","r","t"]
    # for attribute in attributes:
    #     assert isinstance(getattr(stage_position,attribute),float) or isinstance(getattr(stage_position,attribute),int)

    #assert stage_position.coordinate_system in SUPPORTED_COORDINATE_SYSTEMS or stage_position.coordinate_system is None

    stage_position_dict = {
        "x": stage_position.x,
        "y": stage_position.y,
        "z": stage_position.z,
        "r": stage_position.r,
        "t": stage_position.t,
        "coordinate_system": stage_position.coordinate_system,
    }

    return stage_position_dict

Utils

configure_logging(path='', log_filename='logfile', log_level=logging.DEBUG, _DEBUG=False)

Log to the terminal and to file simultaneously.

Source code in fibsem/utils.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def configure_logging(path: Path = "", log_filename="logfile", log_level=logging.DEBUG, _DEBUG: bool = False):
    """Log to the terminal and to file simultaneously."""
    logfile = os.path.join(path, f"{log_filename}.log")

    file_handler = logging.FileHandler(logfile)
    stream_handler = logging.StreamHandler(sys.stdout)
    stream_handler.setLevel(logging.INFO if _DEBUG is False else logging.DEBUG)

    logging.basicConfig(
        format="%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s",
        level=log_level,
        # Multiple handlers can be added to your logging configuration.
        # By default log messages are appended to the file if it exists already
        handlers=[file_handler, stream_handler],
        force=True,
    )

    return logfile

create_gif(path, search, gif_fname, loop=0)

Creates a GIF from a set of images. Images must be in same folder

Parameters:

Name Type Description Default
path Path

Path to images folder

required
search str

search name

required
gif_fname str

name to save gif file

required
loop int

description. Defaults to 0.

0
Source code in fibsem/utils.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def create_gif(path: Path, search: str, gif_fname: str, loop: int = 0) -> None:
    """Creates a GIF from a set of images. Images must be in same folder

    Args:
        path (Path): Path to images folder
        search (str): search name
        gif_fname (str): name to save gif file
        loop (int, optional): _description_. Defaults to 0.
    """
    filenames = glob.glob(os.path.join(path, search))

    imgs = [Image.fromarray(FibsemImage.load(fname).data) for fname in filenames]

    print(f"{len(filenames)} images added to gif.")
    imgs[0].save(
        os.path.join(path, f"{gif_fname}.gif"),
        save_all=True,
        append_images=imgs[1:],
        loop=loop,
    )

current_timestamp()

Returns current time in a specific string format

Returns:

Name Type Description
String

Current time

Source code in fibsem/utils.py
28
29
30
31
32
33
34
def current_timestamp():
    """Returns current time in a specific string format

    Returns:
        String: Current time
    """
    return datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d-%I-%M-%S%p") #PM/AM doesnt work?

current_timestamp_v2()

Returns current time in a specific string format

Returns:

Name Type Description
String

Current time

Source code in fibsem/utils.py
37
38
39
40
41
42
43
def current_timestamp_v2():
    """Returns current time in a specific string format

    Returns:
        String: Current time
    """
    return str(time.time()).replace(".", "_")

get_params(main_str)

Helper function to access relevant metadata parameters from sub field

Parameters:

Name Type Description Default
main_str str

Sub string of relevant metadata

required

Returns:

Name Type Description
list list

Parameters covered by metadata

Source code in fibsem/utils.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
def get_params(main_str: str) -> list:
    """Helper function to access relevant metadata parameters from sub field

    Args:
        main_str (str): Sub string of relevant metadata

    Returns:
        list: Parameters covered by metadata
    """
    cats = []
    cat_str = ""

    i = main_str.find("\n")
    i += 1
    while i < len(main_str):

        if main_str[i] == "=":
            cats.append(cat_str)
            cat_str = ""
            i += main_str[i:].find("\n")
        else:
            cat_str += main_str[i]

        i += 1
    return cats

load_protocol(protocol_path=None)

Load the protocol file from yaml

Parameters:

Name Type Description Default
protocol_path Path

path to protocol file. Defaults to None.

None

Returns:

Name Type Description
dict dict

protocol dictionary

Source code in fibsem/utils.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def load_protocol(protocol_path: Path = None) -> dict:
    """Load the protocol file from yaml

    Args:
        protocol_path (Path, optional): path to protocol file. Defaults to None.

    Returns:
        dict: protocol dictionary
    """
    if protocol_path is not None:
        protocol = load_yaml(protocol_path)
    else:
        protocol = {"name": "demo"}

    #protocol = _format_dictionary(protocol)

    return protocol

load_settings_from_config(config_path=None, protocol_path=None)

Load microscope settings from configuration files

Parameters:

Name Type Description Default
config_path Path

path to config directory. Defaults to None.

None
protocol_path Path

path to protocol file. Defaults to None.

None

Returns:

Name Type Description
MicroscopeSettings MicroscopeSettings

microscope settings

Source code in fibsem/utils.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def load_settings_from_config(
    config_path: Path = None, protocol_path: Path = None
) -> MicroscopeSettings:
    """Load microscope settings from configuration files

    Args:
        config_path (Path, optional): path to config directory. Defaults to None.
        protocol_path (Path, optional): path to protocol file. Defaults to None.

    Returns:
        MicroscopeSettings: microscope settings
    """
    # TODO: this should just be system.yaml path, not directory
    if config_path is None:
        from fibsem.config import CONFIG_PATH

        config_path = os.path.join(CONFIG_PATH, "system.yaml")

    # system settings
    settings = load_yaml(os.path.join(config_path))
    system_settings = SystemSettings.__from_dict__(settings["system"])

    # user settings
    image_settings = ImageSettings.__from_dict__(settings["user"]["imaging"])

    milling_settings = FibsemMillingSettings.__from_dict__(settings["user"]["milling"])

    # protocol settings
    protocol = load_protocol(protocol_path)

    # hardware settings
    hardware_settings = FibsemHardware.__from_dict__(settings["model"])

    settings = MicroscopeSettings(
        system=system_settings,
        image=image_settings,
        protocol=protocol,
        milling=milling_settings,
        hardware=hardware_settings,
    )

    return settings

load_yaml(fname)

load yaml file

Parameters:

Name Type Description Default
fname Path

yaml file path

required

Returns:

Name Type Description
dict dict

Items in yaml

Source code in fibsem/utils.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def load_yaml(fname: Path) -> dict:
    """load yaml file

    Args:
        fname (Path): yaml file path

    Returns:
        dict: Items in yaml
    """
    with open(fname, "r") as f:
        config = yaml.safe_load(f)

    return config

make_logging_directory(path=None, name='run')

Create a logging directory with the specified name at the specified file path. If no path is given, it creates the directory at the default base path.

Parameters:

Name Type Description Default
path Path

The file path to create the logging directory at. If None, default base path is used.

None
name str

The name of the logging directory to create. Default is "run".

'run'

Returns:

Name Type Description
str

The file path to the created logging directory.

Source code in fibsem/utils.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def make_logging_directory(path: Path = None, name="run"):
    """
    Create a logging directory with the specified name at the specified file path. 
    If no path is given, it creates the directory at the default base path.

    Args:
        path (Path, optional): The file path to create the logging directory at. If None, default base path is used. 
        name (str, optional): The name of the logging directory to create. Default is "run".

    Returns:
        str: The file path to the created logging directory.
        """

    if path is None:
        path = os.path.join(cfg.BASE_PATH, "log")
    directory = os.path.join(path, name)
    os.makedirs(directory, exist_ok=True)
    return directory

save_yaml(path, data)

Saves a python dictionary object to a yaml file

Parameters:

Name Type Description Default
path Path

path location to save yaml file

required
data dict

dictionary object

required
Source code in fibsem/utils.py
106
107
108
109
110
111
112
113
114
115
116
def save_yaml(path: Path, data: dict) -> None:
    """Saves a python dictionary object to a yaml file

    Args:
        path (Path): path location to save yaml file
        data (dict): dictionary object
    """
    os.makedirs(os.path.dirname(path), exist_ok=True)
    path = Path(path).with_suffix(".yaml")
    with open(path, "w") as f:
        yaml.dump(data, f, indent=4)

setup_session(session_path=None, config_path=None, protocol_path=None, setup_logging=True, ip_address=None, manufacturer=None)

Setup microscope session

Parameters:

Name Type Description Default
session_path Path

path to logging directory

None
config_path Path

path to config directory (system.yaml)

None
protocol_path Path

path to protocol file

None

Returns:

Name Type Description
tuple tuple[FibsemMicroscope, MicroscopeSettings]

microscope, settings

Source code in fibsem/utils.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def setup_session(
    session_path: Path = None,
    config_path: Path = None,
    protocol_path: Path = None,
    setup_logging: bool = True,
    ip_address: str = None,
    manufacturer: str = None
) -> tuple[FibsemMicroscope, MicroscopeSettings]:
    """Setup microscope session

    Args:
        session_path (Path): path to logging directory
        config_path (Path): path to config directory (system.yaml)
        protocol_path (Path): path to protocol file

    Returns:
        tuple: microscope, settings
    """

    # load settings
    settings = load_settings_from_config(config_path, protocol_path)

    # create session directories
    session = f'{settings.protocol["name"]}_{current_timestamp()}'
    if protocol_path is None:
        protocol_path = os.getcwd()

    # configure paths
    if session_path is None:
        session_path = cfg.LOG_PATH
    os.makedirs(session_path, exist_ok=True)

    # configure logging
    if setup_logging:
        configure_logging(session_path)

    # connect to microscope
    import fibsem.microscope as FibSem

    # cheap overloading
    if ip_address:
        settings.system.ip_address = ip_address

    if manufacturer:
        settings.system.manufacturer = manufacturer

    if settings.system.manufacturer == "Thermo":
        microscope = FibSem.ThermoMicroscope(settings.hardware, settings.system.stage)
        microscope.connect_to_microscope(
            ip_address=settings.system.ip_address, port=7520
        )

    elif settings.system.manufacturer == "Tescan":
        microscope = FibSem.TescanMicroscope(ip_address=settings.system.ip_address, hardware_settings=settings.hardware, stage_settings=settings.system.stage)
        microscope.connect_to_microscope(
            ip_address=settings.system.ip_address, port=8300
        )

    elif settings.system.manufacturer == "Demo":
        microscope = FibSem.DemoMicroscope(settings.hardware, settings.system.stage)
        microscope.connect_to_microscope()

    # image_settings
    settings.image.save_path = session_path

    logging.info(f"Finished setup for session: {session}")

    return microscope, settings

Validation

check_shift_within_tolerance(dx, dy, ref_image, limit=0.25)

Check if required shift is wihtin safety limit

Source code in fibsem/validation.py
218
219
220
221
222
223
224
225
226
227
228
229
def check_shift_within_tolerance(
    dx: float, dy: float, ref_image: FibsemImage, limit: float = 0.25
) -> bool:
    """Check if required shift is wihtin safety limit"""
    # check if the cross correlation movement is within the safety limit

    pixelsize_x = ref_image.metadata.pixel_size.x
    width, height = ref_image.metadata.image_settings.resolution
    X_THRESHOLD = limit * pixelsize_x * width
    Y_THRESHOLD = limit * pixelsize_x * height

    return abs(dx) < X_THRESHOLD and abs(dy) < Y_THRESHOLD

validate_initial_microscope_state(microscope, settings)

Set the initial microscope state to default, and validate other settings.

Source code in fibsem/validation.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def validate_initial_microscope_state(
    microscope: FibsemMicroscope, settings: MicroscopeSettings
) -> None:
    """Set the initial microscope state to default, and validate other settings."""

    # validate chamber state
    _validate_chamber_state(microscope=microscope)

    # validate stage calibration (homed, linked)
    _validate_stage_calibration(microscope=microscope)

    # validate needle calibration (needle calibration, retracted)
    _validate_needle_calibration(microscope=microscope)

    # validate beam settings and calibration
    _validate_beam_system_settings(microscope=microscope, settings=settings)

    # validate scan rotation
    microscope.set("scan_rotation", value=0.0, beam_type=BeamType.ION)

validate_stage_height_for_needle_insertion(microscope, needle_stage_height_limit=0.0037)

Check if the needle can be inserted, based on the current stage height.

Parameters:

Name Type Description Default
microscope SdbMicroscopeClient

autoscript microscope limit

required
needle_stage_height_limit float

minimum stage height limit. Defaults to 3.7e-3.

0.0037

Returns:

Name Type Description
bool bool

needle is insertable

Source code in fibsem/validation.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def validate_stage_height_for_needle_insertion(
    microscope: FibsemMicroscope, needle_stage_height_limit: float = 3.7e-3
) -> bool:
    """Check if the needle can be inserted, based on the current stage height.

    Args:
        microscope (SdbMicroscopeClient): autoscript microscope limit
        needle_stage_height_limit (float, optional): minimum stage height limit. Defaults to 3.7e-3.

    Returns:
        bool: needle is insertable
    """

    stage_position = microscope.get_stage_position()

    # Unable to insert the needle if the stage height is below this limit (3.7e-3)
    return bool(stage_position.z > needle_stage_height_limit)

Detection

detect_bounding_box(mask, color, threshold=25)

Detect the bounding edge points of the mask for a given color (label)

Parameters:

Name Type Description Default
mask

the detection mask (PIL.Image)

required
color

the color of the label for the feature to detect (rgb tuple)

required
threshold

the minimum number of required pixels for a detection to count (int)

25

return:

edge_px: the pixel coordinates of the edge points of the feature list((tuple))
Source code in fibsem/detection/detection.py
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def detect_bounding_box(mask, color, threshold=25):
    """ Detect the bounding edge points of the mask for a given color (label)

    args:
        mask: the detection mask (PIL.Image)
        color: the color of the label for the feature to detect (rgb tuple)
        threshold: the minimum number of required pixels for a detection to count (int)

    return:

        edge_px: the pixel coordinates of the edge points of the feature list((tuple))
    """

    top_px, bottom_px, left_px, right_px = (0, 0), (0, 0), (0, 0), (0, 0)

    # extract class pixels
    class_mask, idx = extract_class_pixels(mask, color)

    # only return an edge point if detection is above a threshold

    if len(idx[0]) > threshold:
        # convert mask to coordinates
        px = list(zip(idx[0], idx[1]))

        # get index of each value
        top_idx = np.argmin(idx[0])
        bottom_idx = np.argmax(idx[0])
        left_idx = np.argmin(idx[1])
        right_idx = np.argmax(idx[1])

        # pixel coordinates
        top_px = px[top_idx]
        bottom_px = px[bottom_idx]
        left_px = px[left_idx]
        right_px = px[right_idx]

    # bbox should be (x0, y0), (x1, y1)
    x0 = top_px[0]
    y0 = left_px[1]
    x1 = bottom_px[0]
    y1 = right_px[1]

    bbox = (x0, y0, x1, y1)

    return bbox

detect_centre_point(mask, threshold=500)

Detect the centre (mean) point of the mask for a given color (label)

Parameters:

Name Type Description Default
mask ndarray

the detection mask (PIL.Image)

required
idx

the index of the desired class in the mask (int)

required
threshold int

the minimum number of required pixels for a detection to count (int)

500

return:

centre_px: the pixel coordinates of the centre point of the feature (tuple)
Source code in fibsem/detection/detection.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def detect_centre_point(mask: np.ndarray, threshold: int = 500) -> Point:
    """ Detect the centre (mean) point of the mask for a given color (label)

    args:
        mask: the detection mask (PIL.Image)
        idx: the index of the desired class in the mask (int)
        threshold: the minimum number of required pixels for a detection to count (int)

    return:

        centre_px: the pixel coordinates of the centre point of the feature (tuple)
    """
    centre_px = Point(x=0, y=0)
    # get mask px coordinates
    idx = np.where(mask)

    # only return a centre point if detection is above a threshold
    if len(idx[0]) > threshold:
        # get the centre point of each coordinate
        y_mid = int(np.mean(idx[0]))
        x_mid = int(np.mean(idx[1]))

        # centre coordinate as tuple
        centre_px = Point(x=x_mid, y=y_mid)
    return centre_px

detect_closest_edge_v2(mask, landing_pt)

Identify the closest edge point to the initially selected point

Parameters:

Name Type Description Default
img

base image (np.ndarray)

required
landing_px

the initial landing point pixel (tuple) (y, x) format

required

return: landing_edge_pt: the closest edge point to the intitially selected point (tuple) edges: the edge mask (np.array)

Source code in fibsem/detection/detection.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
def detect_closest_edge_v2(
    mask: np.ndarray, landing_pt: Point
) -> tuple[Point, np.ndarray]:
    """ Identify the closest edge point to the initially selected point

    args:
        img: base image (np.ndarray)
        landing_px: the initial landing point pixel (tuple) (y, x) format
    return:
        landing_edge_pt: the closest edge point to the intitially selected point (tuple)
        edges: the edge mask (np.array)
    """

    # identify edge pixels
    landing_px = (landing_pt.y, landing_pt.x)
    edge_mask = np.where(mask)
    edge_px = list(zip(edge_mask[0], edge_mask[1]))

    # set min distance
    min_dst = np.inf

    # TODO: vectorise this like
    # https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.euclidean_distances.html

    landing_edge_px = (0, 0)
    for px in edge_px:

        # distance between edges and landing point
        dst = distance.euclidean(landing_px, px)

        # select point with min
        if dst < min_dst:
            min_dst = dst
            landing_edge_px = px

    return Point(x=int(landing_edge_px[1]), y=int(landing_edge_px[0]))

extract_class_pixels(mask, color)

Extract only the pixels that are classified as the desired class (color)

Parameters:

Name Type Description Default
mask

detection mask containing all detection classes (np.array)

required
color

the color of the specified class in the mask (rgb tuple)

required
return

class_mask: the mask containing only the selected class idx: the indexes of the detected class in the mask

Source code in fibsem/detection/detection.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def extract_class_pixels(mask, color):
    """ Extract only the pixels that are classified as the desired class (color)

    args:
        mask: detection mask containing all detection classes (np.array)
        color: the color of the specified class in the mask (rgb tuple)

    return:
        class_mask: the mask containing only the selected class
        idx: the indexes of the detected class in the mask

    """
    # TODO: should the class masks be binary?? probs easier

    # extract only label pixels to find edges
    class_mask = np.zeros_like(mask)
    idx = np.where(np.all(mask == color, axis=-1))
    class_mask[idx] = color

    return class_mask, idx

plot_detection(det)

Plotting image with detected features

Parameters:

Name Type Description Default
det DetectedFeatures

detected features type

required
inverse bool

Inverses the colour of the centre crosshair of the feature. Defaults to True.

required
Source code in fibsem/detection/detection.py
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def plot_detection(det: DetectedFeatures):

    """Plotting image with detected features

    Args:
        det (DetectedFeatures): detected features type
        inverse (bool, optional): Inverses the colour of the centre crosshair of the feature. Defaults to True.
    """
    import matplotlib.pyplot as plt

    fig, ax = plt.subplots(1, 1, figsize=(12, 7))

    fig = plot_det(det, ax)

    return fig

plot_detections(dets, titles=None)

Plotting image with detected features

Parameters:

Name Type Description Default
det DetectedFeatures

detected features type

required
inverse bool

Inverses the colour of the centre crosshair of the feature. Defaults to True.

required
Source code in fibsem/detection/detection.py
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
def plot_detections(dets: list[DetectedFeatures], titles: list[str] = None) -> plt.Figure:
    """Plotting image with detected features

    Args:
        det (DetectedFeatures): detected features type
        inverse (bool, optional): Inverses the colour of the centre crosshair of the feature. Defaults to True.
    """
    import matplotlib.pyplot as plt

    if titles is None:
        titles = [f"Prediction {i}" for i in range(len(dets))]

    fig, ax = plt.subplots(1, len(dets), figsize=(25, 10))

    for i, det in enumerate(dets):

        plot_det(det, ax[i], title=titles[i], show=False)

    plt.subplots_adjust(wspace=0.05, hspace=0.05)
    plt.show()

    return fig

coordinate_distance(p1, p2)

Calculate the distance between two points in each coordinate

Source code in fibsem/detection/utils.py
46
47
48
49
def coordinate_distance(p1: Point, p2: Point):
    """Calculate the distance between two points in each coordinate"""

    return p2.x - p1.x, p2.y - p1.y

decode_segmap(image, nc=3)

Decode segmentation class mask into an RGB image mask

Source code in fibsem/detection/utils.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def decode_segmap(image, nc=3):

    """ Decode segmentation class mask into an RGB image mask"""

    # 0=background, 1=lamella, 2= needle
    label_colors = np.array([(0, 0, 0),
                                (255, 0, 0),
                                (0, 255, 0)])

    # pre-allocate r, g, b channels as zero
    r = np.zeros_like(image, dtype=np.uint8)
    g = np.zeros_like(image, dtype=np.uint8)
    b = np.zeros_like(image, dtype=np.uint8)

    # apply the class label colours to each pixel
    for l in range(0, nc):
        idx = image == l
        r[idx] = label_colors[l, 0]
        g[idx] = label_colors[l, 1]
        b[idx] = label_colors[l, 2]

    # stack rgb channels to form an image
    rgb_mask = np.stack([r, g, b], axis=2)
    return rgb_mask

extract_img_for_labelling(path, show=False)

Extract all the images that have been identified for retraining.

path: path to directory containing logged images

Source code in fibsem/detection/utils.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def extract_img_for_labelling(path, show=False):
    """Extract all the images that have been identified for retraining.

    path: path to directory containing logged images

    """
    import datetime
    import random
    import time

    import liftout
    import matplotlib.pyplot as plt
    from PIL import Image

    # mkdir for copying images to
    data_path = os.path.join(os.path.dirname(liftout.__file__), "data", "retrain")
    os.makedirs(data_path, exist_ok=True)
    print(f"Searching in {path} for retraining images...")

    # find all files for retraining (with _label postfix
    filenames = glob.glob(os.path.join(path, "/**/*label*.tif"), recursive=True)
    print(f"{len(filenames)} images found for relabelling")
    print(f"Copying images to {data_path}...")

    for i, fname in enumerate(filenames):
        # tqdm?
        print(f"Copying {i}/{len(filenames)}")
        # basename = os.path.basename(fname)
        datetime_str = datetime.datetime.fromtimestamp(time.time()).strftime(
            "%Y%m%d.%H%M%S"
        )
        basename = f"{datetime_str}.{random.random()}.tif"  # use a random number to prevent duplicates at seconds time resolution
        # print(fname, basename)
        if show:
            img = Image.open(fname)
            plt.imshow(img, cmap="gray")
            plt.show()

        source_path = os.path.join(fname)
        destination_path = os.path.join(data_path, basename)
        # print(f"Source: {source_path}")
        # print(f"Destination: {destination_path}")
        print("-" * 50)
        shutil.copyfile(source_path, destination_path)

get_scale_invariant_coordinates(point, shape)

Convert the point coordinates from image coordinates to scale invariant coordinates

Source code in fibsem/detection/utils.py
61
62
63
64
65
def get_scale_invariant_coordinates(point: Point, shape: tuple) -> Point:
    """Convert the point coordinates from image coordinates to scale invariant coordinates"""
    scaled_pt = Point(x=point.x / shape[1], y=point.y / shape[0])

    return scaled_pt

scale_coordinate_to_image(point, shape)

Scale invariant coordinates to image shape

Source code in fibsem/detection/utils.py
68
69
70
71
72
def scale_coordinate_to_image(point: Point, shape: tuple) -> Point:
    """Scale invariant coordinates to image shape"""
    scaled_pt = Point(x=int(point.x * shape[1]), y=int(point.y * shape[0]))

    return scaled_pt

scale_pixel_coordinates(px, from_image, to_image)

Scale the pixel coordinate from one image to another

Source code in fibsem/detection/utils.py
51
52
53
54
55
56
57
58
def scale_pixel_coordinates(px: Point, from_image: FibsemImage, to_image: FibsemImage) -> Point:
    """Scale the pixel coordinate from one image to another"""

    invariant_pt = get_scale_invariant_coordinates(px, from_image.data.shape)

    scaled_px = scale_coordinate_to_image(invariant_pt, to_image.data.shape)

    return scaled_px

User Interface