transformnd 0.7.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {transformnd-0.7.2 → transformnd-0.8.0}/PKG-INFO +8 -3
- {transformnd-0.7.2 → transformnd-0.8.0}/README.md +7 -2
- transformnd-0.8.0/pyproject.toml +125 -0
- transformnd-0.7.2/pyproject.toml → transformnd-0.8.0/pyproject.toml.orig +1 -1
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/__init__.py +5 -5
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/base.py +27 -104
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/graph.py +122 -108
- transformnd-0.8.0/src/transformnd/spaced.py +67 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/__init__.py +2 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/affine.py +14 -56
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/bijection.py +5 -15
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/by_dimension.py +25 -8
- transformnd-0.8.0/src/transformnd/transforms/grid.py +47 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/map_axis.py +8 -10
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/moving_least_squares.py +1 -7
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/project_axis.py +13 -8
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/reflection.py +4 -16
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/simple.py +22 -30
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/thinplate.py +2 -7
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/vector_field.py +14 -18
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/types.py +9 -11
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/util.py +6 -9
- transformnd-0.7.2/src/transformnd/constants.py +0 -1
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/__init__.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/base.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/bounding_box.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/pandas.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/polars.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/shapely.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/__init__.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/base.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/bounding_box.py +0 -0
- {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transformnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: ND coordinate transformations
|
|
5
5
|
Author: Chris Barnes
|
|
6
6
|
Author-email: Chris Barnes <chris.barnes@gerbi-gmb.de>
|
|
@@ -86,6 +86,11 @@ See the [tutorial here](https://github.com/clbarnes/transformnd/blob/main/exampl
|
|
|
86
86
|
It is a [marimo](https://marimo.io) notebook.
|
|
87
87
|
Open it with `uv run --group examples marimo edit examples/tutorial.py`.
|
|
88
88
|
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
`transformnd` is pre-1.0 and the API is subject to change.
|
|
92
|
+
Production users should pin the maximum dependency version at the next minor version, like `uv add --bounds minor`.
|
|
93
|
+
|
|
89
94
|
## Implemented transforms
|
|
90
95
|
|
|
91
96
|
All transforms are accessed under the `transformnd.transforms` subpackage.
|
|
@@ -138,11 +143,11 @@ Methods which MUST be implemented:
|
|
|
138
143
|
|
|
139
144
|
Methods which SHOULD be implemented if applicable:
|
|
140
145
|
|
|
141
|
-
- `to_device`: if any of the transformation's parameters need to be placed on a specific device (e.g. affine matrices on the GPU)
|
|
146
|
+
- `to_device`: if any of the transformation's parameters need to be placed on a specific device (e.g. affine matrices on the GPU). The base class implementation returns `self`.
|
|
142
147
|
- `is_identity`: if you can cheaply check whether your transformation is an identity transformation. The base class implementation returns `False`.
|
|
143
148
|
- `to_affine`: if your transformation can be represented as an affine matrix. The base class implementation returns `None`.
|
|
144
149
|
- `invert`: if your transformation can be inverted (default None if not)
|
|
145
|
-
- This automatically implements `__invert__` (the
|
|
150
|
+
- This automatically implements `__invert__` (the `~` operator), which returns `NotImplemented` (probably raising `NotImplementedError`) if `invert` would return `None`.
|
|
146
151
|
|
|
147
152
|
## Contributing
|
|
148
153
|
|
|
@@ -31,6 +31,11 @@ See the [tutorial here](https://github.com/clbarnes/transformnd/blob/main/exampl
|
|
|
31
31
|
It is a [marimo](https://marimo.io) notebook.
|
|
32
32
|
Open it with `uv run --group examples marimo edit examples/tutorial.py`.
|
|
33
33
|
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
`transformnd` is pre-1.0 and the API is subject to change.
|
|
37
|
+
Production users should pin the maximum dependency version at the next minor version, like `uv add --bounds minor`.
|
|
38
|
+
|
|
34
39
|
## Implemented transforms
|
|
35
40
|
|
|
36
41
|
All transforms are accessed under the `transformnd.transforms` subpackage.
|
|
@@ -83,11 +88,11 @@ Methods which MUST be implemented:
|
|
|
83
88
|
|
|
84
89
|
Methods which SHOULD be implemented if applicable:
|
|
85
90
|
|
|
86
|
-
- `to_device`: if any of the transformation's parameters need to be placed on a specific device (e.g. affine matrices on the GPU)
|
|
91
|
+
- `to_device`: if any of the transformation's parameters need to be placed on a specific device (e.g. affine matrices on the GPU). The base class implementation returns `self`.
|
|
87
92
|
- `is_identity`: if you can cheaply check whether your transformation is an identity transformation. The base class implementation returns `False`.
|
|
88
93
|
- `to_affine`: if your transformation can be represented as an affine matrix. The base class implementation returns `None`.
|
|
89
94
|
- `invert`: if your transformation can be inverted (default None if not)
|
|
90
|
-
- This automatically implements `__invert__` (the
|
|
95
|
+
- This automatically implements `__invert__` (the `~` operator), which returns `NotImplemented` (probably raising `NotImplementedError`) if `invert` would return `None`.
|
|
91
96
|
|
|
92
97
|
## Contributing
|
|
93
98
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "transformnd"
|
|
3
|
+
version = "0.8.0"
|
|
4
|
+
description = "ND coordinate transformations"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12, <4.0"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"numpy>=2",
|
|
9
|
+
"networkx>=3",
|
|
10
|
+
"array_api_compat>=1.14",
|
|
11
|
+
"typing-extensions>=4.15.0",
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Topic :: Scientific/Engineering",
|
|
18
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[project.authors]]
|
|
29
|
+
name = "Chris Barnes"
|
|
30
|
+
email = "chris.barnes@gerbi-gmb.de"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
documentation = "https://transformnd.readthedocs.io/en/latest/"
|
|
34
|
+
source = "https://github.com/clbarnes/transformnd"
|
|
35
|
+
issues = "https://github.com/clbarnes/transformnd/issues"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
thinplatesplines = ["morphops>=0.1.13"]
|
|
39
|
+
movingleastsquares = ["molesq>=0.4.0"]
|
|
40
|
+
pandas = ["pandas>=3.0.2"]
|
|
41
|
+
shapely = ["shapely>=2.1.2"]
|
|
42
|
+
polars = ["polars>=1.40.1"]
|
|
43
|
+
vectorfield = ["scipy>=1.17.1"]
|
|
44
|
+
vectorfield-dask = [
|
|
45
|
+
"dask>=2026.3.0",
|
|
46
|
+
"dask-image>=2026.5.0",
|
|
47
|
+
]
|
|
48
|
+
transforms = [
|
|
49
|
+
"transformnd[thinplatesplines]",
|
|
50
|
+
"transformnd[movingleastsquares]",
|
|
51
|
+
"transformnd[vectorfield]",
|
|
52
|
+
"transformnd[vectorfield-dask]",
|
|
53
|
+
]
|
|
54
|
+
adapters = [
|
|
55
|
+
"transformnd[pandas]",
|
|
56
|
+
"transformnd[shapely]",
|
|
57
|
+
"transformnd[polars]",
|
|
58
|
+
]
|
|
59
|
+
all = [
|
|
60
|
+
"transformnd[transforms]",
|
|
61
|
+
"transformnd[adapters]",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[dependency-groups]
|
|
65
|
+
test = [
|
|
66
|
+
"pytest",
|
|
67
|
+
"jax",
|
|
68
|
+
"dask>=2026.3.0",
|
|
69
|
+
]
|
|
70
|
+
lint = [
|
|
71
|
+
"ruff",
|
|
72
|
+
"mypy",
|
|
73
|
+
"prek>=0.3.9",
|
|
74
|
+
"types-shapely>=2.1.0.20260408",
|
|
75
|
+
"pydoclint>=0.8.6",
|
|
76
|
+
]
|
|
77
|
+
doc = ["pdoc>=16.0.0"]
|
|
78
|
+
examples = [
|
|
79
|
+
"marimo>=0.9",
|
|
80
|
+
"matplotlib>=3.10.8",
|
|
81
|
+
"pandas>=3.0.2",
|
|
82
|
+
"pooch>=1.9.0",
|
|
83
|
+
"scikit-image>=0.26.0",
|
|
84
|
+
]
|
|
85
|
+
bench = ["pytest-benchmark>=5.2.3"]
|
|
86
|
+
|
|
87
|
+
[[dependency-groups.dev]]
|
|
88
|
+
include-group = "test"
|
|
89
|
+
|
|
90
|
+
[[dependency-groups.dev]]
|
|
91
|
+
include-group = "lint"
|
|
92
|
+
|
|
93
|
+
[[dependency-groups.dev]]
|
|
94
|
+
include-group = "doc"
|
|
95
|
+
|
|
96
|
+
[[dependency-groups.dev]]
|
|
97
|
+
include-group = "examples"
|
|
98
|
+
|
|
99
|
+
[[dependency-groups.dev]]
|
|
100
|
+
include-group = "bench"
|
|
101
|
+
|
|
102
|
+
[build-system]
|
|
103
|
+
requires = ["uv_build>=0.11.0,<0.12.0"]
|
|
104
|
+
build-backend = "uv_build"
|
|
105
|
+
|
|
106
|
+
[tool.mypy]
|
|
107
|
+
ignore_missing_imports = true
|
|
108
|
+
check_untyped_defs = true
|
|
109
|
+
|
|
110
|
+
[tool.pytest]
|
|
111
|
+
testpaths = [
|
|
112
|
+
"tests",
|
|
113
|
+
"bench",
|
|
114
|
+
]
|
|
115
|
+
addopts = ["--benchmark-skip"]
|
|
116
|
+
|
|
117
|
+
[tool.pydoclint]
|
|
118
|
+
arg-type-hints-in-docstring = false
|
|
119
|
+
allow-init-docstring = true
|
|
120
|
+
|
|
121
|
+
[tool.ruff.lint]
|
|
122
|
+
external = [
|
|
123
|
+
"DOC",
|
|
124
|
+
"D",
|
|
125
|
+
]
|
|
@@ -8,12 +8,12 @@ You can find some examples here:
|
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from .base import Transform, TransformSequence,
|
|
12
|
-
from .
|
|
13
|
-
from .types import Spaces, TransformSignature, NDims
|
|
11
|
+
from .base import Transform, TransformSequence, TransformFnWrapper
|
|
12
|
+
from .types import TransformSignature, NDims, SpaceRef
|
|
14
13
|
from . import transforms
|
|
15
14
|
from . import adapters
|
|
16
15
|
from .graph import TransformGraph
|
|
16
|
+
from .spaced import Spaced
|
|
17
17
|
from importlib.metadata import version as _version
|
|
18
18
|
|
|
19
19
|
__version__ = _version("transformnd")
|
|
@@ -22,11 +22,11 @@ __all__ = [
|
|
|
22
22
|
"Transform",
|
|
23
23
|
"TransformGraph",
|
|
24
24
|
"TransformSequence",
|
|
25
|
-
"
|
|
25
|
+
"TransformFnWrapper",
|
|
26
26
|
"TransformSignature",
|
|
27
27
|
"SpaceRef",
|
|
28
28
|
"transforms",
|
|
29
29
|
"adapters",
|
|
30
|
-
"Spaces",
|
|
31
30
|
"NDims",
|
|
31
|
+
"Spaced",
|
|
32
32
|
]
|
|
@@ -11,14 +11,12 @@ from types import ModuleType
|
|
|
11
11
|
from array_api_compat import array_namespace
|
|
12
12
|
|
|
13
13
|
from .util import (
|
|
14
|
-
SpaceRef,
|
|
15
|
-
same_or_none,
|
|
16
|
-
space_str,
|
|
17
14
|
ArrayT,
|
|
15
|
+
join_strs,
|
|
18
16
|
)
|
|
19
17
|
from itertools import pairwise
|
|
20
18
|
|
|
21
|
-
from .types import TransformSignature,
|
|
19
|
+
from .types import TransformSignature, NDims
|
|
22
20
|
|
|
23
21
|
if TYPE_CHECKING:
|
|
24
22
|
from .transforms import Affine
|
|
@@ -30,19 +28,14 @@ class Transform[ArrayT](ABC):
|
|
|
30
28
|
def __init__(
|
|
31
29
|
self,
|
|
32
30
|
ndims: NDims,
|
|
33
|
-
*,
|
|
34
|
-
spaces: Spaces = Spaces(None, None),
|
|
35
31
|
):
|
|
36
32
|
"""
|
|
37
33
|
Parameters
|
|
38
34
|
----------
|
|
39
35
|
ndims
|
|
40
36
|
Source and target dimensionality.
|
|
41
|
-
spaces
|
|
42
|
-
Optional source and target spaces
|
|
43
37
|
"""
|
|
44
38
|
self.ndims: NDims = ndims
|
|
45
|
-
self.spaces: Spaces = spaces
|
|
46
39
|
|
|
47
40
|
def is_identity(self) -> bool:
|
|
48
41
|
"""Whether this is a no-op transformation."""
|
|
@@ -105,18 +98,18 @@ class Transform[ArrayT](ABC):
|
|
|
105
98
|
"""
|
|
106
99
|
pass
|
|
107
100
|
|
|
108
|
-
def invert(self) -> Transform | None:
|
|
101
|
+
def invert(self) -> Transform[ArrayT] | None:
|
|
109
102
|
"""Invert the transformation, returning `None` if not possible."""
|
|
110
103
|
return None
|
|
111
104
|
|
|
112
|
-
def __invert__(self) -> Transform:
|
|
105
|
+
def __invert__(self) -> Transform[ArrayT]:
|
|
113
106
|
"""Invert transformation if possible.
|
|
114
107
|
|
|
115
108
|
Returns `NotImplemented` otherwise (will raise `NotImplementedError`).
|
|
116
109
|
|
|
117
110
|
Returns
|
|
118
111
|
-------
|
|
119
|
-
Transform
|
|
112
|
+
Transform[ArrayT]
|
|
120
113
|
Inverted transformation.
|
|
121
114
|
"""
|
|
122
115
|
t = self.invert()
|
|
@@ -141,10 +134,9 @@ class Transform[ArrayT](ABC):
|
|
|
141
134
|
Returns
|
|
142
135
|
-------
|
|
143
136
|
Self
|
|
144
|
-
A new transform instance with parameters on the target device
|
|
145
|
-
or NotImplemented if the subclass does not support device placement.
|
|
137
|
+
A new transform instance with parameters on the target device
|
|
146
138
|
"""
|
|
147
|
-
return
|
|
139
|
+
return self
|
|
148
140
|
|
|
149
141
|
def __or__(self, other: Transform[ArrayT]) -> TransformSequence[ArrayT]:
|
|
150
142
|
"""Compose transformations into a sequence.
|
|
@@ -166,7 +158,6 @@ class Transform[ArrayT](ABC):
|
|
|
166
158
|
transforms = as_transform_list(self) + as_transform_list(other)
|
|
167
159
|
return TransformSequence[ArrayT](
|
|
168
160
|
transforms,
|
|
169
|
-
spaces=Spaces(self.spaces.source, other.spaces.target),
|
|
170
161
|
)
|
|
171
162
|
|
|
172
163
|
def __ror__(self, other: Transform[ArrayT]) -> TransformSequence[ArrayT]:
|
|
@@ -189,17 +180,13 @@ class Transform[ArrayT](ABC):
|
|
|
189
180
|
transforms = as_transform_list(other) + as_transform_list(self)
|
|
190
181
|
return TransformSequence(
|
|
191
182
|
transforms,
|
|
192
|
-
spaces=Spaces(other.spaces.source, self.spaces.target),
|
|
193
183
|
)
|
|
194
184
|
|
|
195
185
|
def __str__(self) -> str:
|
|
196
|
-
|
|
197
|
-
src = space_str(self.spaces.source)
|
|
198
|
-
tgt = space_str(self.spaces.target)
|
|
199
|
-
return f"{cls_name}[{src}->{tgt}]"
|
|
186
|
+
return f"{type(self).__qualname__}@{hex(id(self))}[{self.ndims}]"
|
|
200
187
|
|
|
201
188
|
|
|
202
|
-
class
|
|
189
|
+
class TransformFnWrapper(Transform[ArrayT]):
|
|
203
190
|
"""Wrapper around an arbitrary function which transforms coordinates."""
|
|
204
191
|
|
|
205
192
|
def __init__(
|
|
@@ -207,8 +194,6 @@ class TransformWrapper(Transform[ArrayT]):
|
|
|
207
194
|
fn: TransformSignature[ArrayT],
|
|
208
195
|
in_ndim: int,
|
|
209
196
|
out_ndim: int,
|
|
210
|
-
*,
|
|
211
|
-
spaces: Spaces = Spaces(None, None),
|
|
212
197
|
):
|
|
213
198
|
"""Wrapper around an arbitrary function.
|
|
214
199
|
|
|
@@ -223,46 +208,28 @@ class TransformWrapper(Transform[ArrayT]):
|
|
|
223
208
|
Dimensionality of the input coordinates.
|
|
224
209
|
out_ndim
|
|
225
210
|
Dimensionality of the output coordinates.
|
|
226
|
-
spaces
|
|
227
|
-
Optional source and target spaces
|
|
228
211
|
"""
|
|
229
|
-
super().__init__(NDims(in_ndim, out_ndim)
|
|
212
|
+
super().__init__(NDims(in_ndim, out_ndim))
|
|
230
213
|
self.fn = fn
|
|
231
214
|
|
|
232
215
|
def apply(self, coords: ArrayT) -> ArrayT:
|
|
233
216
|
self._validate_coords(coords)
|
|
234
217
|
return self.fn(coords)
|
|
235
218
|
|
|
219
|
+
def __str__(self) -> str:
|
|
220
|
+
return f"{super().__str__()}({self.fn})"
|
|
236
221
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
target_space: SpaceRef | None = None,
|
|
241
|
-
) -> Transform[ArrayT]:
|
|
242
|
-
src_tgt = (t.spaces.source, t.spaces.target)
|
|
243
|
-
src = same_or_none(src_tgt[0], source_space, default=None)
|
|
244
|
-
tgt = same_or_none(src_tgt[1], target_space, default=None)
|
|
245
|
-
if (src, tgt) != src_tgt:
|
|
246
|
-
t = copy(t)
|
|
247
|
-
t.spaces = Spaces(src, tgt)
|
|
248
|
-
return t
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def infer_spaces(
|
|
252
|
-
transforms: Sequence[Transform[ArrayT]], source_space=None, target_space=None
|
|
253
|
-
) -> list[Transform[ArrayT]]:
|
|
254
|
-
prev_tgts = [source_space]
|
|
255
|
-
next_srcs = []
|
|
256
|
-
for t1, t2 in pairwise(transforms):
|
|
257
|
-
prev_tgts.append(t1.spaces.target)
|
|
258
|
-
next_srcs.append(t2.spaces.source)
|
|
222
|
+
@classmethod
|
|
223
|
+
def from_flat(cls, fn: TransformSignature[ArrayT]) -> Self:
|
|
224
|
+
"""Create a 1D transform from a function which would take and return a 1D array."""
|
|
259
225
|
|
|
260
|
-
|
|
226
|
+
def fn2(arr: ArrayT) -> ArrayT:
|
|
227
|
+
xp = array_namespace(arr)
|
|
228
|
+
flat = xp.reshape(arr, (-1,))
|
|
229
|
+
transformed = fn(flat)
|
|
230
|
+
return xp.expand_dims(transformed, 1)
|
|
261
231
|
|
|
262
|
-
|
|
263
|
-
for t, next_src, prev_tgt in zip(transforms, next_srcs, prev_tgts):
|
|
264
|
-
out.append(_with_spaces(t, prev_tgt, next_src))
|
|
265
|
-
return out
|
|
232
|
+
return cls(fn2, 1, 1)
|
|
266
233
|
|
|
267
234
|
|
|
268
235
|
def as_transform_list(t: Transform[ArrayT]) -> list[Transform[ArrayT]]:
|
|
@@ -278,8 +245,6 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
278
245
|
def __init__(
|
|
279
246
|
self,
|
|
280
247
|
transforms: Sequence[Transform[ArrayT]],
|
|
281
|
-
*,
|
|
282
|
-
spaces: Spaces = Spaces(None, None),
|
|
283
248
|
) -> None:
|
|
284
249
|
"""Combine transforms by chaining them.
|
|
285
250
|
|
|
@@ -291,16 +256,13 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
291
256
|
transforms :
|
|
292
257
|
Items which are a TransformSequences
|
|
293
258
|
will each still be treated as a single transform.
|
|
294
|
-
spaces :
|
|
295
|
-
Optional source and target spaces.
|
|
296
|
-
Can also be inferred from the first and last transforms.
|
|
297
259
|
|
|
298
260
|
Raises
|
|
299
261
|
------
|
|
300
262
|
ValueError
|
|
301
263
|
If spaces are incompatible.
|
|
302
264
|
"""
|
|
303
|
-
ts =
|
|
265
|
+
ts = list(transforms)
|
|
304
266
|
if not ts:
|
|
305
267
|
raise ValueError("Empty transform sequence")
|
|
306
268
|
|
|
@@ -312,13 +274,9 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
312
274
|
f"and the next source is {t2.ndims.source}D"
|
|
313
275
|
)
|
|
314
276
|
|
|
315
|
-
spaces = Spaces(ts[0].spaces.source, ts[-1].spaces.target)
|
|
316
277
|
ndims = NDims(ts[0].ndims.source, ts[-1].ndims.target)
|
|
317
278
|
|
|
318
|
-
super().__init__(
|
|
319
|
-
ndims,
|
|
320
|
-
spaces=spaces,
|
|
321
|
-
)
|
|
279
|
+
super().__init__(ndims)
|
|
322
280
|
|
|
323
281
|
self.transforms: list[Transform[ArrayT]] = ts
|
|
324
282
|
|
|
@@ -347,7 +305,6 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
347
305
|
return None
|
|
348
306
|
return type(self)(
|
|
349
307
|
transforms,
|
|
350
|
-
spaces=self.spaces.invert(),
|
|
351
308
|
)
|
|
352
309
|
|
|
353
310
|
def apply(self, coords: ArrayT) -> ArrayT:
|
|
@@ -360,42 +317,9 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
360
317
|
result.transforms = [t.to_device(xp, device) for t in self.transforms]
|
|
361
318
|
return result
|
|
362
319
|
|
|
363
|
-
def list_spaces(self, skip_none: bool = False) -> list[SpaceRef]:
|
|
364
|
-
"""List spaces in this transform.
|
|
365
|
-
|
|
366
|
-
Parameters
|
|
367
|
-
----------
|
|
368
|
-
skip_none
|
|
369
|
-
Whether to skip undefined spaces, default False.
|
|
370
|
-
|
|
371
|
-
Returns
|
|
372
|
-
-------
|
|
373
|
-
list[SpaceRef]
|
|
374
|
-
The list of spaces.
|
|
375
|
-
"""
|
|
376
|
-
spaces = [self.spaces.source] + [t.spaces.target for t in self.transforms]
|
|
377
|
-
if skip_none:
|
|
378
|
-
spaces = [s for s in spaces if s is not None]
|
|
379
|
-
return spaces
|
|
380
|
-
|
|
381
|
-
def split(self) -> Iterator[Transform[ArrayT]]:
|
|
382
|
-
"""Split the sequence where an intermediate space is known."""
|
|
383
|
-
this_seq = []
|
|
384
|
-
|
|
385
|
-
for t in self.transforms:
|
|
386
|
-
if t.spaces.source is not None and t.spaces.target is not None:
|
|
387
|
-
yield t
|
|
388
|
-
continue
|
|
389
|
-
|
|
390
|
-
this_seq.append(t)
|
|
391
|
-
if t.spaces.target is not None:
|
|
392
|
-
yield type(self)(this_seq)
|
|
393
|
-
this_seq = []
|
|
394
|
-
|
|
395
320
|
def __str__(self) -> str:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return f"{cls_name}[{spaces_str}]"
|
|
321
|
+
spaces_str = join_strs(self.transforms, "|")
|
|
322
|
+
return f"{super().__str__()}({spaces_str})"
|
|
399
323
|
|
|
400
324
|
def __getitem__(self, idx: slice | int):
|
|
401
325
|
if isinstance(idx, int):
|
|
@@ -418,7 +342,7 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
418
342
|
out.extend(t.flatten())
|
|
419
343
|
else:
|
|
420
344
|
out.append(t)
|
|
421
|
-
return TransformSequence(out
|
|
345
|
+
return TransformSequence(out) # type:ignore
|
|
422
346
|
|
|
423
347
|
def simplify(self, drop_inverse: bool = True):
|
|
424
348
|
"""Reduce the number of transformations in this sequence if possible.
|
|
@@ -461,7 +385,7 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
|
|
|
461
385
|
if not out:
|
|
462
386
|
out.append(Identity(self.ndims.source))
|
|
463
387
|
|
|
464
|
-
return type(self)(out
|
|
388
|
+
return type(self)(out)
|
|
465
389
|
|
|
466
390
|
def to_affine(self) -> Affine[ArrayT] | None:
|
|
467
391
|
simple = self.simplify(True)
|
|
@@ -475,6 +399,5 @@ def add_to_output(transform: Transform, lst: list[Transform]) -> bool:
|
|
|
475
399
|
return False
|
|
476
400
|
|
|
477
401
|
transform = copy(transform)
|
|
478
|
-
transform.spaces = Spaces(None, None)
|
|
479
402
|
lst.append(transform)
|
|
480
403
|
return True
|