transformnd 0.8.0__tar.gz → 0.9.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 (32) hide show
  1. {transformnd-0.8.0 → transformnd-0.9.0}/PKG-INFO +1 -1
  2. {transformnd-0.8.0 → transformnd-0.9.0}/pyproject.toml +1 -1
  3. {transformnd-0.8.0 → transformnd-0.9.0}/pyproject.toml.orig +1 -1
  4. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/base.py +37 -8
  5. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/graph.py +14 -0
  6. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/simple.py +11 -0
  7. {transformnd-0.8.0 → transformnd-0.9.0}/README.md +0 -0
  8. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/__init__.py +0 -0
  9. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/__init__.py +0 -0
  10. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/base.py +0 -0
  11. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/bounding_box.py +0 -0
  12. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/pandas.py +0 -0
  13. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/polars.py +0 -0
  14. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/adapters/shapely.py +0 -0
  15. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/extents/__init__.py +0 -0
  16. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/extents/base.py +0 -0
  17. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/extents/bounding_box.py +0 -0
  18. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/py.typed +0 -0
  19. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/spaced.py +0 -0
  20. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/__init__.py +0 -0
  21. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/affine.py +0 -0
  22. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/bijection.py +0 -0
  23. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/by_dimension.py +0 -0
  24. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/grid.py +0 -0
  25. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/map_axis.py +0 -0
  26. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/moving_least_squares.py +0 -0
  27. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/project_axis.py +0 -0
  28. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/reflection.py +0 -0
  29. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/thinplate.py +0 -0
  30. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/transforms/vector_field.py +0 -0
  31. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/types.py +0 -0
  32. {transformnd-0.8.0 → transformnd-0.9.0}/src/transformnd/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transformnd
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: ND coordinate transformations
5
5
  Author: Chris Barnes
6
6
  Author-email: Chris Barnes <chris.barnes@gerbi-gmb.de>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transformnd"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "ND coordinate transformations"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12, <4.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transformnd"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "ND coordinate transformations"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Chris Barnes", email = "chris.barnes@gerbi-gmb.de" }]
@@ -240,7 +240,11 @@ def as_transform_list(t: Transform[ArrayT]) -> list[Transform[ArrayT]]:
240
240
 
241
241
 
242
242
  class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
243
- """Chain transforms, applying one after another."""
243
+ """Chain transforms, applying one after another.
244
+
245
+ The `TransformSequence()` constructor takes a sequence of transforms which must not be empty.
246
+ Empty sequences can be handled with `TransformSequence.empty(ndim: int)`.
247
+ """
244
248
 
245
249
  def __init__(
246
250
  self,
@@ -248,8 +252,10 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
248
252
  ) -> None:
249
253
  """Combine transforms by chaining them.
250
254
 
251
- Also checks for consistent dimensionality and space references,
252
- inferring if None.
255
+ Empty sequences raise an error;
256
+ use the `TransformSequence.empty(ndim)` constructor instead.
257
+
258
+ Also checks for consistent dimensionality.
253
259
 
254
260
  Parameters
255
261
  ----------
@@ -260,11 +266,13 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
260
266
  Raises
261
267
  ------
262
268
  ValueError
263
- If spaces are incompatible.
269
+ If spaces are incompatible, or no spaces are given.
264
270
  """
265
271
  ts = list(transforms)
266
272
  if not ts:
267
- raise ValueError("Empty transform sequence")
273
+ raise ValueError(
274
+ "Empty transform sequence; use TransformSequence.empty(ndim)"
275
+ )
268
276
 
269
277
  for idx, (t1, t2) in enumerate(pairwise(ts)):
270
278
  if t1.ndims.target != t2.ndims.source:
@@ -280,6 +288,14 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
280
288
 
281
289
  self.transforms: list[Transform[ArrayT]] = ts
282
290
 
291
+ @classmethod
292
+ def empty(cls, ndim: int) -> Self:
293
+ from .transforms import Identity
294
+
295
+ out = cls([Identity(ndim)])
296
+ out.transforms.pop()
297
+ return out
298
+
283
299
  def __iter__(self) -> Iterator[Transform[ArrayT]]:
284
300
  """Iterate through component transforms.
285
301
 
@@ -299,6 +315,9 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
299
315
  return len(self.transforms)
300
316
 
301
317
  def invert(self) -> Transform[ArrayT] | None:
318
+ if self.is_empty():
319
+ return type(self).empty(self.ndims.source)
320
+
302
321
  try:
303
322
  transforms = [~t for t in reversed(self.transforms)]
304
323
  except NotImplementedError:
@@ -314,7 +333,8 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
314
333
 
315
334
  def to_device(self, xp: ModuleType, device: str | None = None) -> Self:
316
335
  result = copy(self)
317
- result.transforms = [t.to_device(xp, device) for t in self.transforms]
336
+ if not self.is_empty():
337
+ result.transforms = [t.to_device(xp, device) for t in self.transforms]
318
338
  return result
319
339
 
320
340
  def __str__(self) -> str:
@@ -329,10 +349,16 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
329
349
  def is_identity(self) -> bool:
330
350
  return all(t.is_identity() for t in self)
331
351
 
352
+ def is_empty(self) -> bool:
353
+ return not self.transforms
354
+
332
355
  def flatten(self, drop_inverse: bool = True) -> Self:
333
356
  """Flatten nested sequences."""
334
357
  from .transforms.bijection import Bijection
335
358
 
359
+ if self.is_empty():
360
+ return copy(self)
361
+
336
362
  out: list[Transform[ArrayT]] = []
337
363
 
338
364
  for t in self.transforms:
@@ -357,7 +383,8 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
357
383
  Does not check whether transforms invert each other,
358
384
  e.g. `Translation(1) | Translation(-1)`.
359
385
  """
360
- from .transforms import Identity
386
+ if self.is_empty():
387
+ return copy(self)
361
388
 
362
389
  out: list[Transform[ArrayT]] = []
363
390
  affine = None
@@ -383,11 +410,13 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
383
410
  add_to_output(affine, out)
384
411
 
385
412
  if not out:
386
- out.append(Identity(self.ndims.source))
413
+ return type(self).empty(self.ndims.source)
387
414
 
388
415
  return type(self)(out)
389
416
 
390
417
  def to_affine(self) -> Affine[ArrayT] | None:
418
+ if self.is_empty():
419
+ return Affine.identity(self.ndims.source) # type:ignore
391
420
  simple = self.simplify(True)
392
421
  if len(simple) != 1:
393
422
  return None
@@ -225,7 +225,21 @@ class TransformGraph(Generic[ArrayT, SpaceRef]):
225
225
  -------
226
226
  TransformSequence[ArrayT]
227
227
  The shortest transform sequence between the spaces.
228
+
229
+ Raises
230
+ ------
231
+ ValueError
232
+ Unknown source or target space.
228
233
  """
234
+ src_ndim = self.ndim(source_space)
235
+ if src_ndim is None:
236
+ raise ValueError(f"Unknown source space {source_space}")
237
+ elif source_space == target_space:
238
+ return TransformSequence.empty(src_ndim) # type:ignore
239
+
240
+ if self.ndim(target_space) is None:
241
+ raise ValueError(f"Unknown target space {source_space}")
242
+
229
243
  path = nx.shortest_path(self.graph, source_space, target_space, weight) # type:ignore
230
244
  transforms = []
231
245
  if len(path) == 1:
@@ -45,6 +45,9 @@ class Identity(Transform[ArrayT]):
45
45
  def __str__(self) -> str:
46
46
  return f"{super().__str__()}({self.ndims.source})"
47
47
 
48
+ def is_identity(self) -> bool:
49
+ return True
50
+
48
51
 
49
52
  class Translate(Transform[ArrayT]):
50
53
  """Translate coordinates by addition."""
@@ -72,6 +75,10 @@ class Translate(Transform[ArrayT]):
72
75
  )
73
76
  super().__init__(NDims(len(self.translation), len(self.translation)))
74
77
 
78
+ def is_identity(self) -> bool:
79
+ xp = array_namespace(self.translation)
80
+ return xp.all(self.translation == 0)
81
+
75
82
  def to_affine(self) -> Affine[ArrayT]:
76
83
  return Affine[ArrayT].translation(self.translation)
77
84
 
@@ -140,3 +147,7 @@ class Scale(Transform[ArrayT]):
140
147
 
141
148
  def __str__(self) -> str:
142
149
  return f"{super().__str__()}({join_strs(self.scale)})"
150
+
151
+ def is_identity(self) -> bool:
152
+ xp = array_namespace(self.scale)
153
+ return xp.all(self.scale == 1)
File without changes