pytribeam.stage

Stage Module

This module contains functions for managing and controlling the stage in the microscope, including setting coordinate systems, moving the stage, and checking stage positions.

Functions

coordinate_system(microscope: tbt.Microscope, mode: tbt.StageCoordinateSystem = tbt.StageCoordinateSystem.RAW) -> bool Set the stage coordinate system mode.

stop(microscope: tbt.Microscope) -> None Immediately stop all stage movement and exit.

encoder_to_user_position(pos: tbt.StagePositionEncoder) -> tbt.StagePositionUser Convert from encoder position (m/radian units) to user position (mm/deg units).

user_to_encoder_position(pos: tbt.StagePositionUser) -> tbt.StagePositionEncoder Convert from user position (mm/deg units) to encoder position (m/radian units).

rotation_side_adjustment(rotation_side: tbt.RotationSide, initial_position_m: float, delta_pos_m: float) -> float Adjust the translation stage destination based on the rotation side.

target_position(stage: tbt.StageSettings, slice_number: int, slice_thickness_um: float) -> tbt.StagePositionUser Calculate the target position for the stage movement.

safe(microscope: tbt.Microscope, position: tbt.StagePositionUser) -> bool Check if the target position is within the stage limits.

axis_translational_in_range(current_pos_mm: float, target_pos_mm: float, stage_tolerance_um: float) -> bool Determine whether the translation axis needs to be moved.

axis_angular_in_range(current_pos_deg: float, target_pos_deg: float, stage_tolerance_deg: float) -> bool Determine whether the angular axis needs to be moved.

