Skip to content

cylindra_builtins.relion

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

load_molecules(ui, path)

Read monomer coordinates and angles from RELION .star file.

Parameters:

Name Type Description Default
path path - like

The path to the star file.

required
Source code in cylindra_builtins/relion/io.py
49
50
51
52
53
54
55
56
57
58
59
60
61
@register_function(name="Load molecules")
def load_molecules(ui: CylindraMainWidget, path: Path.Read[FileFilter.STAR]):
    """Read monomer coordinates and angles from RELION .star file.

    Parameters
    ----------
    path : path-like
        The path to the star file.
    """
    path = Path(path)
    moles = _read_star(path, ui.tomogram)
    for i, mole in enumerate(moles.values()):
        add_molecules(ui.parent_viewer, mole, f"{path.name}-{i}", source=None)

load_splines(ui, path)

Read a star file and register all the tubes as splines.

The "rlnHelicalTubeID" column will be used to group the points into splines.

Parameters:

Name Type Description Default
path path - like

The path to the star file.

required
Source code in cylindra_builtins/relion/io.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@register_function(name="Load splines")
def load_splines(ui: CylindraMainWidget, path: Path.Read[FileFilter.STAR]):
    """Read a star file and register all the tubes as splines.

    The "rlnHelicalTubeID" column will be used to group the points into splines.

    Parameters
    ----------
    path : path-like
        The path to the star file.
    """
    mole = Molecules.concat(_read_star(path, ui.tomogram).values())
    if RELION_TUBE_ID not in mole.features.columns:
        warnings.warn(
            f"{RELION_TUBE_ID!r} not found in star file. Use all points as a "
            "single spline.",
            UserWarning,
            stacklevel=2,
        )
        ui.register_path(mole.pos, err_max=1e-8)
    else:
        for _, each in mole.group_by(RELION_TUBE_ID):
            ui.register_path(each.pos, err_max=1e-8)

open_relion_job(ui, path, invert=True, bin_size=[1])

Open a RELION tomogram reconstruction job folder.

Parameters:

Name Type Description Default
path path - like

The path to the RELION job.star file.

required
invert bool

Set to True if the tomograms are light backgroud.

True
bin_size list[int]

The multiscale binning size for the tomograms.

[1]
Source code in cylindra_builtins/relion/io.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
@register_function(name="Open RELION job")
def open_relion_job(
    ui: CylindraMainWidget,
    path: Path.Read[FileFilter.STAR_JOB],
    invert: bool = True,
    bin_size: list[int] = [1],
):
    """Open a RELION tomogram reconstruction job folder.

    Parameters
    ----------
    path : path-like
        The path to the RELION job.star file.
    invert : bool, default True
        Set to True if the tomograms are light backgroud.
    bin_size : list[int], default [1]
        The multiscale binning size for the tomograms.
    """
    path = Path(path)
    if path.name != "job.star" or not path.is_file() or not path.exists():
        raise ValueError(f"Path must be an existing RELION job.star file, got {path}")
    job_dir_path = Path(path).parent
    rln_project_path = _relion_project_path(job_dir_path)
    jobtype = _get_job_type(job_dir_path)
    if jobtype in ("relion.reconstructtomograms", "relion.denoisetomo"):
        # Reconstruct Tomogram job
        tomogram_star_path = job_dir_path / "tomograms.star"
        if not tomogram_star_path.exists():
            raise FileNotFoundError(
                f"tomogram.star file {tomogram_star_path} does not exist. Make sure "
                "the input job has an tomogram output."
            )
        col = (
            REC_TOMO_DENOISED_PATH if jobtype == "relion.denoisetomo" else REC_TOMO_PATH
        )
        _, tomo_paths, scale_nm = _parse_tomo_star(tomogram_star_path, col)
        ui.batch._new_projects_from_table(
            path=[rln_project_path / p for p in tomo_paths],
            scale=scale_nm,
            invert=[invert] * len(tomo_paths),
            save_root=job_dir_path / "cylindra",
        )
    elif jobtype in ("relion.picktomo", "relion.pseudosubtomo"):
        if not (opt_star_path := job_dir_path / "optimisation_set.star").exists():
            raise ValueError(
                f"Optimisation set star file not found in {job_dir_path}. "
                "Please ensure the job is a RELION 5.0 pick-particles job."
            )
        paths, scales, moles = _parse_optimisation_star(opt_star_path, rln_project_path)
        ui.batch._new_projects_from_table(
            paths,
            save_root=job_dir_path / "cylindra",
            invert=[invert] * len(paths),
            scale=scales,
            molecules=moles,
            bin_size=[bin_size] * len(paths),
        )
    elif jobtype in ("relion.initialmodel.tomo", "relion.refine3d.tomo"):
        opt_set_path_list = sorted(
            job_dir_path.glob("run_it*_optimisation_set.star"),
            key=lambda p: p.stem,
        )
        if len(opt_set_path_list) == 0:
            raise ValueError(
                f"No optimisation set star files found in {job_dir_path}. "
                "Please ensure at least one iteration has finished."
            )
        opt_star_path = opt_set_path_list[-1]
        paths, scales, moles = _parse_optimisation_star(opt_star_path, rln_project_path)
        ui.batch._new_projects_from_table(
            paths,
            save_root=job_dir_path / "cylindra",
            invert=[invert] * len(paths),
            scale=scales,
            molecules=moles,
            bin_size=[bin_size] * len(paths),
        )
    else:
        raise ValueError(f"Job {job_dir_path.name} is not a supported RELION job.")

