Skip to content

backends

Gives a transform a .backend property.

self._backend holds only what the user explicitly asked for (None means "follow the config"). The effective backend is resolved lazily, on access.

That laziness is load-bearing: template packages such as flybrains build all their transforms at import time, i.e. long before a user gets a chance to touch navis.config. If we snapshotted the backend in __init__, setting navis.config.default_transform_backend after import flybrains would silently have no effect.

Subclasses whose fastcore implementation is not the CMTK/elastix one override _fallback_backend and _fastcore_available.

Source code in navis/transforms/backends.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class BackendMixin:
    """Gives a transform a `.backend` property.

    `self._backend` holds only what the user explicitly asked for (`None` means
    "follow the config"). The effective backend is resolved lazily, on access.

    That laziness is load-bearing: template packages such as `flybrains` build
    all their transforms at import time, i.e. long before a user gets a chance
    to touch `navis.config`. If we snapshotted the backend in `__init__`,
    setting `navis.config.default_transform_backend` after `import flybrains`
    would silently have no effect.

    Subclasses whose fastcore implementation is not the CMTK/elastix one
    override `_fallback_backend` and `_fastcore_available`.

    """

    _backend = None

    #: What this transform calls its non-fastcore implementation.
    _fallback_backend = "binary"

    #: Predicate deciding whether fastcore can serve this transform.
    _fastcore_available = staticmethod(fastcore_transforms_available)

    @property
    def backend(self) -> str:
        """The backend this transform will actually use."""
        return resolve_backend(
            self._backend,
            available=self._fastcore_available,
            fallback=self._fallback_backend,
        )

The backend this transform will actually use.

Whether this elastix transform can be inverted.

Uses fastcore's header-only probe, which is ~10x cheaper than parsing the file - it does not read the B-spline coefficients. That matters because this is asked of every registered transform whenever the bridging graph is built.

Source code in navis/transforms/backends.py
194
195
196
197
198
199
200
201
202
203
204
205
@functools.lru_cache(maxsize=256)
def elastix_is_invertible(path: str) -> bool:
    """Whether this elastix transform can be inverted.

    Uses fastcore's header-only probe, which is ~10x cheaper than parsing the
    file - it does not read the B-spline coefficients. That matters because this
    is asked of every registered transform whenever the bridging graph is built.

    """
    from .. import utils

    return bool(utils.fastcore.probe_elastix_invertible(path))

Whether the installed navis-fastcore can do the landmark transforms.

Probed separately from fastcore_transforms_available - and for the same reason it probes classes rather than a version - because the two arrived in different fastcore releases. An install can have one and not the other.

Source code in navis/transforms/backends.py
67
68
69
70
71
72
73
74
75
76
77
78
def fastcore_landmarks_available() -> bool:
    """Whether the installed navis-fastcore can do the landmark transforms.

    Probed separately from `fastcore_transforms_available` - and for the same
    reason it probes classes rather than a version - because the two arrived in
    different fastcore releases. An install can have one and not the other.

    """
    from .. import utils

    fc = utils.fastcore
    return fc is not None and hasattr(fc, "TpsTransform") and hasattr(fc, "MlsTransform")

Whether the installed navis-fastcore can do CMTK/elastix transforms.

We probe for the classes rather than comparing fastcore.__version__: the latter is derived from the install metadata and is unreliable (notably in editable installs, where it can lag the actual build by several versions).

Source code in navis/transforms/backends.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def fastcore_transforms_available() -> bool:
    """Whether the installed navis-fastcore can do CMTK/elastix transforms.

    We probe for the classes rather than comparing `fastcore.__version__`: the
    latter is derived from the install metadata and is unreliable (notably in
    editable installs, where it can lag the actual build by several versions).

    """
    # NB: must go through the module (rather than `from ..utils import fastcore`)
    # so tests can monkeypatch `navis.utils.fastcore = None`.
    from .. import utils

    fc = utils.fastcore
    return (
        fc is not None
        and hasattr(fc, "ElastixTransform")
        and hasattr(fc, "CmtkRegistration")
    )

Get a (cached) fastcore CmtkRegistration for these files.

Source code in navis/transforms/backends.py
178
179
180
181
182
183
@functools.lru_cache(maxsize=64)
def get_cmtk_reg(paths: tuple):
    """Get a (cached) fastcore CmtkRegistration for these files."""
    from .. import utils

    return utils.fastcore.CmtkRegistration(list(paths))

Get a (cached) fastcore ElastixTransform for this file.

Source code in navis/transforms/backends.py
186
187
188
189
190
191
@functools.lru_cache(maxsize=64)
def get_elastix_transform(path: str):
    """Get a (cached) fastcore ElastixTransform for this file."""
    from .. import utils

    return utils.fastcore.ElastixTransform(path)

Resolve a backend request to either "fastcore" or fallback.

PARAMETER DESCRIPTION
requested
    `None` defers to `navis.config.default_transform_backend`.
    "binary" and "python" both mean "do not use fastcore"; which of
    the two comes back is decided by `fallback`, not by the request.

TYPE: auto | fastcore | binary | python | None DEFAULT: None

available
    Predicate deciding whether fastcore can serve this transform.
    Defaults to the CMTK/elastix probe.

TYPE: callable DEFAULT: None

fallback
    What to return when fastcore is not used.

TYPE: str DEFAULT: 'binary'

Source code in navis/transforms/backends.py
 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
def resolve_backend(requested=None, available=None, fallback="binary") -> str:
    """Resolve a backend request to either "fastcore" or `fallback`.

    Parameters
    ----------
    requested : "auto" | "fastcore" | "binary" | "python" | None
                `None` defers to `navis.config.default_transform_backend`.
                "binary" and "python" both mean "do not use fastcore"; which of
                the two comes back is decided by `fallback`, not by the request.
    available : callable, optional
                Predicate deciding whether fastcore can serve this transform.
                Defaults to the CMTK/elastix probe.
    fallback :  str
                What to return when fastcore is not used.

    """
    if requested is None:
        requested = getattr(config, "default_transform_backend", "auto")

    if requested not in BACKENDS:
        raise ValueError(
            f'Unknown transform backend "{requested}". Must be one of {BACKENDS}.'
        )

    if available is None:
        available = fastcore_transforms_available

    if requested in FALLBACK_BACKENDS:
        return fallback

    if requested == "fastcore":
        if not available():
            raise ValueError(
                'Transform backend "fastcore" was requested but navis-fastcore is '
                "either not installed or too old to provide this transform. "
                "Try `pip install -U navis-fastcore`."
            )
        return "fastcore"

    # "auto": use fastcore if we can, else fall back
    return "fastcore" if available() else fallback