Skip to content

cylindra_builtins.imod

This built-in plugin submodule provides functions to work with IMOD file formats.

export_project(ui, layer, save_dir, template_path, mask_params=None, project_name='project-0')

Export cylindra state as a PEET prm file.

Molecules and images will be exported to a directory that can be directly used by PEET.

Parameters:

Name Type Description Default
layer MoleculesLayer

Molecules layer to export.

required
save_dir Path

Directory to save the files needed for a PEET project.

required
template_path str

Path to the template image.

required
mask_params Any

Mask parameters.

None
project_name str

Name of the PEET project.

"project-0"
Source code in cylindra_builtins/imod/io.py
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@register_function(name="Export project")
def export_project(
    ui: CylindraMainWidget,
    layer: MoleculesLayerType,
    save_dir: Path.Dir,
    template_path: Annotated[str, {"bind": _get_template_path}],
    mask_params: Annotated[Any, {"bind": _get_mask_params}] = None,
    project_name: str = "project-0",
):
    """
    Export cylindra state as a PEET prm file.

    Molecules and images will be exported to a directory that can be
    directly used by PEET.

    Parameters
    ----------
    layer : MoleculesLayer
        Molecules layer to export.
    save_dir : Path
        Directory to save the files needed for a PEET project.
    template_path : str
        Path to the template image.
    mask_params : Any, default None
        Mask parameters.
    project_name : str, default "project-0"
        Name of the PEET project.
    """
    save_dir = Path(save_dir)
    layer = assert_layer(layer, ui.parent_viewer)
    if not save_dir.exists():
        save_dir.mkdir()
    loader = ui.tomogram.get_subtomogram_loader(
        Molecules.empty(),
        binsize=1,
    )
    template_image, mask_image = loader.normalize_input(
        template=ui.sta.params._norm_template_param(template_path),
        mask=ui.sta.params._get_mask(params=mask_params),
    )

    if template_image is None:
        raise ValueError("Template image is not loaded.")

    # paths
    coordinates_path = "./coordinates.mod"
    angles_path = "./angles.csv"
    template_path = "./template-image.mrc"
    mask_path = "./mask-image.mrc"
    prm_path = save_dir / f"{project_name}.prm"

    txt = PEET_TEMPLATE.format(
        tomograms=str(ui.tomogram.source),
        coordinates=coordinates_path,
        angles=angles_path,
        tilt_range=list(ui.tomogram.tilt["range"]),
        template=template_path,
        project_name=project_name,
        shape=list(template_image.shape),
        mask_type=mask_path,
    )

    # save files
    prm_path.write_text(txt)
    mol = layer.molecules
    _save_molecules(save_dir=save_dir, mol=mol, scale=ui.tomogram.scale)
    ip.asarray(template_image, axes="zyx").set_scale(
        zyx=ui.tomogram.scale, unit="nm"
    ).imsave(save_dir / template_path)
    if mask_image is not None:
        ip.asarray(mask_image, axes="zyx").set_scale(
            zyx=ui.tomogram.scale, unit="nm"
        ).imsave(save_dir / mask_path)

    return None

load_molecules(ui, mod_path, ang_path, shift_mol=True)

Read molecule coordinates and angles from IMOD .mod files.

Parameters:

Name Type Description Default
mod_path Path

Path to the mod file that contains molecule coordinates.

required
ang_path Path

Path to the text file that contains molecule angles in Euler angles.

required
shift_mol bool

In PEET output csv there may be xOffset, yOffset, zOffset columns that can be directly applied to the molecule coordinates.

True
Source code in cylindra_builtins/imod/io.py
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
@register_function(name="Load molecules")
def load_molecules(
    ui: CylindraMainWidget,
    mod_path: Annotated[Path.Read[FileFilter.MOD], {"label": "Path to MOD file"}],
    ang_path: Annotated[Path.Read[FileFilter.CSV], {"label": "Path to csv file"}],
    shift_mol: Annotated[bool, {"label": "Apply shifts to monomers if offsets are available."}] = True,
):  # fmt: skip
    """
    Read molecule coordinates and angles from IMOD .mod files.

    Parameters
    ----------
    mod_path : Path
        Path to the mod file that contains molecule coordinates.
    ang_path : Path
        Path to the text file that contains molecule angles in Euler angles.
    shift_mol : bool, default True
        In PEET output csv there may be xOffset, yOffset, zOffset columns that can
        be directly applied to the molecule coordinates.
    """
    from .cmd import read_mod

    mod_path = Path(mod_path)
    df = read_mod(mod_path)
    mod = df.select("z", "y", "x").to_numpy(writable=True)
    mod[:, 1:] -= 0.5  # shift to center of voxel
    shifts, angs = _read_shift_and_angle(ang_path)
    scale = ui.tomogram.scale
    mol = Molecules.from_euler(pos=mod * scale, angles=angs, degrees=True)
    if shift_mol:
        mol.translate(shifts * scale, copy=False)

    return add_molecules(ui.parent_viewer, mol, mod_path.name, source=None)