axis_in_range(microscope: tbt.Microscope, axis: tbt.StageAxis, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool Check whether the position of the specified axis is within the stage tolerance.

move_axis(microscope: tbt.Microscope, axis: tbt.StageAxis, target_position: tbt.StagePositionUser, num_attempts: int = cs.Constants.stage_move_attempts, stage_delay_s: float = cs.Constants.stage_move_delay_s) -> bool Move the specified stage axis to the requested user target position.

move_stage(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> bool Move the stage axes if they are outside of tolerance.

move_completed(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> bool Check whether the stage is at the target position.

home_stage(microscope: tbt.Microscope, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool Move the stage to the home position defined in pytribeam.constants.

move_to_position(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool Move the stage to the target position with error checking.

_bad_axes_message(target_position: tbt.StagePositionUser, current_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> str Generate an error message for axes that are out of tolerance.

step_start_position(microscope: tbt.Microscope, slice_number: int, operation: tbt.Step, general_settings: tbt.GeneralSettings) -> bool Move the stage to the starting position for the step.

  1#!/usr/bin/python3
  2"""
  3Stage Module
  4============
  5
  6This module contains functions for managing and controlling the stage in the microscope, including setting coordinate systems, moving the stage, and checking stage positions.
  7
  8Functions
  9---------
 10coordinate_system(microscope: tbt.Microscope, mode: tbt.StageCoordinateSystem = tbt.StageCoordinateSystem.RAW) -> bool
 11    Set the stage coordinate system mode.
 12
 13stop(microscope: tbt.Microscope) -> None
 14    Immediately stop all stage movement and exit.
 15
 16encoder_to_user_position(pos: tbt.StagePositionEncoder) -> tbt.StagePositionUser
 17    Convert from encoder position (m/radian units) to user position (mm/deg units).
 18
 19user_to_encoder_position(pos: tbt.StagePositionUser) -> tbt.StagePositionEncoder
 20    Convert from user position (mm/deg units) to encoder position (m/radian units).
 21
 22rotation_side_adjustment(rotation_side: tbt.RotationSide, initial_position_m: float, delta_pos_m: float) -> float
 23    Adjust the translation stage destination based on the rotation side.
 24
 25target_position(stage: tbt.StageSettings, slice_number: int, slice_thickness_um: float) -> tbt.StagePositionUser
 26    Calculate the target position for the stage movement.
 27
 28safe(microscope: tbt.Microscope, position: tbt.StagePositionUser) -> bool
 29    Check if the target position is within the stage limits.
 30
 31axis_translational_in_range(current_pos_mm: float, target_pos_mm: float, stage_tolerance_um: float) -> bool
 32    Determine whether the translation axis needs to be moved.
 33
 34axis_angular_in_range(current_pos_deg: float, target_pos_deg: float, stage_tolerance_deg: float) -> bool
 35    Determine whether the angular axis needs to be moved.
 36
 37axis_in_range(microscope: tbt.Microscope, axis: tbt.StageAxis, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool
 38    Check whether the position of the specified axis is within the stage tolerance.
 39
 40move_axis(microscope: tbt.Microscope, axis: tbt.StageAxis, target_position: tbt.StagePositionUser, num_attempts: int = cs.Constants.stage_move_attempts, stage_delay_s: float = cs.Constants.stage_move_delay_s) -> bool
 41    Move the specified stage axis to the requested user target position.
 42
 43move_stage(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> bool
 44    Move the stage axes if they are outside of tolerance.
 45
 46move_completed(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> bool
 47    Check whether the stage is at the target position.
 48
 49home_stage(microscope: tbt.Microscope, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool
 50    Move the stage to the home position defined in pytribeam.constants.
 51
 52move_to_position(microscope: tbt.Microscope, target_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance) -> bool
 53    Move the stage to the target position with error checking.
 54
 55_bad_axes_message(target_position: tbt.StagePositionUser, current_position: tbt.StagePositionUser, stage_tolerance: tbt.StageTolerance) -> str
 56    Generate an error message for axes that are out of tolerance.
 57
 58step_start_position(microscope: tbt.Microscope, slice_number: int, operation: tbt.Step, general_settings: tbt.GeneralSettings) -> bool
 59    Move the stage to the starting position for the step.
 60"""
 61
 62# Default python modules
 63# from functools import singledispatch
 64import os
 65from pathlib import Path
 66import time
 67import warnings
 68import math
 69from typing import NamedTuple, List, Tuple
 70import sys
 71
 72# Autoscript included modules
 73import numpy as np
 74from matplotlib import pyplot as plt
 75
 76# 3rd party module
 77
 78# Local scripts
 79import pytribeam.constants as cs
 80from pytribeam.constants import Conversions
 81import pytribeam.insertable_devices as devices
 82import pytribeam.factory as factory
 83import pytribeam.types as tbt
 84import pytribeam.utilities as ut
 85
 86
 87def coordinate_system(
 88    microscope: tbt.Microscope,
 89    mode: tbt.StageCoordinateSystem = tbt.StageCoordinateSystem.RAW,
 90) -> bool:
 91    """
 92    Set the stage coordinate system mode.
 93
 94    This function sets the stage coordinate system mode. The default mode is "RAW", which is recommended.
 95
 96    Parameters
 97    ----------
 98    microscope : tbt.Microscope
 99        The microscope object for which to set the stage coordinate system mode.
100    mode : tbt.StageCoordinateSystem, optional
101        The stage coordinate system mode to set (default is tbt.StageCoordinateSystem.RAW).
102
103    Returns
104    -------
105    bool
106        True if the coordinate system mode is set successfully.
107    """
108    if mode != tbt.StageCoordinateSystem.RAW:
109        warnings.warn(
110            f"""Warning. {mode.value} coordinate system requested
111                      instead of "RAW" stage coordinate system. This can lead
112                      to inaccurate stage movements and increases collision risk."""
113        )
114        # TODO ask for yes no continue
115    microscope.specimen.stage.set_default_coordinate_system(mode.value)
116    return True
117
118
119def stop(microscope: tbt.Microscope) -> None:
120    """
121    Immediately stop all stage movement and exit.
122
123    This function stops all stage movement and disconnects the microscope.
124
125    Parameters
126    ----------
127    microscope : tbt.Microscope
128        The microscope object for which to stop stage movement.
129
130    Raises
131    ------
132    SystemError
133        If the stage movement is halted.
134    """
135    microscope.specimen.stage.stop()
136    microscope.disconnect()
137
138    raise SystemError("Microscope stage movement was halted.")
139    # sys.exit("Microscope stage movement was halted.")
140
141
142def encoder_to_user_position(pos: tbt.StagePositionEncoder) -> tbt.StagePositionUser:
143    """
144    Convert from encoder position (m/radian units) to user position (mm/deg units).
145
146    This function converts a stage position from encoder units to user units.
147
148    Parameters
149    ----------
150    pos : tbt.StagePositionEncoder
151        The stage position in encoder units.
152
153    Returns
154    -------
155    tbt.StagePositionUser
156        The stage position in user units.
157
158    Raises
159    ------
160    TypeError
161        If the provided position is not of type tbt.StagePositionEncoder.
162    """
163    if not isinstance(pos, tbt.StagePositionEncoder):
164        raise TypeError(
165            f"provided position is not of type '<class pytribeam.types.StagePositionEncoder>', but instead of type '{type(pos)}'. Did you mean to use the function 'user_to_encoder_position'?"
166        )
167    user_pos = tbt.StagePositionUser(
168        x_mm=round(pos.x * Conversions.M_TO_MM, 6),
169        y_mm=round(pos.y * Conversions.M_TO_MM, 6),
170        z_mm=round(pos.z * Conversions.M_TO_MM, 6),
171        r_deg=round(pos.r * Conversions.RAD_TO_DEG, 6),
172        t_deg=round(pos.t * Conversions.RAD_TO_DEG, 6),
173        coordinate_system=tbt.StageCoordinateSystem(pos.coordinate_system),
174    )
175    return user_pos
176
177
178def user_to_encoder_position(pos: tbt.StagePositionUser) -> tbt.StagePositionEncoder:
179    """
180    Convert from user position (mm/deg units) to encoder position (m/radian units).
181
182    This function converts a stage position from user units to encoder units.
183
184    Parameters
185    ----------
186    pos : tbt.StagePositionUser
187        The stage position in user units.
188
189    Returns
190    -------
191    tbt.StagePositionEncoder
192        The stage position in encoder units.
193
194    Raises
195    ------
196    TypeError
197        If the provided position is not of type tbt.StagePositionUser.
198    """
199
200    if not isinstance(pos, tbt.StagePositionUser):
201        raise TypeError(
202            f"Provided position is not of type '<class pytribeam.types.StagePositionUser>', but instead of type '{type(pos)}'. Did you mean to use the function 'encoder_to_user_position'?"
203        )
204    encoder_pos = tbt.StagePositionEncoder(
205        x=pos.x_mm * Conversions.MM_TO_M,
206        y=pos.y_mm * Conversions.MM_TO_M,
207        z=pos.z_mm * Conversions.MM_TO_M,
208        r=pos.r_deg * Conversions.DEG_TO_RAD,
209        t=pos.t_deg * Conversions.DEG_TO_RAD,
210        coordinate_system=pos.coordinate_system.value,
211    )
212    return encoder_pos
213
214
215# TODO not yet implemented for other sectioning axes, below example works on Z-sectioning
216def rotation_side_adjustment(
217    rotation_side: tbt.RotationSide,
218    initial_position_m: float,
219    delta_pos_m: float,
220) -> float:
221    """
222    Adjust the translation stage destination based on the rotation side.
223
224    This function adjusts the translation stage destination based on the specified rotation side.
225
226    Parameters
227    ----------
228    rotation_side : tbt.RotationSide
229        The rotation side to consider for the adjustment.
230    initial_position_m : float
231        The initial position in meters.
232    delta_pos_m : float
233        The change in position in meters.
234
235    Returns
236    -------
237    float
238        The adjusted target position in meters.
239
240    Raises
241    ------
242    NotImplementedError
243        If the rotation side is unsupported.
244    """
245    if rotation_side == tbt.RotationSide.FSL_MILL:
246        target_m = (
247            initial_position_m - delta_pos_m
248        )  # absolute negative direction (towards laser)
249    elif rotation_side == tbt.RotationSide.FIB_MILL:
250        target_m = (
251            initial_position_m + delta_pos_m
252        )  # absolute positive direction (towards FIB)
253    elif rotation_side == tbt.RotationSide.EBEAM_NORMAL:
254        target_m = initial_position_m  # no adjustment needed
255    else:
256        raise NotImplementedError(
257            f"Unsupported RotationSide enumeration of '{rotation_side}'"
258        )
259    return target_m
260
261
262def target_position(
263    stage: tbt.StageSettings,
264    slice_number: int,
265    slice_thickness_um: float,
266) -> tbt.StagePositionUser:
267    """
268    Calculate the target position for the stage movement.
269
270    This function calculates the target position for the stage movement based on the sectioning axis, slice number, and slice thickness.
271
272    For Z-axis sectioning:
273        - Z will always incrememnt toward pole piece (positive direction)
274        - Y will increment with slice number if a non-zero pre-tilt is used
275        - Y increment direction depends on rotation of the stage relative to machining operations
276
277    For X-axis sectioning (not yet implemented, need to determine rotation_side_adjustment)
278    For Y-axis sectioning (not yet implemented, need to determine rotation_side_adjustment)
279
280    Parameters
281    ----------
282    stage : tbt.StageSettings
283        The stage settings for the experiment.
284    slice_number : int
285        The slice number for the experiment.
286    slice_thickness_um : float
287        The slice thickness in micrometers.
288
289    Returns
290    -------
291    tbt.StagePositionUser
292        The target position for the stage movement.
293
294    Raises
295    ------
296    NotImplementedError
297        If the sectioning axis is unsupported.
298    """
299
300    initial_pos_user = stage.initial_position
301    initial_pos_encoder = user_to_encoder_position(initial_pos_user)
302    slice_thickness_m = slice_thickness_um * Conversions.UM_TO_M
303    pre_tilt_rad = stage.pretilt_angle_deg * Conversions.DEG_TO_RAD
304    sectioning_axis = stage.sectioning_axis
305    rotation_side = stage.rotation_side
306    increment_factor_m = slice_thickness_m * (slice_number - 1)  # slices are 1-indexed
307
308    # only modify needed axes for each case, so initialize with original position
309    target_x_m = initial_pos_encoder.x
310    target_y_m = initial_pos_encoder.y
311    target_z_m = initial_pos_encoder.z
312    target_r_rad = initial_pos_encoder.r
313    target_t_rad = initial_pos_encoder.t
314
315    if sectioning_axis == tbt.SectioningAxis.Z:
316        delta_z_m = math.cos(pre_tilt_rad) * increment_factor_m
317        # Z always increments in positive direction
318        target_z_m = initial_pos_encoder.z + delta_z_m
319
320        # Y axis depends on rotation_side
321        delta_y_m = math.sin(pre_tilt_rad) * increment_factor_m
322
323        if rotation_side == tbt.RotationSide.FSL_MILL:
324            target_y_m = (
325                initial_pos_encoder.y - delta_y_m
326            )  # absolute negative direction (towards laser)
327        elif rotation_side == tbt.RotationSide.FIB_MILL:
328            target_y_m = (
329                initial_pos_encoder.y + delta_y_m
330            )  # absolute positive direction (towards FIB)
331        elif rotation_side == tbt.RotationSide.EBEAM_NORMAL:
332            target_y_m = initial_pos_encoder.y  # no adjustment needed
333        else:
334            raise NotImplementedError(
335                f"Unsupported RotationSide enumeration of '{rotation_side}'"
336            )
337
338    # TODO
339    elif sectioning_axis == tbt.SectioningAxis.X_POS:
340        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
341        delta_x_m = increment_factor_m
342        pass
343    elif sectioning_axis == tbt.SectioningAxis.X_NEG:
344        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
345        delta_x_m = increment_factor_m
346        pass
347    elif sectioning_axis == tbt.SectioningAxis.Y_POS:
348        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
349        delta_x_m = increment_factor_m
350        pass
351    elif sectioning_axis == tbt.SectioningAxis.Y_NEG:
352        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
353        delta_x_m = increment_factor_m
354        pass
355
356    target_pos_encoder = tbt.StagePositionEncoder(
357        x=target_x_m,
358        y=target_y_m,
359        z=target_z_m,
360        r=target_r_rad,
361        t=target_t_rad,
362        coordinate_system=tbt.StageCoordinateSystem.RAW.value,
363    )
364    target_pos_user = encoder_to_user_position(target_pos_encoder)
365    return target_pos_user
366
367
368def safe(
369    microscope: tbt.Microscope,
370    position: tbt.StagePositionUser,
371) -> bool:
372    """
373    Check if the target position is within the stage limits.
374
375    This function checks if the target position is within the stage limits.
376
377    Parameters
378    ----------
379    microscope : tbt.Microscope
380        The microscope object for which to check the stage limits.
381    position : tbt.StagePositionUser
382        The target position to check.
383
384    Returns
385    -------
386    bool
387        True if the target position is within the stage limits, False otherwise.
388    """
389
390    # returns in user units (mm, deg)
391    stage_limits = factory.stage_limits(microscope=microscope)
392
393    if not ut.in_interval(
394        position.x_mm, stage_limits.x_mm, type=tbt.IntervalType.CLOSED
395    ):
396        return False
397    if not ut.in_interval(
398        position.y_mm, stage_limits.y_mm, type=tbt.IntervalType.CLOSED
399    ):
400        return False
401    if not ut.in_interval(
402        position.z_mm, stage_limits.z_mm, type=tbt.IntervalType.CLOSED
403    ):
404        return False
405    if not ut.in_interval(
406        position.t_deg, stage_limits.t_deg, type=tbt.IntervalType.CLOSED
407    ):
408        return False
409    if not ut.in_interval(
410        position.r_deg, stage_limits.r_deg, type=tbt.IntervalType.CLOSED
411    ):
412        return False
413
414    return True
415
416
417def axis_translational_in_range(
418    current_pos_mm: float,
419    target_pos_mm: float,
420    stage_tolerance_um: float,
421) -> bool:
422    """
423    Determine whether the translation axis needs to be moved.
424
425    This function checks if the translation axis is within the specified tolerance.
426
427    Parameters
428    ----------
429    current_pos_mm : float
430        The current position of the translation axis in millimeters.
431    target_pos_mm : float
432        The target position of the translation axis in millimeters.
433    stage_tolerance_um : float
434        The tolerance for the translation axis in micrometers.
435
436    Returns
437    -------
438    bool
439        True if the translation axis is within the specified tolerance, False otherwise.
440    """
441    return ut.in_interval(
442        current_pos_mm,
443        limit=tbt.Limit(
444            min=target_pos_mm - (stage_tolerance_um * Conversions.UM_TO_MM),
445            max=target_pos_mm + (stage_tolerance_um * Conversions.UM_TO_MM),
446        ),
447        type=tbt.IntervalType.CLOSED,
448    )
449
450
451def axis_angular_in_range(
452    current_pos_deg: float,
453    target_pos_deg: float,
454    stage_tolerance_deg: float,
455) -> bool:
456    """
457    Determine whether the angular axis needs to be moved.
458
459    This function checks if the angular axis is within the specified tolerance.
460
461    Parameters
462    ----------
463    current_pos_deg : float
464        The current position of the angular axis in degrees.
465    target_pos_deg : float
466        The target position of the angular axis in degrees.
467    stage_tolerance_deg : float
468        The tolerance for the angular axis in degrees.
469
470    Returns
471    -------
472    bool
473        True if the angular axis is within the specified tolerance, False otherwise.
474    """
475    return ut.in_interval(
476        current_pos_deg,
477        limit=tbt.Limit(
478            min=target_pos_deg - (stage_tolerance_deg * Conversions.DEG_TO_RAD),
479            max=target_pos_deg + (stage_tolerance_deg * Conversions.DEG_TO_RAD),
480        ),
481        type=tbt.IntervalType.CLOSED,
482    )
483
484
485def axis_in_range(
486    microscope: tbt.Microscope,
487    axis: tbt.StageAxis,
488    target_position: tbt.StagePositionUser,
489    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
490) -> bool:
491    """
492    Check whether the position of the specified axis is within the stage tolerance.
493
494    This function checks if the position of the specified axis is within the stage tolerance.
495
496    Parameters
497    ----------
498    microscope : tbt.Microscope
499        The microscope object for which to check the axis position.
500    axis : tbt.StageAxis
501        The axis to check.
502    target_position : tbt.StagePositionUser
503        The target position to check.
504    stage_tolerance : tbt.StageTolerance, optional
505        The stage tolerance for the axis (default is cs.Constants.default_stage_tolerance).
506
507    Returns
508    -------
509    bool
510        True if the axis position is within the stage tolerance, False otherwise.
511    """
512    current_position = factory.active_stage_position_settings(
513        microscope=microscope
514    )  # user units [mm_deg]
515
516    # TODO convert to match statements at python >=3.10
517    match_db = {
518        tbt.StageAxis.X: {
519            "current_position": current_position.x_mm,
520            "target_position": target_position.x_mm,
521            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
522        },
523        tbt.StageAxis.Y: {
524            "current_position": current_position.y_mm,
525            "target_position": target_position.y_mm,
526            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
527        },
528        tbt.StageAxis.Z: {
529            "current_position": current_position.z_mm,
530            "target_position": target_position.z_mm,
531            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
532        },
533        tbt.StageAxis.R: {
534            "current_position": current_position.r_deg,
535            "target_position": target_position.r_deg,
536            "tolerance": stage_tolerance.angular_deg,
537        },
538        tbt.StageAxis.T: {
539            "current_position": current_position.t_deg,
540            "target_position": target_position.t_deg,
541            "tolerance": stage_tolerance.angular_deg,
542        },
543    }
544
545    return ut.in_interval(
546        val=match_db[axis]["current_position"],
547        limit=tbt.Limit(
548            min=match_db[axis]["target_position"] - match_db[axis]["tolerance"],
549            max=match_db[axis]["target_position"] + match_db[axis]["tolerance"],
550        ),
551        type=tbt.IntervalType.CLOSED,
552    )
553
554
555def move_axis(
556    microscope: tbt.Microscope,
557    axis: tbt.StageAxis,
558    target_position: tbt.StagePositionUser,
559    num_attempts: int = cs.Constants.stage_move_attempts,
560    stage_delay_s: float = cs.Constants.stage_move_delay_s,
561) -> bool:
562    """
563    Move the specified stage axis to the requested user target position.
564
565    This function moves the specified stage axis to the requested user target position.
566
567    Parameters
568    ----------
569    microscope : tbt.Microscope
570        The microscope object for which to move the stage axis.
571    axis : tbt.StageAxis
572        The stage axis to move.
573    target_position : tbt.StagePositionUser
574        The target position to move the axis to.
575    num_attempts : int, optional
576        The number of attempts to move the axis (default is cs.Constants.stage_move_attempts).
577    stage_delay_s : float, optional
578        The delay in seconds between attempts (default is cs.Constants.stage_move_delay_s).
579
580    Returns
581    -------
582    bool
583        True if the axis is moved to the target position successfully.
584    """
585    encoder_position = user_to_encoder_position(target_position)
586    # TODO convert to match statements at python >=3.10
587    match_db = {
588        tbt.StageAxis.X: tbt.StagePositionEncoder(x=encoder_position.x),
589        tbt.StageAxis.Y: tbt.StagePositionEncoder(y=encoder_position.y),
590        tbt.StageAxis.Z: tbt.StagePositionEncoder(z=encoder_position.z),
591        tbt.StageAxis.R: tbt.StagePositionEncoder(r=encoder_position.r),
592        tbt.StageAxis.T: tbt.StagePositionEncoder(t=encoder_position.t),
593    }
594    for _ in range(num_attempts):
595        microscope.specimen.stage.absolute_move(match_db[axis])
596        time.sleep(stage_delay_s)
597    return True
598
599
600def move_stage(
601    microscope: tbt.Microscope,
602    target_position: tbt.StagePositionUser,
603    stage_tolerance: tbt.StageTolerance,
604) -> bool:
605    """
606    Move the stage axes if they are outside of tolerance.
607
608    This function moves the stage axes to the target position if they are outside of the specified tolerance. The stage axes are moved one at a time in the following sequence:
609    - R-axis: if needed, tilt will be adjusted to 0 degrees first for safety
610    - X-axis
611    - Y-axis
612    - Z-axis
613    - T-axis
614
615    Parameters
616    ----------
617    microscope : tbt.Microscope
618        The microscope object for which to move the stage.
619    target_position : tbt.StagePositionUser
620        The target position to move the stage to.
621    stage_tolerance : tbt.StageTolerance
622        The stage tolerance for the movement.
623
624    Returns
625    -------
626    bool
627        True if the stage is moved to the target position successfully.
628    """
629
630    # ensure RAW specimen coordiantes
631    coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
632
633    # r-axis first for safety
634    if not axis_in_range(
635        microscope=microscope,
636        axis=tbt.StageAxis.R,
637        target_position=target_position,
638        stage_tolerance=stage_tolerance,
639    ):
640        # move t-axis to 0 deg (home position) first if needed
641        if not axis_in_range(
642            microscope=microscope,
643            axis=tbt.StageAxis.T,
644            target_position=cs.Constants.home_position,
645            stage_tolerance=stage_tolerance,
646        ):
647            move_axis(
648                microscope=microscope,
649                axis=tbt.StageAxis.T,
650                target_position=cs.Constants.home_position,
651            )
652
653        move_axis(
654            microscope=microscope,
655            axis=tbt.StageAxis.R,
656            target_position=target_position,
657        )
658
659    # remaining axes are independent, but sequential
660    remaining_axes = [
661        tbt.StageAxis.X,
662        tbt.StageAxis.Y,
663        tbt.StageAxis.Z,
664        tbt.StageAxis.T,
665    ]
666    for axis in remaining_axes:
667        if not axis_in_range(
668            microscope=microscope,
669            axis=axis,
670            target_position=target_position,
671            stage_tolerance=stage_tolerance,
672        ):
673            move_axis(
674                microscope=microscope,
675                axis=axis,
676                target_position=target_position,
677            )
678
679    return True
680
681
682def move_completed(
683    microscope: tbt.Microscope,
684    target_position: tbt.StagePositionUser,
685    stage_tolerance: tbt.StageTolerance,
686) -> bool:
687    """
688    Check whether the stage is at the target position.
689
690    This function checks if the stage is at the target position within the specified tolerance.
691
692    Parameters
693    ----------
694    microscope : tbt.Microscope
695        The microscope object for which to check the stage position.
696    target_position : tbt.StagePositionUser
697        The target position to check.
698    stage_tolerance : tbt.StageTolerance
699        The stage tolerance for the position.
700
701    Returns
702    -------
703    bool
704        True if the stage is at the target position, False otherwise.
705    """
706    # ensure RAW specimen coordiantes
707    coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
708
709    axes = [
710        tbt.StageAxis.X,
711        tbt.StageAxis.Y,
712        tbt.StageAxis.Z,
713        tbt.StageAxis.R,
714        tbt.StageAxis.T,
715    ]
716    for axis in axes:
717        if not axis_in_range(
718            microscope=microscope,
719            axis=axis,
720            target_position=target_position,
721            stage_tolerance=stage_tolerance,
722        ):
723            # handle -179.9 degrees of rotation being very close equivalently to 180.0 degrees, for example
724            if axis == tbt.StageAxis.R:
725                equivalent_position_pos = tbt.StagePositionUser(
726                    x_mm=target_position.x_mm,
727                    y_mm=target_position.y_mm,
728                    z_mm=target_position.z_mm,
729                    r_deg=target_position.r_deg + 360.0,
730                    t_deg=target_position.t_deg,
731                )
732                equivalent_position_neg = tbt.StagePositionUser(
733                    x_mm=target_position.x_mm,
734                    y_mm=target_position.y_mm,
735                    z_mm=target_position.z_mm,
736                    r_deg=target_position.r_deg - 360.0,
737                    t_deg=target_position.t_deg,
738                )
739                if axis_in_range(
740                    microscope=microscope,
741                    axis=axis,
742                    target_position=equivalent_position_pos,
743                    stage_tolerance=stage_tolerance,
744                ) or axis_in_range(
745                    microscope=microscope,
746                    axis=axis,
747                    target_position=equivalent_position_neg,
748                    stage_tolerance=stage_tolerance,
749                ):
750                    continue
751            return False
752    return True
753
754
755## main methods
756def home_stage(
757    microscope: tbt.Microscope,
758    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
759) -> bool:
760    """
761    Move the stage to the home position defined in pytribeam.constants.
762
763    This function moves the stage to the home position defined in pytribeam.constants, which is a special case of the move_to_position function.
764
765    Parameters
766    ----------
767    microscope : tbt.Microscope
768        The microscope object for which to move the stage.
769    stage_tolerance : tbt.StageTolerance, optional
770        The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).
771
772    Returns
773    -------
774    bool
775        True if the stage is moved to the home position successfully.
776    """
777    target_position = cs.Constants.home_position
778    move_to_position(
779        microscope=microscope,
780        target_position=target_position,
781        stage_tolerance=stage_tolerance,
782    )
783    return True
784
785
786def move_to_position(
787    microscope: tbt.Microscope,
788    target_position: tbt.StagePositionUser,
789    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
790) -> bool:
791    """
792    Move the stage to the target position with error checking.
793
794    This function moves the stage to the target position with error checking.
795
796    Parameters
797    ----------
798    microscope : tbt.Microscope
799        The microscope object for which to move the stage.
800    target_position : tbt.StagePositionUser
801        The target position to move the stage to.
802    stage_tolerance : tbt.StageTolerance, optional
803        The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).
804
805    Returns
806    -------
807    bool
808        True if the stage is moved to the target position successfully.
809
810    Raises
811    ValueError
812        If the target position is unsafe.
813    SystemError
814        If the stage move did not execute correctly.
815    """
816    # check if safe
817    if not safe(microscope=microscope, position=target_position):
818        raise ValueError(
819            f"Destination position of {target_position} is unsafe. Stage limits are:\n\t{factory.stage_limits(microscope=microscope)}. \nExiting now"
820        )
821
822    # visualize movement on CCD
823    devices.CCD_view(microscope=microscope)
824
825    # move the stage
826    move_stage(
827        microscope=microscope,
828        target_position=target_position,
829        stage_tolerance=stage_tolerance,
830    )
831
832    # stop visualization on CCD
833    devices.CCD_pause(microscope=microscope)
834
835    # check if completed #TODO clean this with a loop
836    if not move_completed(
837        microscope=microscope,
838        target_position=target_position,
839        stage_tolerance=stage_tolerance,
840    ):
841        # Try again
842        move_stage(
843            microscope=microscope,
844            target_position=target_position,
845            stage_tolerance=stage_tolerance,
846        )
847        # check if completed
848        if not move_completed(
849            microscope=microscope,
850            target_position=target_position,
851            stage_tolerance=stage_tolerance,
852        ):
853            current_position = factory.active_stage_position_settings(
854                microscope=microscope
855            )
856            error_message = _bad_axes_message(
857                target_position, current_position, stage_tolerance
858            )
859            raise SystemError(error_message)
860
861    return True
862
863
864def _bad_axes_message(
865    target_position: tbt.StagePositionUser,
866    current_position: tbt.StagePositionUser,
867    stage_tolerance: tbt.StageTolerance,
868) -> str:
869    """
870    Generate an error message for axes that are out of tolerance.
871
872    This function generates an error message for axes that are out of tolerance.
873
874    Parameters
875    ----------
876    target_position : tbt.StagePositionUser
877        The target position of the stage.
878    current_position : tbt.StagePositionUser
879        The current position of the stage.
880    stage_tolerance : tbt.StageTolerance
881        The stage tolerance for the movement.
882
883    Returns
884    -------
885    str
886        The error message for axes that are out of tolerance.
887    """
888    error_msg = "Error: Stage move did not execute correctly.\n"
889    x_error_um = np.around(
890        abs(target_position.x_mm - current_position.x_mm) * Conversions.MM_TO_UM, 3
891    )
892    y_error_um = np.around(
893        abs(target_position.y_mm - current_position.y_mm) * Conversions.MM_TO_UM, 3
894    )
895    z_error_um = np.around(
896        abs(target_position.z_mm - current_position.z_mm) * Conversions.MM_TO_UM, 3
897    )
898
899    translation_errors = [x_error_um, y_error_um, z_error_um]
900    translation_axes = ["X", "Y", "Z"]
901    for axis in range(0, len(translation_axes)):
902        if translation_errors[axis] > stage_tolerance.translational_um:
903            error_msg += f"\t {translation_axes[axis]} axis error: {translation_errors[axis]} micron, stage tolerance is {stage_tolerance.translational_um} micron\n"
904
905    t_error_deg = np.around(abs(target_position.t_deg - current_position.t_deg), 3)
906    r_error_deg = np.around(abs(target_position.r_deg - current_position.r_deg), 3)
907    angular_errors = [t_error_deg, r_error_deg]
908    angular_axes = ["T", "R"]
909    for axis in range(0, len(angular_axes)):
910        if angular_errors[axis] > stage_tolerance.translational_um:
911            error_msg += f"\t {angular_axes[axis]} axis error: {angular_errors[axis]} degrees, stage tolerance is {stage_tolerance.angular_deg} degrees\n"
912
913    return error_msg
914
915
916def step_start_position(
917    microscope: tbt.Microscope,
918    slice_number: int,
919    operation: tbt.Step,
920    general_settings: tbt.GeneralSettings,
921) -> bool:
922    """
923    Move the stage to the starting position for the step.
924
925    This function moves the stage to the starting position for the step based on the slice number, operation, and general settings.
926
927    Parameters
928    ----------
929    microscope : tbt.Microscope
930        The microscope object for which to move the stage.
931    slice_number : int
932        The slice number for the step.
933    operation : tbt.Step
934        The step object containing the operation settings.
935    general_settings : tbt.GeneralSettings
936        The general settings object.
937
938    Returns
939    -------
940    bool
941        True if the stage is moved to the starting position successfully.
942    """
943    position = target_position(
944        operation.stage,
945        slice_number=slice_number,
946        slice_thickness_um=general_settings.slice_thickness_um,
947    )
948    print("\tMoving to following position:")
949    space = " "
950    print(
951        f"\t\t{'X [mm]' + space:<10}{'Y [mm]' + space:<10}{'Z [mm]' + space:<10}{'R [deg]' + space:<10}{'T [deg]' + space:<10}"
952    )
953    print(
954        f"\t\t{round(position.x_mm,4):<10}{round(position.y_mm,4):<10}{round(position.z_mm,4):<10}{round(position.r_deg,3):<10}{round(position.t_deg,3):<10}"
955    )
956    move_to_position(
957        microscope=microscope,
958        target_position=position,
959        stage_tolerance=general_settings.stage_tolerance,
960    )
def coordinate_system( microscope: pytribeam.types.Microscope, mode: pytribeam.types.StageCoordinateSystem = <StageCoordinateSystem.RAW: 'Raw'>) -> bool:
 88def coordinate_system(
 89    microscope: tbt.Microscope,
 90    mode: tbt.StageCoordinateSystem = tbt.StageCoordinateSystem.RAW,
 91) -> bool:
 92    """
 93    Set the stage coordinate system mode.
 94
 95    This function sets the stage coordinate system mode. The default mode is "RAW", which is recommended.
 96
 97    Parameters
 98    ----------
 99    microscope : tbt.Microscope
100        The microscope object for which to set the stage coordinate system mode.
101    mode : tbt.StageCoordinateSystem, optional
102        The stage coordinate system mode to set (default is tbt.StageCoordinateSystem.RAW).
103
104    Returns
105    -------
106    bool
107        True if the coordinate system mode is set successfully.
108    """
109    if mode != tbt.StageCoordinateSystem.RAW:
110        warnings.warn(
111            f"""Warning. {mode.value} coordinate system requested
112                      instead of "RAW" stage coordinate system. This can lead
113                      to inaccurate stage movements and increases collision risk."""
114        )
115        # TODO ask for yes no continue
116    microscope.specimen.stage.set_default_coordinate_system(mode.value)
117    return True

Set the stage coordinate system mode.

This function sets the stage coordinate system mode. The default mode is "RAW", which is recommended.

Parameters

microscope : tbt.Microscope The microscope object for which to set the stage coordinate system mode. mode : tbt.StageCoordinateSystem, optional The stage coordinate system mode to set (default is tbt.StageCoordinateSystem.RAW).

Returns

bool True if the coordinate system mode is set successfully.

def stop(microscope: pytribeam.types.Microscope) -> None:
120def stop(microscope: tbt.Microscope) -> None:
121    """
122    Immediately stop all stage movement and exit.
123
124    This function stops all stage movement and disconnects the microscope.
125
126    Parameters
127    ----------
128    microscope : tbt.Microscope
129        The microscope object for which to stop stage movement.
130
131    Raises
132    ------
133    SystemError
134        If the stage movement is halted.
135    """
136    microscope.specimen.stage.stop()
137    microscope.disconnect()
138
139    raise SystemError("Microscope stage movement was halted.")
140    # sys.exit("Microscope stage movement was halted.")

Immediately stop all stage movement and exit.

This function stops all stage movement and disconnects the microscope.

Parameters

microscope : tbt.Microscope The microscope object for which to stop stage movement.

Raises

SystemError If the stage movement is halted.

def encoder_to_user_position( pos: pytribeam.types.StagePositionEncoder) -> pytribeam.types.StagePositionUser:
143def encoder_to_user_position(pos: tbt.StagePositionEncoder) -> tbt.StagePositionUser:
144    """
145    Convert from encoder position (m/radian units) to user position (mm/deg units).
146
147    This function converts a stage position from encoder units to user units.
148
149    Parameters
150    ----------
151    pos : tbt.StagePositionEncoder
152        The stage position in encoder units.
153
154    Returns
155    -------
156    tbt.StagePositionUser
157        The stage position in user units.
158
159    Raises
160    ------
161    TypeError
162        If the provided position is not of type tbt.StagePositionEncoder.
163    """
164    if not isinstance(pos, tbt.StagePositionEncoder):
165        raise TypeError(
166            f"provided position is not of type '<class pytribeam.types.StagePositionEncoder>', but instead of type '{type(pos)}'. Did you mean to use the function 'user_to_encoder_position'?"
167        )
168    user_pos = tbt.StagePositionUser(
169        x_mm=round(pos.x * Conversions.M_TO_MM, 6),
170        y_mm=round(pos.y * Conversions.M_TO_MM, 6),
171        z_mm=round(pos.z * Conversions.M_TO_MM, 6),
172        r_deg=round(pos.r * Conversions.RAD_TO_DEG, 6),
173        t_deg=round(pos.t * Conversions.RAD_TO_DEG, 6),
174        coordinate_system=tbt.StageCoordinateSystem(pos.coordinate_system),
175    )
176    return user_pos

Convert from encoder position (m/radian units) to user position (mm/deg units).

This function converts a stage position from encoder units to user units.

Parameters

pos : tbt.StagePositionEncoder The stage position in encoder units.

Returns

tbt.StagePositionUser The stage position in user units.

Raises

TypeError If the provided position is not of type tbt.StagePositionEncoder.

def user_to_encoder_position( pos: pytribeam.types.StagePositionUser) -> pytribeam.types.StagePositionEncoder:
179def user_to_encoder_position(pos: tbt.StagePositionUser) -> tbt.StagePositionEncoder:
180    """
181    Convert from user position (mm/deg units) to encoder position (m/radian units).
182
183    This function converts a stage position from user units to encoder units.
184
185    Parameters
186    ----------
187    pos : tbt.StagePositionUser
188        The stage position in user units.
189
190    Returns
191    -------
192    tbt.StagePositionEncoder
193        The stage position in encoder units.
194
195    Raises
196    ------
197    TypeError
198        If the provided position is not of type tbt.StagePositionUser.
199    """
200
201    if not isinstance(pos, tbt.StagePositionUser):
202        raise TypeError(
203            f"Provided position is not of type '<class pytribeam.types.StagePositionUser>', but instead of type '{type(pos)}'. Did you mean to use the function 'encoder_to_user_position'?"
204        )
205    encoder_pos = tbt.StagePositionEncoder(
206        x=pos.x_mm * Conversions.MM_TO_M,
207        y=pos.y_mm * Conversions.MM_TO_M,
208        z=pos.z_mm * Conversions.MM_TO_M,
209        r=pos.r_deg * Conversions.DEG_TO_RAD,
210        t=pos.t_deg * Conversions.DEG_TO_RAD,
211        coordinate_system=pos.coordinate_system.value,
212    )
213    return encoder_pos

Convert from user position (mm/deg units) to encoder position (m/radian units).

This function converts a stage position from user units to encoder units.

Parameters

pos : tbt.StagePositionUser The stage position in user units.

Returns

tbt.StagePositionEncoder The stage position in encoder units.

Raises

TypeError If the provided position is not of type tbt.StagePositionUser.

def rotation_side_adjustment( rotation_side: pytribeam.types.RotationSide, initial_position_m: float, delta_pos_m: float) -> float:
217def rotation_side_adjustment(
218    rotation_side: tbt.RotationSide,
219    initial_position_m: float,
220    delta_pos_m: float,
221) -> float:
222    """
223    Adjust the translation stage destination based on the rotation side.
224
225    This function adjusts the translation stage destination based on the specified rotation side.
226
227    Parameters
228    ----------
229    rotation_side : tbt.RotationSide
230        The rotation side to consider for the adjustment.
231    initial_position_m : float
232        The initial position in meters.
233    delta_pos_m : float
234        The change in position in meters.
235
236    Returns
237    -------
238    float
239        The adjusted target position in meters.
240
241    Raises
242    ------
243    NotImplementedError
244        If the rotation side is unsupported.
245    """
246    if rotation_side == tbt.RotationSide.FSL_MILL:
247        target_m = (
248            initial_position_m - delta_pos_m
249        )  # absolute negative direction (towards laser)
250    elif rotation_side == tbt.RotationSide.FIB_MILL:
251        target_m = (
252            initial_position_m + delta_pos_m
253        )  # absolute positive direction (towards FIB)
254    elif rotation_side == tbt.RotationSide.EBEAM_NORMAL:
255        target_m = initial_position_m  # no adjustment needed
256    else:
257        raise NotImplementedError(
258            f"Unsupported RotationSide enumeration of '{rotation_side}'"
259        )
260    return target_m

Adjust the translation stage destination based on the rotation side.

This function adjusts the translation stage destination based on the specified rotation side.

Parameters

rotation_side : tbt.RotationSide The rotation side to consider for the adjustment. initial_position_m : float The initial position in meters. delta_pos_m : float The change in position in meters.

Returns

float The adjusted target position in meters.

Raises

NotImplementedError If the rotation side is unsupported.

def target_position( stage: pytribeam.types.StageSettings, slice_number: int, slice_thickness_um: float) -> pytribeam.types.StagePositionUser:
263def target_position(
264    stage: tbt.StageSettings,
265    slice_number: int,
266    slice_thickness_um: float,
267) -> tbt.StagePositionUser:
268    """
269    Calculate the target position for the stage movement.
270
271    This function calculates the target position for the stage movement based on the sectioning axis, slice number, and slice thickness.
272
273    For Z-axis sectioning:
274        - Z will always incrememnt toward pole piece (positive direction)
275        - Y will increment with slice number if a non-zero pre-tilt is used
276        - Y increment direction depends on rotation of the stage relative to machining operations
277
278    For X-axis sectioning (not yet implemented, need to determine rotation_side_adjustment)
279    For Y-axis sectioning (not yet implemented, need to determine rotation_side_adjustment)
280
281    Parameters
282    ----------
283    stage : tbt.StageSettings
284        The stage settings for the experiment.
285    slice_number : int
286        The slice number for the experiment.
287    slice_thickness_um : float
288        The slice thickness in micrometers.
289
290    Returns
291    -------
292    tbt.StagePositionUser
293        The target position for the stage movement.
294
295    Raises
296    ------
297    NotImplementedError
298        If the sectioning axis is unsupported.
299    """
300
301    initial_pos_user = stage.initial_position
302    initial_pos_encoder = user_to_encoder_position(initial_pos_user)
303    slice_thickness_m = slice_thickness_um * Conversions.UM_TO_M
304    pre_tilt_rad = stage.pretilt_angle_deg * Conversions.DEG_TO_RAD
305    sectioning_axis = stage.sectioning_axis
306    rotation_side = stage.rotation_side
307    increment_factor_m = slice_thickness_m * (slice_number - 1)  # slices are 1-indexed
308
309    # only modify needed axes for each case, so initialize with original position
310    target_x_m = initial_pos_encoder.x
311    target_y_m = initial_pos_encoder.y
312    target_z_m = initial_pos_encoder.z
313    target_r_rad = initial_pos_encoder.r
314    target_t_rad = initial_pos_encoder.t
315
316    if sectioning_axis == tbt.SectioningAxis.Z:
317        delta_z_m = math.cos(pre_tilt_rad) * increment_factor_m
318        # Z always increments in positive direction
319        target_z_m = initial_pos_encoder.z + delta_z_m
320
321        # Y axis depends on rotation_side
322        delta_y_m = math.sin(pre_tilt_rad) * increment_factor_m
323
324        if rotation_side == tbt.RotationSide.FSL_MILL:
325            target_y_m = (
326                initial_pos_encoder.y - delta_y_m
327            )  # absolute negative direction (towards laser)
328        elif rotation_side == tbt.RotationSide.FIB_MILL:
329            target_y_m = (
330                initial_pos_encoder.y + delta_y_m
331            )  # absolute positive direction (towards FIB)
332        elif rotation_side == tbt.RotationSide.EBEAM_NORMAL:
333            target_y_m = initial_pos_encoder.y  # no adjustment needed
334        else:
335            raise NotImplementedError(
336                f"Unsupported RotationSide enumeration of '{rotation_side}'"
337            )
338
339    # TODO
340    elif sectioning_axis == tbt.SectioningAxis.X_POS:
341        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
342        delta_x_m = increment_factor_m
343        pass
344    elif sectioning_axis == tbt.SectioningAxis.X_NEG:
345        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
346        delta_x_m = increment_factor_m
347        pass
348    elif sectioning_axis == tbt.SectioningAxis.Y_POS:
349        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
350        delta_x_m = increment_factor_m
351        pass
352    elif sectioning_axis == tbt.SectioningAxis.Y_NEG:
353        raise NotImplementedError("Currently only Z-axis sectioning is supported.")
354        delta_x_m = increment_factor_m
355        pass
356
357    target_pos_encoder = tbt.StagePositionEncoder(
358        x=target_x_m,
359        y=target_y_m,
360        z=target_z_m,
361        r=target_r_rad,
362        t=target_t_rad,
363        coordinate_system=tbt.StageCoordinateSystem.RAW.value,
364    )
365    target_pos_user = encoder_to_user_position(target_pos_encoder)
366    return target_pos_user

Calculate the target position for the stage movement.

This function calculates the target position for the stage movement based on the sectioning axis, slice number, and slice thickness.

For Z-axis sectioning: - Z will always incrememnt toward pole piece (positive direction) - Y will increment with slice number if a non-zero pre-tilt is used - Y increment direction depends on rotation of the stage relative to machining operations

For X-axis sectioning (not yet implemented, need to determine rotation_side_adjustment) For Y-axis sectioning (not yet implemented, need to determine rotation_side_adjustment)

Parameters

stage : tbt.StageSettings The stage settings for the experiment. slice_number : int The slice number for the experiment. slice_thickness_um : float The slice thickness in micrometers.

Returns

tbt.StagePositionUser The target position for the stage movement.

Raises

NotImplementedError If the sectioning axis is unsupported.

def safe( microscope: pytribeam.types.Microscope, position: pytribeam.types.StagePositionUser) -> bool:
369def safe(
370    microscope: tbt.Microscope,
371    position: tbt.StagePositionUser,
372) -> bool:
373    """
374    Check if the target position is within the stage limits.
375
376    This function checks if the target position is within the stage limits.
377
378    Parameters
379    ----------
380    microscope : tbt.Microscope
381        The microscope object for which to check the stage limits.
382    position : tbt.StagePositionUser
383        The target position to check.
384
385    Returns
386    -------
387    bool
388        True if the target position is within the stage limits, False otherwise.
389    """
390
391    # returns in user units (mm, deg)
392    stage_limits = factory.stage_limits(microscope=microscope)
393
394    if not ut.in_interval(
395        position.x_mm, stage_limits.x_mm, type=tbt.IntervalType.CLOSED
396    ):
397        return False
398    if not ut.in_interval(
399        position.y_mm, stage_limits.y_mm, type=tbt.IntervalType.CLOSED
400    ):
401        return False
402    if not ut.in_interval(
403        position.z_mm, stage_limits.z_mm, type=tbt.IntervalType.CLOSED
404    ):
405        return False
406    if not ut.in_interval(
407        position.t_deg, stage_limits.t_deg, type=tbt.IntervalType.CLOSED
408    ):
409        return False
410    if not ut.in_interval(
411        position.r_deg, stage_limits.r_deg, type=tbt.IntervalType.CLOSED
412    ):
413        return False
414
415    return True

Check if the target position is within the stage limits.

This function checks if the target position is within the stage limits.

Parameters

microscope : tbt.Microscope The microscope object for which to check the stage limits. position : tbt.StagePositionUser The target position to check.

Returns

bool True if the target position is within the stage limits, False otherwise.

def axis_translational_in_range( current_pos_mm: float, target_pos_mm: float, stage_tolerance_um: float) -> bool:
418def axis_translational_in_range(
419    current_pos_mm: float,
420    target_pos_mm: float,
421    stage_tolerance_um: float,
422) -> bool:
423    """
424    Determine whether the translation axis needs to be moved.
425
426    This function checks if the translation axis is within the specified tolerance.
427
428    Parameters
429    ----------
430    current_pos_mm : float
431        The current position of the translation axis in millimeters.
432    target_pos_mm : float
433        The target position of the translation axis in millimeters.
434    stage_tolerance_um : float
435        The tolerance for the translation axis in micrometers.
436
437    Returns
438    -------
439    bool
440        True if the translation axis is within the specified tolerance, False otherwise.
441    """
442    return ut.in_interval(
443        current_pos_mm,
444        limit=tbt.Limit(
445            min=target_pos_mm - (stage_tolerance_um * Conversions.UM_TO_MM),
446            max=target_pos_mm + (stage_tolerance_um * Conversions.UM_TO_MM),
447        ),
448        type=tbt.IntervalType.CLOSED,
449    )

Determine whether the translation axis needs to be moved.

This function checks if the translation axis is within the specified tolerance.

Parameters

current_pos_mm : float The current position of the translation axis in millimeters. target_pos_mm : float The target position of the translation axis in millimeters. stage_tolerance_um : float The tolerance for the translation axis in micrometers.

Returns

bool True if the translation axis is within the specified tolerance, False otherwise.

def axis_angular_in_range( current_pos_deg: float, target_pos_deg: float, stage_tolerance_deg: float) -> bool:
452def axis_angular_in_range(
453    current_pos_deg: float,
454    target_pos_deg: float,
455    stage_tolerance_deg: float,
456) -> bool:
457    """
458    Determine whether the angular axis needs to be moved.
459
460    This function checks if the angular axis is within the specified tolerance.
461
462    Parameters
463    ----------
464    current_pos_deg : float
465        The current position of the angular axis in degrees.
466    target_pos_deg : float
467        The target position of the angular axis in degrees.
468    stage_tolerance_deg : float
469        The tolerance for the angular axis in degrees.
470
471    Returns
472    -------
473    bool
474        True if the angular axis is within the specified tolerance, False otherwise.
475    """
476    return ut.in_interval(
477        current_pos_deg,
478        limit=tbt.Limit(
479            min=target_pos_deg - (stage_tolerance_deg * Conversions.DEG_TO_RAD),
480            max=target_pos_deg + (stage_tolerance_deg * Conversions.DEG_TO_RAD),
481        ),
482        type=tbt.IntervalType.CLOSED,
483    )

Determine whether the angular axis needs to be moved.

This function checks if the angular axis is within the specified tolerance.

Parameters

current_pos_deg : float The current position of the angular axis in degrees. target_pos_deg : float The target position of the angular axis in degrees. stage_tolerance_deg : float The tolerance for the angular axis in degrees.

Returns

bool True if the angular axis is within the specified tolerance, False otherwise.

def axis_in_range( microscope: pytribeam.types.Microscope, axis: pytribeam.types.StageAxis, target_position: pytribeam.types.StagePositionUser, stage_tolerance: pytribeam.types.StageTolerance = StageTolerance(translational_um=0.5, angular_deg=0.02)) -> bool:
486def axis_in_range(
487    microscope: tbt.Microscope,
488    axis: tbt.StageAxis,
489    target_position: tbt.StagePositionUser,
490    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
491) -> bool:
492    """
493    Check whether the position of the specified axis is within the stage tolerance.
494
495    This function checks if the position of the specified axis is within the stage tolerance.
496
497    Parameters
498    ----------
499    microscope : tbt.Microscope
500        The microscope object for which to check the axis position.
501    axis : tbt.StageAxis
502        The axis to check.
503    target_position : tbt.StagePositionUser
504        The target position to check.
505    stage_tolerance : tbt.StageTolerance, optional
506        The stage tolerance for the axis (default is cs.Constants.default_stage_tolerance).
507
508    Returns
509    -------
510    bool
511        True if the axis position is within the stage tolerance, False otherwise.
512    """
513    current_position = factory.active_stage_position_settings(
514        microscope=microscope
515    )  # user units [mm_deg]
516
517    # TODO convert to match statements at python >=3.10
518    match_db = {
519        tbt.StageAxis.X: {
520            "current_position": current_position.x_mm,
521            "target_position": target_position.x_mm,
522            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
523        },
524        tbt.StageAxis.Y: {
525            "current_position": current_position.y_mm,
526            "target_position": target_position.y_mm,
527            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
528        },
529        tbt.StageAxis.Z: {
530            "current_position": current_position.z_mm,
531            "target_position": target_position.z_mm,
532            "tolerance": stage_tolerance.translational_um * Conversions.UM_TO_MM,
533        },
534        tbt.StageAxis.R: {
535            "current_position": current_position.r_deg,
536            "target_position": target_position.r_deg,
537            "tolerance": stage_tolerance.angular_deg,
538        },
539        tbt.StageAxis.T: {
540            "current_position": current_position.t_deg,
541            "target_position": target_position.t_deg,
542            "tolerance": stage_tolerance.angular_deg,
543        },
544    }
545
546    return ut.in_interval(
547        val=match_db[axis]["current_position"],
548        limit=tbt.Limit(
549            min=match_db[axis]["target_position"] - match_db[axis]["tolerance"],
550            max=match_db[axis]["target_position"] + match_db[axis]["tolerance"],
551        ),
552        type=tbt.IntervalType.CLOSED,
553    )

Check whether the position of the specified axis is within the stage tolerance.

This function checks if the position of the specified axis is within the stage tolerance.

Parameters

microscope : tbt.Microscope The microscope object for which to check the axis position. axis : tbt.StageAxis The axis to check. target_position : tbt.StagePositionUser The target position to check. stage_tolerance : tbt.StageTolerance, optional The stage tolerance for the axis (default is cs.Constants.default_stage_tolerance).

Returns

bool True if the axis position is within the stage tolerance, False otherwise.

def move_axis( microscope: pytribeam.types.Microscope, axis: pytribeam.types.StageAxis, target_position: pytribeam.types.StagePositionUser, num_attempts: int = 2, stage_delay_s: float = 0.5) -> bool:
556def move_axis(
557    microscope: tbt.Microscope,
558    axis: tbt.StageAxis,
559    target_position: tbt.StagePositionUser,
560    num_attempts: int = cs.Constants.stage_move_attempts,
561    stage_delay_s: float = cs.Constants.stage_move_delay_s,
562) -> bool:
563    """
564    Move the specified stage axis to the requested user target position.
565
566    This function moves the specified stage axis to the requested user target position.
567
568    Parameters
569    ----------
570    microscope : tbt.Microscope
571        The microscope object for which to move the stage axis.
572    axis : tbt.StageAxis
573        The stage axis to move.
574    target_position : tbt.StagePositionUser
575        The target position to move the axis to.
576    num_attempts : int, optional
577        The number of attempts to move the axis (default is cs.Constants.stage_move_attempts).
578    stage_delay_s : float, optional
579        The delay in seconds between attempts (default is cs.Constants.stage_move_delay_s).
580
581    Returns
582    -------
583    bool
584        True if the axis is moved to the target position successfully.
585    """
586    encoder_position = user_to_encoder_position(target_position)
587    # TODO convert to match statements at python >=3.10
588    match_db = {
589        tbt.StageAxis.X: tbt.StagePositionEncoder(x=encoder_position.x),
590        tbt.StageAxis.Y: tbt.StagePositionEncoder(y=encoder_position.y),
591        tbt.StageAxis.Z: tbt.StagePositionEncoder(z=encoder_position.z),
592        tbt.StageAxis.R: tbt.StagePositionEncoder(r=encoder_position.r),
593        tbt.StageAxis.T: tbt.StagePositionEncoder(t=encoder_position.t),
594    }
595    for _ in range(num_attempts):
596        microscope.specimen.stage.absolute_move(match_db[axis])
597        time.sleep(stage_delay_s)
598    return True

Move the specified stage axis to the requested user target position.

This function moves the specified stage axis to the requested user target position.

Parameters

microscope : tbt.Microscope The microscope object for which to move the stage axis. axis : tbt.StageAxis The stage axis to move. target_position : tbt.StagePositionUser The target position to move the axis to. num_attempts : int, optional The number of attempts to move the axis (default is cs.Constants.stage_move_attempts). stage_delay_s : float, optional The delay in seconds between attempts (default is cs.Constants.stage_move_delay_s).

Returns

bool True if the axis is moved to the target position successfully.

def move_stage( microscope: pytribeam.types.Microscope, target_position: pytribeam.types.StagePositionUser, stage_tolerance: pytribeam.types.StageTolerance) -> bool:
601def move_stage(
602    microscope: tbt.Microscope,
603    target_position: tbt.StagePositionUser,
604    stage_tolerance: tbt.StageTolerance,
605) -> bool:
606    """
607    Move the stage axes if they are outside of tolerance.
608
609    This function moves the stage axes to the target position if they are outside of the specified tolerance. The stage axes are moved one at a time in the following sequence:
610    - R-axis: if needed, tilt will be adjusted to 0 degrees first for safety
611    - X-axis
612    - Y-axis
613    - Z-axis
614    - T-axis
615
616    Parameters
617    ----------
618    microscope : tbt.Microscope
619        The microscope object for which to move the stage.
620    target_position : tbt.StagePositionUser
621        The target position to move the stage to.
622    stage_tolerance : tbt.StageTolerance
623        The stage tolerance for the movement.
624
625    Returns
626    -------
627    bool
628        True if the stage is moved to the target position successfully.
629    """
630
631    # ensure RAW specimen coordiantes
632    coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
633
634    # r-axis first for safety
635    if not axis_in_range(
636        microscope=microscope,
637        axis=tbt.StageAxis.R,
638        target_position=target_position,
639        stage_tolerance=stage_tolerance,
640    ):
641        # move t-axis to 0 deg (home position) first if needed
642        if not axis_in_range(
643            microscope=microscope,
644            axis=tbt.StageAxis.T,
645            target_position=cs.Constants.home_position,
646            stage_tolerance=stage_tolerance,
647        ):
648            move_axis(
649                microscope=microscope,
650                axis=tbt.StageAxis.T,
651                target_position=cs.Constants.home_position,
652            )
653
654        move_axis(
655            microscope=microscope,
656            axis=tbt.StageAxis.R,
657            target_position=target_position,
658        )
659
660    # remaining axes are independent, but sequential
661    remaining_axes = [
662        tbt.StageAxis.X,
663        tbt.StageAxis.Y,
664        tbt.StageAxis.Z,
665        tbt.StageAxis.T,
666    ]
667    for axis in remaining_axes:
668        if not axis_in_range(
669            microscope=microscope,
670            axis=axis,
671            target_position=target_position,
672            stage_tolerance=stage_tolerance,
673        ):
674            move_axis(
675                microscope=microscope,
676                axis=axis,
677                target_position=target_position,
678            )
679
680    return True

Move the stage axes if they are outside of tolerance.

This function moves the stage axes to the target position if they are outside of the specified tolerance. The stage axes are moved one at a time in the following sequence:

  • R-axis: if needed, tilt will be adjusted to 0 degrees first for safety
  • X-axis
  • Y-axis
  • Z-axis
  • T-axis

Parameters

microscope : tbt.Microscope The microscope object for which to move the stage. target_position : tbt.StagePositionUser The target position to move the stage to. stage_tolerance : tbt.StageTolerance The stage tolerance for the movement.

Returns

bool True if the stage is moved to the target position successfully.

def move_completed( microscope: pytribeam.types.Microscope, target_position: pytribeam.types.StagePositionUser, stage_tolerance: pytribeam.types.StageTolerance) -> bool:
683def move_completed(
684    microscope: tbt.Microscope,
685    target_position: tbt.StagePositionUser,
686    stage_tolerance: tbt.StageTolerance,
687) -> bool:
688    """
689    Check whether the stage is at the target position.
690
691    This function checks if the stage is at the target position within the specified tolerance.
692
693    Parameters
694    ----------
695    microscope : tbt.Microscope
696        The microscope object for which to check the stage position.
697    target_position : tbt.StagePositionUser
698        The target position to check.
699    stage_tolerance : tbt.StageTolerance
700        The stage tolerance for the position.
701
702    Returns
703    -------
704    bool
705        True if the stage is at the target position, False otherwise.
706    """
707    # ensure RAW specimen coordiantes
708    coordinate_system(microscope=microscope, mode=tbt.StageCoordinateSystem.RAW)
709
710    axes = [
711        tbt.StageAxis.X,
712        tbt.StageAxis.Y,
713        tbt.StageAxis.Z,
714        tbt.StageAxis.R,
715        tbt.StageAxis.T,
716    ]
717    for axis in axes:
718        if not axis_in_range(
719            microscope=microscope,
720            axis=axis,
721            target_position=target_position,
722            stage_tolerance=stage_tolerance,
723        ):
724            # handle -179.9 degrees of rotation being very close equivalently to 180.0 degrees, for example
725            if axis == tbt.StageAxis.R:
726                equivalent_position_pos = tbt.StagePositionUser(
727                    x_mm=target_position.x_mm,
728                    y_mm=target_position.y_mm,
729                    z_mm=target_position.z_mm,
730                    r_deg=target_position.r_deg + 360.0,
731                    t_deg=target_position.t_deg,
732                )
733                equivalent_position_neg = tbt.StagePositionUser(
734                    x_mm=target_position.x_mm,
735                    y_mm=target_position.y_mm,
736                    z_mm=target_position.z_mm,
737                    r_deg=target_position.r_deg - 360.0,
738                    t_deg=target_position.t_deg,
739                )
740                if axis_in_range(
741                    microscope=microscope,
742                    axis=axis,
743                    target_position=equivalent_position_pos,
744                    stage_tolerance=stage_tolerance,
745                ) or axis_in_range(
746                    microscope=microscope,
747                    axis=axis,
748                    target_position=equivalent_position_neg,
749                    stage_tolerance=stage_tolerance,
750                ):
751                    continue
752            return False
753    return True

Check whether the stage is at the target position.

This function checks if the stage is at the target position within the specified tolerance.

Parameters

microscope : tbt.Microscope The microscope object for which to check the stage position. target_position : tbt.StagePositionUser The target position to check. stage_tolerance : tbt.StageTolerance The stage tolerance for the position.

Returns

bool True if the stage is at the target position, False otherwise.

def home_stage( microscope: pytribeam.types.Microscope, stage_tolerance: pytribeam.types.StageTolerance = StageTolerance(translational_um=0.5, angular_deg=0.02)) -> bool:
757def home_stage(
758    microscope: tbt.Microscope,
759    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
760) -> bool:
761    """
762    Move the stage to the home position defined in pytribeam.constants.
763
764    This function moves the stage to the home position defined in pytribeam.constants, which is a special case of the move_to_position function.
765
766    Parameters
767    ----------
768    microscope : tbt.Microscope
769        The microscope object for which to move the stage.
770    stage_tolerance : tbt.StageTolerance, optional
771        The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).
772
773    Returns
774    -------
775    bool
776        True if the stage is moved to the home position successfully.
777    """
778    target_position = cs.Constants.home_position
779    move_to_position(
780        microscope=microscope,
781        target_position=target_position,
782        stage_tolerance=stage_tolerance,
783    )
784    return True

Move the stage to the home position defined in pytribeam.constants.

This function moves the stage to the home position defined in pytribeam.constants, which is a special case of the move_to_position function.

Parameters

microscope : tbt.Microscope The microscope object for which to move the stage. stage_tolerance : tbt.StageTolerance, optional The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).

Returns

bool True if the stage is moved to the home position successfully.

def move_to_position( microscope: pytribeam.types.Microscope, target_position: pytribeam.types.StagePositionUser, stage_tolerance: pytribeam.types.StageTolerance = StageTolerance(translational_um=0.5, angular_deg=0.02)) -> bool:
787def move_to_position(
788    microscope: tbt.Microscope,
789    target_position: tbt.StagePositionUser,
790    stage_tolerance: tbt.StageTolerance = cs.Constants.default_stage_tolerance,
791) -> bool:
792    """
793    Move the stage to the target position with error checking.
794
795    This function moves the stage to the target position with error checking.
796
797    Parameters
798    ----------
799    microscope : tbt.Microscope
800        The microscope object for which to move the stage.
801    target_position : tbt.StagePositionUser
802        The target position to move the stage to.
803    stage_tolerance : tbt.StageTolerance, optional
804        The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).
805
806    Returns
807    -------
808    bool
809        True if the stage is moved to the target position successfully.
810
811    Raises
812    ValueError
813        If the target position is unsafe.
814    SystemError
815        If the stage move did not execute correctly.
816    """
817    # check if safe
818    if not safe(microscope=microscope, position=target_position):
819        raise ValueError(
820            f"Destination position of {target_position} is unsafe. Stage limits are:\n\t{factory.stage_limits(microscope=microscope)}. \nExiting now"
821        )
822
823    # visualize movement on CCD
824    devices.CCD_view(microscope=microscope)
825
826    # move the stage
827    move_stage(
828        microscope=microscope,
829        target_position=target_position,
830        stage_tolerance=stage_tolerance,
831    )
832
833    # stop visualization on CCD
834    devices.CCD_pause(microscope=microscope)
835
836    # check if completed #TODO clean this with a loop
837    if not move_completed(
838        microscope=microscope,
839        target_position=target_position,
840        stage_tolerance=stage_tolerance,
841    ):
842        # Try again
843        move_stage(
844            microscope=microscope,
845            target_position=target_position,
846            stage_tolerance=stage_tolerance,
847        )
848        # check if completed
849        if not move_completed(
850            microscope=microscope,
851            target_position=target_position,
852            stage_tolerance=stage_tolerance,
853        ):
854            current_position = factory.active_stage_position_settings(
855                microscope=microscope
856            )
857            error_message = _bad_axes_message(
858                target_position, current_position, stage_tolerance
859            )
860            raise SystemError(error_message)
861
862    return True

Move the stage to the target position with error checking.

This function moves the stage to the target position with error checking.

Parameters

microscope : tbt.Microscope The microscope object for which to move the stage. target_position : tbt.StagePositionUser The target position to move the stage to. stage_tolerance : tbt.StageTolerance, optional The stage tolerance for the movement (default is cs.Constants.default_stage_tolerance).

Returns

bool True if the stage is moved to the target position successfully.

Raises ValueError If the target position is unsafe. SystemError If the stage move did not execute correctly.

def step_start_position( microscope: pytribeam.types.Microscope, slice_number: int, operation: pytribeam.types.Step, general_settings: pytribeam.types.GeneralSettings) -> bool:
917def step_start_position(
918    microscope: tbt.Microscope,
919    slice_number: int,
920    operation: tbt.Step,
921    general_settings: tbt.GeneralSettings,
922) -> bool:
923    """
924    Move the stage to the starting position for the step.
925
926    This function moves the stage to the starting position for the step based on the slice number, operation, and general settings.
927
928    Parameters
929    ----------
930    microscope : tbt.Microscope
931        The microscope object for which to move the stage.
932    slice_number : int
933        The slice number for the step.
934    operation : tbt.Step
935        The step object containing the operation settings.
936    general_settings : tbt.GeneralSettings
937        The general settings object.
938
939    Returns
940    -------
941    bool
942        True if the stage is moved to the starting position successfully.
943    """
944    position = target_position(
945        operation.stage,
946        slice_number=slice_number,
947        slice_thickness_um=general_settings.slice_thickness_um,
948    )
949    print("\tMoving to following position:")
950    space = " "
951    print(
952        f"\t\t{'X [mm]' + space:<10}{'Y [mm]' + space:<10}{'Z [mm]' + space:<10}{'R [deg]' + space:<10}{'T [deg]' + space:<10}"
953    )
954    print(
955        f"\t\t{round(position.x_mm,4):<10}{round(position.y_mm,4):<10}{round(position.z_mm,4):<10}{round(position.r_deg,3):<10}{round(position.t_deg,3):<10}"
956    )
957    move_to_position(
958        microscope=microscope,
959        target_position=position,
960        stage_tolerance=general_settings.stage_tolerance,
961    )

Move the stage to the starting position for the step.

This function moves the stage to the starting position for the step based on the slice number, operation, and general settings.

Parameters

microscope : tbt.Microscope The microscope object for which to move the stage. slice_number : int The slice number for the step. operation : tbt.Step The step object containing the operation settings. general_settings : tbt.GeneralSettings The general settings object.

Returns

bool True if the stage is moved to the starting position successfully.