Skip to content

Handlers

This module contains some tools which simplify working with IMOD alignment metadata from Python.

yet_another_imod_wrapper.utils.etomo.EtomoOutput

Convenient retrieval of outputs from an Etomo alignment project.

For details see the source code.

Source code in src/yet_another_imod_wrapper/utils/etomo.py
12
13
14
15
16
17
18
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
class EtomoOutput:
    """Convenient retrieval of outputs from an Etomo alignment project.

    For details see the source code.
    """
    def __init__(self, basename: str, directory: Path):
        self.directory: Path = directory
        self.basename: str = basename

    @property
    def tilt_series_file(self) -> Path:
        return self.directory / f'{self.basename}.mrc'

    @property
    def rawtlt_file(self) -> Path:
        return self.directory / f'{self.basename}.rawtlt'

    @property
    def xf_file(self) -> Path:
        return self.directory / f'{self.basename}.xf'

    @property
    def tlt_file(self) -> Path:
        return self.directory / f'{self.basename}.tlt'

    @property
    def edf_file(self) -> Path:
        return self.directory / f'{self.basename}.edf'

    @property
    def align_log_file(self) -> Path:
        return self.directory / 'align.log'

    @property
    def is_ready_for_alignment(self) -> bool:
        return self.tilt_series_file.exists() and self.rawtlt_file.exists()

    @property
    def contains_alignment_results(self) -> bool:
        return self.xf_file.exists() and self.tlt_file.exists()

yet_another_imod_wrapper.utils.xf.XF

Convenient retrieval of properties from IMOD xf data.

An xf file contains one line with six numbers per image in the tilt-series, each specifying a linear transformation:

A11 A12 A21 A22 DX DY

where the coordinate (X, Y) is transformed to (X', Y') by:

X' = A11 * X + A12 * Y + DX
Y' = A21 * X + A22 * Y + DY

The rotation center in IMOD is at (N-1) / 2 where N is the number of elements in each dimension. This calculates a 0-indexed coordinate.

Source code in src/yet_another_imod_wrapper/utils/xf.py
 9
10
11
12
13
14
15
16
17
18
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
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
class XF:
    """Convenient retrieval of properties from IMOD xf data.

    An xf file contains one line with six numbers per image in the tilt-series,
    each specifying a linear transformation:

        A11 A12 A21 A22 DX DY

    where the coordinate `(X, Y)` is transformed to `(X', Y')` by:

        X' = A11 * X + A12 * Y + DX
        Y' = A21 * X + A22 * Y + DY

    The rotation center in IMOD is at `(N-1) / 2` where `N` is the
    number of elements in each dimension. This calculates a 0-indexed
    coordinate.
    """

    def __init__(
        self,
        xf: np.ndarray,
        initial_tilt_axis_rotation_angle: Optional[float] = None
    ):
        self.xf_data = xf
        self.initial_tilt_axis_rotation_angle = initial_tilt_axis_rotation_angle

    @classmethod
    def from_file(
        cls,
        filename: os.PathLike,
        initial_tilt_axis_rotation_angle: float = None
    ):
        return cls(read_xf(filename), initial_tilt_axis_rotation_angle)

    @property
    def shifts(self) -> np.ndarray:
        """`(n, 2)` array of `(DX, DY)` from xf data.

        These shifts are applied **after** rotation/skew.
        """
        return self.xf_data[:, -2:]

    @property
    def transformation_matrices(self) -> np.ndarray:
        """`(n, 2, 2)` array containing `A11, A12, A21, A22` from xf data."""
        return self.xf_data[:, :4].reshape((-1, 2, 2))

    @property
    def in_plane_rotations(self) -> np.ndarray:
        """`(n, )` array of in plane rotation angles from xf data.

        Angles are in degrees and counter-clockwise angles are positive.
        """
        cos_theta = self.transformation_matrices[:, 0, 0]
        theta = np.rad2deg(np.arccos(cos_theta))
        if self.initial_tilt_axis_rotation_angle is None:
            warn(
                'no initial value provided for tilt-axis angle was \
                provided and there are multiple valid solutions  \
                for the requested in-plane rotation angle.'
            )
        else:
            initial_theta = self.initial_tilt_axis_rotation_angle
            difference = np.abs(initial_theta - theta).sum()
            flipped_difference = np.abs((-1 * initial_theta) - theta).sum()
            if flipped_difference < difference:
                theta = -1 * theta
        return theta

    @property
    def image_shifts(self) -> np.ndarray:
        """`(n, 2)` array of xy shifts aligning tilt-images with the projected specimen.

        Rotation center is in IMOD convention `(N-1) / 2`.
        """
        inverse_transformation_matrices = np.linalg.pinv(self.transformation_matrices)
        return np.squeeze(inverse_transformation_matrices @ self.shifts.reshape((-1, 2, 1)))

    @property
    def specimen_shifts(self) -> np.ndarray:
        """`(n, 2)` array of xy shifts aligning the projected specimen with tilt-images.

        Rotation center is in IMOD convention `(N-1) / 2`.
        """
        return -self.image_shifts

shifts: np.ndarray property

(n, 2) array of (DX, DY) from xf data.

These shifts are applied after rotation/skew.

transformation_matrices: np.ndarray property

(n, 2, 2) array containing A11, A12, A21, A22 from xf data.

in_plane_rotations: np.ndarray property

(n, ) array of in plane rotation angles from xf data.

Angles are in degrees and counter-clockwise angles are positive.

image_shifts: np.ndarray property

(n, 2) array of xy shifts aligning tilt-images with the projected specimen.

Rotation center is in IMOD convention (N-1) / 2.

specimen_shifts: np.ndarray property

(n, 2) array of xy shifts aligning the projected specimen with tilt-images.

Rotation center is in IMOD convention (N-1) / 2.