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.
Files changed (33) hide show
  1. {transformnd-0.7.2 → transformnd-0.8.0}/PKG-INFO +8 -3
  2. {transformnd-0.7.2 → transformnd-0.8.0}/README.md +7 -2
  3. transformnd-0.8.0/pyproject.toml +125 -0
  4. transformnd-0.7.2/pyproject.toml → transformnd-0.8.0/pyproject.toml.orig +1 -1
  5. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/__init__.py +5 -5
  6. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/base.py +27 -104
  7. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/graph.py +122 -108
  8. transformnd-0.8.0/src/transformnd/spaced.py +67 -0
  9. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/__init__.py +2 -0
  10. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/affine.py +14 -56
  11. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/bijection.py +5 -15
  12. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/by_dimension.py +25 -8
  13. transformnd-0.8.0/src/transformnd/transforms/grid.py +47 -0
  14. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/map_axis.py +8 -10
  15. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/moving_least_squares.py +1 -7
  16. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/project_axis.py +13 -8
  17. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/reflection.py +4 -16
  18. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/simple.py +22 -30
  19. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/thinplate.py +2 -7
  20. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/transforms/vector_field.py +14 -18
  21. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/types.py +9 -11
  22. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/util.py +6 -9
  23. transformnd-0.7.2/src/transformnd/constants.py +0 -1
  24. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/__init__.py +0 -0
  25. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/base.py +0 -0
  26. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/bounding_box.py +0 -0
  27. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/pandas.py +0 -0
  28. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/polars.py +0 -0
  29. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/adapters/shapely.py +0 -0
  30. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/__init__.py +0 -0
  31. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/base.py +0 -0
  32. {transformnd-0.7.2 → transformnd-0.8.0}/src/transformnd/extents/bounding_box.py +0 -0
  33. {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.7.2
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 `~my_transform` operator), which returns `NotImplemented` (probably raising `NotImplementedError`) if `invert` would return `None`.
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 `~my_transform` operator), which returns `NotImplemented` (probably raising `NotImplementedError`) if `invert` would return `None`.
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
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transformnd"
3
- version = "0.7.2"
3
+ version = "0.8.0"
4
4
  description = "ND coordinate transformations"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Chris Barnes", email = "chris.barnes@gerbi-gmb.de" }]
@@ -8,12 +8,12 @@ You can find some examples here:
8
8
 
9
9
  """
10
10
 
11
- from .base import Transform, TransformSequence, TransformWrapper
12
- from .util import SpaceRef
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
- "TransformWrapper",
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, Spaces, NDims
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 NotImplemented
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
- cls_name = type(self).__name__
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 TransformWrapper(Transform[ArrayT]):
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), spaces=spaces)
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
- def _with_spaces(
238
- t: Transform[ArrayT],
239
- source_space: SpaceRef | None = None,
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
- next_srcs.append(target_space)
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
- out = []
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 = infer_spaces(transforms, *spaces)
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
- cls_name = type(self).__name__
397
- spaces_str = "->".join(space_str(s) for s in self.list_spaces())
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, spaces=self.spaces) # type:ignore
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, spaces=self.spaces)
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