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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@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=repr(ui.tomogram.source),
        coordinates=repr(coordinates_path),
        angles=repr(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)

export_project_batch(ui, save_dir, path_sets, project_name='project-0', size=10.0)

Export cylindra batch analyzer state as a PEET prm file.

A epe file will be generated, which can directly be used by etomo <name>.epe.

Parameters:

Name Type Description Default
save_dir Path

Directory to save the files needed for a PEET project.

required
path_sets Any

Path sets of the tomograms and coordinates.

required
project_name str

Name of the PEET project.

"project-0"
size float

Size of the subtomograms in nanometers.

10.0
Source code in cylindra_builtins/imod/io.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
@register_function(name="Export project as batch")
def export_project_batch(
    ui: CylindraMainWidget,
    save_dir: Path.Dir,
    path_sets: Annotated[Any, {"bind": _get_loader_paths}],
    project_name: str = "project-0",
    size: Annotated[float, {"label": "Subtomogram size (nm)", "min": 1.0, "max": 1000.0}] = 10.0,  # fmt: skip
):
    """Export cylindra batch analyzer state as a PEET prm file.

    A epe file will be generated, which can directly be used by `etomo <name>.epe`.

    Parameters
    ----------
    save_dir : Path
        Directory to save the files needed for a PEET project.
    path_sets : Any
        Path sets of the tomograms and coordinates.
    project_name : str, default "project-0"
        Name of the PEET project.
    size : float, default 10.0
        Size of the subtomograms in nanometers.
    """
    from cylindra.widgets.batch._sequence import PathInfo
    from cylindra.widgets.batch._utils import TempFeatures

    save_dir = Path(save_dir)
    if not save_dir.exists():
        save_dir.mkdir()

    _temp_feat = TempFeatures()

    _tomogram_list = list[str]()
    _coords_list = list[str]()
    _angle_list = list[str]()
    _tilt_list = list[str]()
    _count = 0
    scales = []
    for path_info in path_sets:
        path_info = PathInfo(*path_info)
        prj = path_info.project_instance(missing_ok=False)
        moles = list(path_info.iter_molecules(_temp_feat, prj.scale))
        if len(moles) > 0:
            _tomogram_list.append(repr(path_info.image.as_posix()))
            mod_name = f"coordinates-{_count:0>3}_{path_info.image.stem}.mod"
            csv_name = f"angles-{_count:0>3}_{path_info.image.stem}.csv"
            _save_molecules(
                save_dir=save_dir,
                mol=Molecules.concat(moles),
                scale=prj.scale,
                mod_name=mod_name,
                csv_name=csv_name,
            )
            _coords_list.append(f"'./{mod_name}'")
            _angle_list.append(f"'./{csv_name}'")
            if mw_dict := prj.missing_wedge.as_param():
                if "range" in mw_dict:
                    tilt_range = mw_dict["range"]
                    _tilt_list.append(f"[{tilt_range[0]}, {tilt_range[1]}]")
            scales.append(prj.scale)
            _count += 1

    # determine shape using the average scale
    if len(scales) == 0:
        raise ValueError("No tomograms found in the project.")
    scale = np.mean(scales)
    shape = [int(round(size / scale / 2)) * 2] * 3  # must be even

    # paths
    prm_path = save_dir / f"{project_name}.prm"
    epe_path = save_dir / f"{project_name}.epe"

    prm_txt = PEET_TEMPLATE.format(
        tomograms=", ".join(_tomogram_list),
        coordinates=", ".join(_coords_list),
        angles=", ".join(_angle_list),
        tilt_range=", ".join(_tilt_list),
        template="",
        project_name=project_name,
        shape=shape,
        mask_type="none",
    )

    # save files
    prm_path.write_text(prm_txt)
    epe_path.write_text(f"Peet.RootName={project_name}\n")

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
@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
56
57
58
59
60
61
62
63
64
65
66
67
68
@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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@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)