Skip to content

cylindra.core

collect_projects(files, *, skip_exc=False)

Collect project files into a ProjectSequence object.

collect_projects("path/to/dir/*.json")

Parameters:

Name Type Description Default
files path-like or iterable of path-like

Project file paths or glob pattern(s).

required
Source code in cylindra/core.py
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
267
268
269
270
271
272
273
274
275
276
def collect_projects(
    files: PathLike | Iterable[PathLike],
    *,
    skip_exc: bool = False,
) -> ProjectSequence:
    """
    Collect project files into a ProjectSequence object.

    >>> collect_projects("path/to/dir/*.json")

    Parameters
    ----------
    files : path-like or iterable of path-like
        Project file paths or glob pattern(s).
    """
    from cylindra.project import ProjectSequence

    if isinstance(files, (str, Path)):
        if "*" in str(files):
            _files = glob.glob(str(files))
        else:
            if not Path(files).exists():
                raise FileNotFoundError(f"File not found: {files}")
            _files = [files]
    elif hasattr(files, "__iter__"):
        _files = []
        for f in files:
            f = str(f)
            if "*" not in f:
                _files.append(f)
            else:
                _files.extend(list(glob.glob(f)))
    else:
        raise TypeError(f"files must be path or iterable of paths, got {type(files)}")
    if len(_files) == 0:
        raise FileNotFoundError(f"No project files found from the input {files!r}.")
    seq = ProjectSequence.from_paths(_files, check_scale=False, skip_exc=skip_exc)
    return seq

instance(create=False)

Get the current CylindraMainWidget instance.

Source code in cylindra/core.py
166
167
168
169
170
171
def instance(create=False):
    """Get the current CylindraMainWidget instance."""
    ins = _CURRENT_INSTANCE
    if ins is None and create:
        ins = start()
    return ins

read_molecules(file, pos_cols=('z', 'y', 'x'), rot_cols=('zvec', 'yvec', 'xvec'))

Read a molecules CSV file.

Parameters:

Name Type Description Default
file PathLike

File path.

required
pos_cols sequence of str

Column names for the molecule positions.

("z", "y", "x")
rot_cols sequence of str

Column names for the molecule rotation vectors.

("zvec", "yvec", "xvec")

Returns:

Type Description
Molecules

Molecules object.

Source code in cylindra/core.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
def read_molecules(
    file: PathLike,
    pos_cols: Sequence[str] = ("z", "y", "x"),
    rot_cols: Sequence[str] = ("zvec", "yvec", "xvec"),
) -> Molecules:
    """
    Read a molecules CSV file.

    Parameters
    ----------
    file : PathLike
        File path.
    pos_cols : sequence of str, default ("z", "y", "x")
        Column names for the molecule positions.
    rot_cols : sequence of str, default ("zvec", "yvec", "xvec")
        Column names for the molecule rotation vectors.

    Returns
    -------
    Molecules
        Molecules object.
    """
    from acryo import Molecules

    path = Path(file)
    return Molecules.from_file(path, pos_cols=list(pos_cols), rot_cols=list(rot_cols))

read_project(file)

Read the Cylindra project file.

Source code in cylindra/core.py
185
186
187
188
189
def read_project(file: PathLike) -> CylindraProject:
    """Read the Cylindra project file."""
    from cylindra.project import CylindraProject

    return CylindraProject.from_file(file)

read_spline(file)

Read the spline file.

Parameters:

Name Type Description Default
file PathLike

File path.

required

Returns:

Type Description
CylSpline

CylSpline object.

Source code in cylindra/core.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def read_spline(file: PathLike) -> CylSpline:
    """
    Read the spline file.

    Parameters
    ----------
    file : PathLike
        File path.

    Returns
    -------
    CylSpline
        CylSpline object.
    """
    from cylindra.components import CylSpline

    return CylSpline.from_json(file)

start(project_file=None, viewer=None, *, log_level='INFO', headless=False, add_main_widget=True, run=True)

Start napari viewer and dock cylindra widget as a dock widget.

Parameters:

Name Type Description Default
project_file path - like

If given, load the project file.

None
viewer Viewer

Give a viewer object and this viewer will be used as the parent.

None
log_level int or str

Log level. See logging module for details.

"INFO"
headless bool

If True, do not show the viewer.

False
Source code in cylindra/core.py
 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
 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