load_splines(ui, mod_path)

Read a mod file and register all the contours as splines.

Source code in cylindra_builtins/imod/io.py
57
58
59
60
61
62
63
64
65
66
67
68
69
@register_function(name="Load splines")
def load_splines(
    ui: CylindraMainWidget,
    mod_path: Annotated[Path.Read[FileFilter.MOD], {"label": "Path to MOD file"}],
):
    """Read a mod file and register all the contours as splines."""
    from .cmd import read_mod

    df = read_mod(mod_path)
    for _, sub in df.group_by("object_id", "contour_id", maintain_order=True):
        coords = sub.select("z", "y", "x").to_numpy(writable=True)
        coords[:, 1:] -= 0.5  # shift YX to center of voxel
        ui.register_path(coords * ui.tomogram.scale, err_max=1e-8)

save_molecules(ui, save_dir, layers)

Save monomer positions and angles in the PEET format.

Parameters:

Name Type Description Default
save_dir Path

Saving path.

required
layers sequence of MoleculesLayer

Select the layers to save. All the molecules will be concatenated.

required
Source code in cylindra_builtins/imod/io.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@register_function(name="Save molecules")
def save_molecules(
    ui: CylindraMainWidget, save_dir: Path.Dir, layers: MoleculesLayersType
):
    """
    Save monomer positions and angles in the PEET format.

    Parameters
    ----------
    save_dir : Path
        Saving path.
    layers : sequence of MoleculesLayer
        Select the layers to save. All the molecules will be concatenated.
    """
    save_dir = Path(save_dir)
    layers = assert_list_of_layers(layers, ui.parent_viewer)
    mol = Molecules.concat([l.molecules for l in layers])
    return _save_molecules(save_dir=save_dir, mol=mol, scale=ui.tomogram.scale)

save_splines(ui, save_path, interval=10.0)

Save splines as a mod file.

This function will sample coordinates along the splines and save the coordinates as a mod file. The mod file will be labeled with object_id=1 and contour_id=i+1, where i is the index of the spline.

Parameters:

Name Type Description Default
save_path Path

Saving path.

required
interval float

Sampling interval along the splines. For example, if interval=10.0 and the length of a spline is 100.0, 11 points will be sampled.

10.0
Source code in cylindra_builtins/imod/io.py
 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
@register_function(name="Save splines")
def save_splines(
    ui: CylindraMainWidget,
    save_path: Path.Save[FileFilter.MOD],
    interval: Annotated[float, {"min": 0.01, "max": 1000.0, "label": "Sampling interval (nm)"}] = 10.0,
):  # fmt: skip
    """
    Save splines as a mod file.

    This function will sample coordinates along the splines and save the coordinates
    as a mod file. The mod file will be labeled with object_id=1 and contour_id=i+1,
    where i is the index of the spline.

    Parameters
    ----------
    save_path : Path
        Saving path.
    interval : float, default 10.0
        Sampling interval along the splines. For example, if interval=10.0 and the
        length of a spline is 100.0, 11 points will be sampled.
    """
    from .cmd import save_mod

    if interval <= 1e-4:
        raise ValueError("Interval must be larger than 1e-4.")
    data_list = []
    for i, spl in enumerate(ui.splines):
        num = int(spl.length() / interval)
        coords = spl.partition(num) / ui.tomogram.scale
        df = pl.DataFrame(
            {
                "object_id": 1,
                "contour_id": i + 1,
                "x": coords[:, 2] + 0.5,
                "y": coords[:, 1] + 0.5,
                "z": coords[:, 0],
            }
        )
        data_list.append(df)
    data_all = pl.concat(data_list, how="vertical")
    save_mod(save_path, data_all)