save_coordinates_for_import(ui, coordinates_path, path_sets, save_features=False, shift_by_origin=True, centered=True)

Save the batch analyzer state as a optimisation set for subtomogram extraction.

Parameters:

Name Type Description Default
coordinates_path path - like

The path to save the star file containing the particles.

required
path_sets sequence of PathInfo

The path sets to the tomograms and molecules.

required
save_features bool

Whether to save the features of the molecules to the star file.

False
shift_by_origin bool

If True, the positions will be shifted by the origin of the tomogram. This option is required if you picked molecules in a trimmed tomogram.

True
centered bool

If True, the positions will be centered around the tomogram center, and columns "rlnCenteredCoordinateX/Y/ZAngst" will be used. If False, columns "rlnCoordinateX/Y/Z" will be used.

True
Source code in cylindra_builtins/relion/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
260
@register_function(name="Save coordinates for import", record=False)
def save_coordinates_for_import(
    ui: CylindraMainWidget,
    coordinates_path: Path.Save[FileFilter.STAR],
    path_sets: Annotated[Any, {"bind": _get_loader_paths}],
    save_features: bool = False,
    shift_by_origin: bool = True,
    centered: bool = True,
):
    """Save the batch analyzer state as a optimisation set for subtomogram extraction.

    Parameters
    ----------
    coordinates_path : path-like
        The path to save the star file containing the particles.
    path_sets : sequence of PathInfo
        The path sets to the tomograms and molecules.
    save_features : bool, default False
        Whether to save the features of the molecules to the star file.
    shift_by_origin : bool, default True
        If True, the positions will be shifted by the origin of the tomogram. This
        option is required if you picked molecules in a trimmed tomogram.
    centered : bool, default True
        If True, the positions will be centered around the tomogram center, and columns
        "rlnCenteredCoordinateX/Y/ZAngst" will be used. If False, columns
        "rlnCoordinateX/Y/Z" will be used.
    """
    from cylindra.components.tomogram import CylTomogram
    from cylindra.widgets.batch._sequence import PathInfo
    from cylindra.widgets.batch._utils import TempFeatures

    coordinates_path = Path(coordinates_path)
    save_dir = coordinates_path.parent / f"{coordinates_path.stem}_particles"
    save_dir.mkdir(exist_ok=True)
    _temp_feat = TempFeatures()

    tomo_names = list[str]()
    particles_paths = list[str]()
    particles_dfs = list[pd.DataFrame]()
    for path_info in path_sets:
        path_info = PathInfo(*path_info)
        prj = path_info.project_instance(missing_ok=False)
        tomo_name = _strip_relion5_prefix(path_info.image.stem)
        img = path_info.lazy_imread()
        tomo = CylTomogram.from_image(
            img,
            scale=prj.scale,
            tilt=prj.missing_wedge.as_param(),
            compute=False,
        )
        moles = list(path_info.iter_molecules(_temp_feat, prj.scale))
        if len(moles) > 0:
            df = _mole_to_star_df(
                moles,
                tomo,
                tomo_name,
                save_features,
                shift_by_origin,
                centered=centered,
            )
            particles_path = save_dir / f"{tomo_name}_particles.star"
            particles_dfs.append(df)
            particles_paths.append(particles_path)
            tomo_names.append(tomo_name)
            starfile.write(df, particles_path)

    df_opt = pd.DataFrame(
        {
            TOMO_NAME: tomo_names,
            IMPORT_PARTICLE_FILE: particles_paths,
        }
    )
    starfile.write(df_opt, coordinates_path)

save_molecules(ui, save_path, layers, save_features=False, tomo_name_override='', shift_by_origin=True)

Save the selected molecules to a RELION .star file.

If multiple layers are selected, the MoleculeGroupID column will be added to the star file to distinguish the layers. This method is RELION 5 compliant.