133
134
135
136
137
138
def start(
    project_file: str | None = None,
    viewer: napari.Viewer | None = None,
    *,
    log_level: int | str = "INFO",
    headless: bool = False,
    add_main_widget: bool = True,
    run: bool = True,
) -> CylindraMainWidget:
    """
    Start napari viewer and dock cylindra widget as a dock widget.

    Parameters
    ----------
    project_file : path-like, optional
        If given, load the project file.
    viewer : napari.Viewer
        Give a viewer object and this viewer will be used as the parent.
    log_level : int or str, default "INFO"
        Log level. See `logging` module for details.
    headless : bool, default False
        If True, do not show the viewer.
    """
    from cylindra.widgets import CylindraMainWidget  # noqa: I001
    import impy as ip
    import matplotlib.pyplot as plt
    import napari
    import numpy as np
    import polars as pl
    from IPython import get_ipython
    from magicclass import logging

    from cylindra._config import init_config

    global _CURRENT_INSTANCE

    if viewer is None:
        viewer = napari.Viewer(show=not headless)
    elif not isinstance(viewer, napari.Viewer):
        raise TypeError(f"viewer must be a napari.Viewer object, got {type(viewer)}")

    init_config()
    ui = CylindraMainWidget()
    ui.macro.options.max_undo = 16
    ACTIVE_WIDGETS.add(ui)

    # set logger
    logger = logging.getLogger("cylindra")
    formatter = logging.Formatter(fmt="%(levelname)s || %(message)s")
    logger.widget.setFormatter(formatter)
    logger.widget.min_height = 150

    # set log level
    if isinstance(log_level, str):
        log_level = log_level.upper()
        if log_level in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
            log_level = getattr(logging, log_level)
        else:
            raise ValueError(f"Invalid log level: {log_level}")
    logger.setLevel(log_level)

    # set polars display options
    pl.Config().set_tbl_width_chars(120)

    if add_main_widget:
        dock = viewer.window.add_dock_widget(
            ui, area="right", allowed_areas=["right"], name="cylindra"
        )
        dock.setMinimumHeight(300)
    viewer.window.add_dock_widget(logger.widget, name="Log")
    ui.macro.options.syntax_highlight = False

    if project_file is not None:
        ui.load_project(project_file)
    _CURRENT_INSTANCE = ui

    with suppress(Exception):
        # update console namespace
        viewer.window._qt_viewer.console.push(
            {
                ".ui": ui,  # only available from namespace dict
                "ui": ui,
                "np": np,
                "ip": ip,
                "pl": pl,
                "plt": plt,
                "Path": Path,
            }
        )

    with suppress(Exception):  # This block uses private API.
        # napari viewer does not disconnect layer events when the viewer is closed,
        # so we need to do it manually
        @viewer.window._qt_window.destroyed.connect
        def _on_destroy():
            viewer.layers.events.removing.disconnect()
            viewer.layers.events.removed.disconnect()

        # napari-console disables calltips by default. It's better to enable it.
        viewer.window._qt_viewer.console.enable_calltips = True

    ui.show(run=run)
    if add_main_widget:
        try:  # Just in case
            # avoid accidentally closing/hiding the dock widget
            dock.title.close_button.disconnect()
            dock.title.hide_button.disconnect()
        except Exception:  # pragma: no cover
            print("Failed to disconnect the close/hide button of the dock widget.")

    # Programmatically run `%matplotlib inline` magic
    if ipy := get_ipython():
        ipy.run_line_magic("matplotlib", "inline")

    return ui

start_as_plugin(run=True)

Start Cylindra as a napari plugin

Source code in cylindra/core.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def start_as_plugin(run: bool = True):
    """Start Cylindra as a napari plugin"""
    import napari
    from magicclass import logging

    ui = start(
        viewer=napari.current_viewer(),
        add_main_widget=False,
        run=run,
    )
    # float logger widget
    logger = logging.getLogger("cylindra")
    logger.widget.native.parentWidget().setFloating(True)
    logger.widget.height = 160
    return ui

view_project(project_file, show=True)

View the Cylindra project file.

Source code in cylindra/core.py
174
175
176
177
178
179
180
181
182
def view_project(project_file: PathLike, show: bool = True):
    """View the Cylindra project file."""
    from cylindra.project import CylindraProject

    widget = CylindraProject.from_file(project_file).make_project_viewer()
    if show:
        widget.show(run=False)
    ACTIVE_WIDGETS.add(widget)
    return widget