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
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@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
Source code in cylindra_builtins/relion/io.py
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
317
318
@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.
    """
    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 == "relion.reconstructtomograms":
        # Reconstruct Tomogram job
        tomogram_star_path = job_dir_path / "tomograms.star"
        _, tomo_paths, scale_nm = _parse_tomo_star(tomogram_star_path)
        ui.batch.constructor._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 == "relion.picktomo":
        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 = []
        molecules = []
        for item in _iter_from_optimisation_star(opt_star_path, rln_project_path):
            paths.append(rln_project_path / item.tomo_path)
            scales.append(item.scale)
            molecules.append(item.molecules)

        ui.batch.constructor._new_projects_from_table(
            paths,
            save_root=job_dir_path / "cylindra",
            invert=[invert] * len(paths),
            scale=scales,
            molecules=molecules,
            bin_size=[bin_size] * len(paths),
        )
    else:
        raise ValueError(f"Job {job_dir_path.name} is not a supported RELION job.")

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
 83
 84
 85
 86
 87
 88
 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
@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
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
@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_COLUMNS[2]: (coords[:, 2] - centerx + orig.x) * 10,  # Angstrom
                POS_COLUMNS[1]: (coords[:, 1] - centery + orig.y) * 10,  # Angstrom
                POS_COLUMNS[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,
                IMAGE_PIXEL_SIZE: ui.tomogram.scale * 10,  # convert to Angstrom
                OPTICS_GROUP: np.ones(mole_count, dtype=np.uint32),
            }
        )
        data_list.append(df)
    df = pl.concat(data_list, how="vertical").to_pandas()
    starfile.write(df, save_path)