Parameters:

Name Type Description Default
save_path path - like

The path to save the star file.

required
layers sequence of MoleculesLayer

The layers to save.

required
save_features bool

Whether to save the features of the molecules.

True
tomo_name_override str

If provided, this will override the tomogram name identifier (the rlnTomoName column) in the star file.

""
shift_by_origin bool

If True, the positions will be shifted by the origin of the tomogram. This option is required if you picked molecules in a trimmed tomogram.

True
Source code in cylindra_builtins/relion/io.py
 89
 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
@register_function(name="Save molecules")
def save_molecules(
    ui: CylindraMainWidget,
    save_path: Path.Save[FileFilter.STAR],
    layers: MoleculesLayersType,
    save_features: bool = False,
    tomo_name_override: str = "",
    shift_by_origin: bool = True,
):
    """Save the selected molecules to a RELION .star file.

    If multiple layers are selected, the `MoleculeGroupID` column will be added
    to the star file to distinguish the layers. This method is RELION 5 compliant.

    Parameters
    ----------
    save_path : path-like
        The path to save the star file.
    layers : sequence of MoleculesLayer
        The layers to save.
    save_features : bool, default True
        Whether to save the features of the molecules.
    tomo_name_override : str, default ""
        If provided, this will override the tomogram name identifier (the rlnTomoName
        column) in the star file.
    shift_by_origin : bool, default True
        If True, the positions will be shifted by the origin of the tomogram. This
        option is required if you picked molecules in a trimmed tomogram.
    """
    save_path = Path(save_path)
    layers = assert_list_of_layers(layers, ui.parent_viewer)
    tomo_name = tomo_name_override or _strip_relion5_prefix(ui.tomogram.image.name)
    df = _mole_to_star_df(
        [layer.molecules for layer in layers],
        ui.tomogram,
        tomo_name,
        save_features,
        shift_by_origin,
    )
    starfile.write(df, save_path)

save_splines(ui, save_path, interval=10.0, tomo_name_override='', shift_by_origin=True)

Save the current splines to a RELION .star file.

Parameters:

Name Type Description Default
save_path path - like

The path to save the star file.

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
tomo_name_override str

If provided, this will override the tomogram name identifier (the rlnTomoName column) in the star file.

""
shift_by_origin bool

If True, the positions will be shifted by the origin of the tomogram. This option is required if you picked molecules in a trimmed tomogram.

True
Source code in cylindra_builtins/relion/io.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
@register_function(name="Save splines")
def save_splines(
    ui: CylindraMainWidget,
    save_path: Path.Save[FileFilter.STAR],
    interval: Annotated[float, {"min": 0.01, "max": 1000.0, "label": "Sampling interval (nm)"}] = 10.0,
    tomo_name_override: str = "",
    shift_by_origin: bool = True,
):  # fmt: skip
    """Save the current splines to a RELION .star file.

    Parameters
    ----------
    save_path : path-like
        The path to save the star file.
    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.
    tomo_name_override : str, default ""
        If provided, this will override the tomogram name identifier (the rlnTomoName
        column) in the star file.
    shift_by_origin : bool, default True
        If True, the positions will be shifted by the origin of the tomogram. This
        option is required if you picked molecules in a trimmed tomogram.
    """

    if interval <= 1e-4:
        raise ValueError("Interval must be larger than 1e-4.")
    save_path = Path(save_path)
    data_list: list[pl.DataFrame] = []
    orig = ui.tomogram.origin
    if not shift_by_origin:
        orig = type(orig)(0.0, 0.0, 0.0)
    tomo_name = tomo_name_override or ui.tomogram.image.name
    scale = ui.tomogram.scale
    centerz, centery, centerx = (np.array(ui.tomogram.image.shape) / 2 - 1) * scale
    for i, spl in enumerate(ui.splines):
        num = int(spl.length() / interval)
        coords = spl.partition(num)
        mole_count = coords.shape[0]
        df = pl.DataFrame(
            {
                TOMO_NAME: [tomo_name] * mole_count,
                POS_CENTERED[2]: (coords[:, 2] - centerx + orig.x) * 10,  # Angstrom
                POS_CENTERED[1]: (coords[:, 1] - centery + orig.y) * 10,  # Angstrom
                POS_CENTERED[0]: (coords[:, 0] - centerz + orig.z) * 10,  # Angstrom
                ROT_COLUMNS[0]: 0.0,
                ROT_COLUMNS[1]: 0.0,
                ROT_COLUMNS[2]: 0.0,
                RELION_TUBE_ID: i,
            }
        )
        data_list.append(df)
    df = pl.concat(data_list, how="vertical").to_pandas()
    starfile.write(df, save_path)