transformnd 0.7.0__tar.gz → 0.7.2__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 (30) hide show
  1. {transformnd-0.7.0 → transformnd-0.7.2}/PKG-INFO +2 -2
  2. {transformnd-0.7.0 → transformnd-0.7.2}/README.md +1 -1
  3. {transformnd-0.7.0 → transformnd-0.7.2}/pyproject.toml +5 -3
  4. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/__init__.py +6 -0
  5. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/base.py +8 -0
  6. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/__init__.py +1 -3
  7. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/project_axis.py +19 -85
  8. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/__init__.py +0 -0
  9. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/base.py +0 -0
  10. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/bounding_box.py +0 -0
  11. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/pandas.py +0 -0
  12. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/polars.py +0 -0
  13. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/adapters/shapely.py +0 -0
  14. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/constants.py +0 -0
  15. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/extents/__init__.py +0 -0
  16. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/extents/base.py +0 -0
  17. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/extents/bounding_box.py +0 -0
  18. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/graph.py +0 -0
  19. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/py.typed +0 -0
  20. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/affine.py +0 -0
  21. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/bijection.py +0 -0
  22. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/by_dimension.py +0 -0
  23. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/map_axis.py +0 -0
  24. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/moving_least_squares.py +0 -0
  25. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/reflection.py +0 -0
  26. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/simple.py +0 -0
  27. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/thinplate.py +0 -0
  28. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/transforms/vector_field.py +0 -0
  29. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/types.py +0 -0
  30. {transformnd-0.7.0 → transformnd-0.7.2}/src/transformnd/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transformnd
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: ND coordinate transformations
5
5
  Author: Chris Barnes
6
6
  Author-email: Chris Barnes <chris.barnes@gerbi-gmb.de>
@@ -84,7 +84,7 @@ and implements these adapters for a few common types.
84
84
 
85
85
  See the [tutorial here](https://github.com/clbarnes/transformnd/blob/main/examples/tutorial.py).
86
86
  It is a [marimo](https://marimo.io) notebook.
87
- Open it with `uv run --group tutorial marimo edit examples/tutorial.py`.
87
+ Open it with `uv run --group examples marimo edit examples/tutorial.py`.
88
88
 
89
89
  ## Implemented transforms
90
90
 
@@ -29,7 +29,7 @@ and implements these adapters for a few common types.
29
29
 
30
30
  See the [tutorial here](https://github.com/clbarnes/transformnd/blob/main/examples/tutorial.py).
31
31
  It is a [marimo](https://marimo.io) notebook.
32
- Open it with `uv run --group tutorial marimo edit examples/tutorial.py`.
32
+ Open it with `uv run --group examples marimo edit examples/tutorial.py`.
33
33
 
34
34
  ## Implemented transforms
35
35
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transformnd"
3
- version = "0.7.0"
3
+ version = "0.7.2"
4
4
  description = "ND coordinate transformations"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Chris Barnes", email = "chris.barnes@gerbi-gmb.de" }]
@@ -62,7 +62,7 @@ dev = [
62
62
  {include-group = "test"},
63
63
  {include-group = "lint"},
64
64
  {include-group = "doc"},
65
- {include-group = "tutorial"},
65
+ {include-group = "examples"},
66
66
  {include-group = "bench"},
67
67
  ]
68
68
  test = [
@@ -80,10 +80,12 @@ lint = [
80
80
  doc = [
81
81
  "pdoc>=16.0.0",
82
82
  ]
83
- tutorial = [
83
+ examples = [
84
84
  "marimo>=0.9",
85
85
  "matplotlib>=3.10.8",
86
86
  "pandas>=3.0.2",
87
+ "pooch>=1.9.0",
88
+ "scikit-image>=0.26.0",
87
89
  ]
88
90
  bench = [
89
91
  "pytest-benchmark>=5.2.3",
@@ -1,5 +1,11 @@
1
1
  """
2
2
  .. include:: ../../README.md
3
+
4
+ You can find some examples here:
5
+
6
+ - [Tutorial](./examples/tutorial.html)
7
+ - [Image transformation](./examples/image.html)
8
+
3
9
  """
4
10
 
5
11
  from .base import Transform, TransformSequence, TransformWrapper
@@ -304,6 +304,14 @@ class TransformSequence(Transform[ArrayT], Sequence[Transform[ArrayT]]):
304
304
  if not ts:
305
305
  raise ValueError("Empty transform sequence")
306
306
 
307
+ for idx, (t1, t2) in enumerate(pairwise(ts)):
308
+ if t1.ndims.target != t2.ndims.source:
309
+ raise ValueError(
310
+ "Incompatible dimensionality: "
311
+ f"transform {idx}'s target is {t1.ndims.target}D "
312
+ f"and the next source is {t2.ndims.source}D"
313
+ )
314
+
307
315
  spaces = Spaces(ts[0].spaces.source, ts[-1].spaces.target)
308
316
  ndims = NDims(ts[0].ndims.source, ts[-1].ndims.target)
309
317
 
@@ -5,7 +5,7 @@ from .reflection import Reflect
5
5
  from .simple import Identity, Scale, Translate
6
6
  from .map_axis import MapAxis
7
7
  from .bijection import Bijection
8
- from .project_axis import ProjectAxis, Insert, Remove
8
+ from .project_axis import ProjectAxis
9
9
  from .by_dimension import ByDimension, SubTransform
10
10
  from .vector_field import Coordinates, Displacements
11
11
  from .moving_least_squares import MovingLeastSquares
@@ -15,8 +15,6 @@ __all__ = [
15
15
  "Affine",
16
16
  "Identity",
17
17
  "ProjectAxis",
18
- "Insert",
19
- "Remove",
20
18
  "Reflect",
21
19
  "Scale",
22
20
  "Translate",
@@ -1,69 +1,14 @@
1
1
  from __future__ import annotations
2
- from abc import ABC, abstractmethod
3
- from copy import copy
4
- from typing import Self, Sequence
2
+ from typing import Self
5
3
 
6
4
  import numpy as np
7
5
  from array_api_compat import array_namespace
8
6
  from transformnd.transforms import Affine
9
7
  from transformnd.types import NDims, Spaces
10
- from dataclasses import dataclass
11
8
  from ..base import Transform
12
9
  from ..types import ArrayT
13
10
 
14
11
 
15
- @dataclass(frozen=True, eq=True)
16
- class BaseOperation(ABC):
17
- idx: int
18
- """Which axis to apply the operation to."""
19
-
20
- def __post_init__(self):
21
- if self.idx < 0:
22
- raise ValueError("insert/remove idx must be positive")
23
-
24
- @abstractmethod
25
- def check(self, ndim: int) -> int: ...
26
-
27
- @abstractmethod
28
- def invert(self) -> BaseOperation: ...
29
-
30
-
31
- @dataclass(frozen=True, eq=True)
32
- class Insert(BaseOperation):
33
- """Component of the `ProjectAxis` transform which inserts a new axis."""
34
-
35
- def check(self, ndim: int) -> int:
36
- if self.idx > ndim or self.idx <= -ndim:
37
- raise ValueError(
38
- f"Index {self.idx} is out of range for dimensionality {ndim}"
39
- )
40
- return ndim + 1
41
-
42
- def invert(self) -> Remove:
43
- return Remove(self.idx)
44
-
45
-
46
- @dataclass(frozen=True, eq=True)
47
- class Remove(BaseOperation):
48
- """Component of the `ProjectAxis` transform which removes an existing axis."""
49
-
50
- def check(self, ndim: int) -> int:
51
- if self.idx >= ndim or self.idx <= -ndim:
52
- raise ValueError(
53
- f"Index {self.idx} is out of range for dimensionality {ndim}"
54
- )
55
- return ndim - 1
56
-
57
- def invert(self) -> Insert:
58
- if self.idx == -1:
59
- raise ValueError("Removal of the -1th axis is not invertible")
60
- return Insert(self.idx)
61
-
62
-
63
- Operation = Insert | Remove
64
- """Insert or remove an axis."""
65
-
66
-
67
12
  class ProjectAxis(Transform):
68
13
  """Transform for adding and removing axes.
69
14
 
@@ -72,7 +17,8 @@ class ProjectAxis(Transform):
72
17
 
73
18
  def __init__(
74
19
  self,
75
- operations: Sequence[Operation],
20
+ dropped: set[int] | None = None,
21
+ created: set[int] | None = None,
76
22
  source_ndim: int | None = None,
77
23
  target_ndim: int | None = None,
78
24
  *,
@@ -84,8 +30,10 @@ class ProjectAxis(Transform):
84
30
 
85
31
  Parameters
86
32
  ----------
87
- operations
88
- Sequence of operations to apply.
33
+ dropped
34
+ Set of INPUT dimension indices to drop, if any.
35
+ created
36
+ Set of OUTPUT dimension indices which are new, if any.
89
37
  source_ndim
90
38
  If omitted, can be inferred from `target_ndim`.
91
39
  target_ndim
@@ -99,22 +47,18 @@ class ProjectAxis(Transform):
99
47
  Operations are inconsistent with given dimensionality,
100
48
  or insufficient dimensionality information was given.
101
49
  """
102
- self.operations = []
103
- self._has_inserts = False
50
+ self.dropped = dropped or set()
51
+ self.created = created or set()
104
52
 
105
53
  if source_ndim is not None:
106
- nd = source_ndim
107
- for op in operations:
108
- nd = op.check(nd)
54
+ nd = source_ndim - len(self.dropped) + len(self.created)
109
55
  if target_ndim is None:
110
56
  target_ndim = nd
111
57
  elif target_ndim != nd:
112
58
  raise ValueError("Operations do not match expected target ndim")
113
59
 
114
60
  elif target_ndim is not None:
115
- nd = target_ndim
116
- for op in reversed(operations):
117
- nd = op.invert().check(nd)
61
+ nd = target_ndim - len(self.created) + len(self.dropped)
118
62
  if source_ndim is None:
119
63
  source_ndim = nd
120
64
  elif source_ndim != nd:
@@ -124,20 +68,17 @@ class ProjectAxis(Transform):
124
68
  raise ValueError("At least one of source_ndim or target_ndim must be given")
125
69
 
126
70
  idxs: list[int | None] = list(range(source_ndim))
127
- for op in operations:
128
- if isinstance(op, Insert):
129
- self._has_inserts = True
130
- idxs.insert(op.idx, None)
131
- elif isinstance(op, Remove):
132
- idxs.pop(op.idx)
133
- self.operations.append(op)
71
+ for drop in sorted(self.dropped, reverse=True):
72
+ idxs.pop(drop)
73
+ for create in sorted(self.created):
74
+ idxs.insert(create, None)
134
75
  self._idxs = idxs
135
76
 
136
77
  super().__init__(NDims(source_ndim, target_ndim), spaces=spaces)
137
78
 
138
79
  def apply(self, coords: ArrayT) -> ArrayT:
139
80
  coords = self._validate_coords(coords)
140
- if self._has_inserts:
81
+ if self.created:
141
82
  xp = array_namespace(coords)
142
83
  out = xp.zeros_like(coords, shape=(xp.shape(coords)[0], self.ndims.target))
143
84
  for idx, orig_idx in enumerate(self._idxs):
@@ -149,15 +90,7 @@ class ProjectAxis(Transform):
149
90
  return out
150
91
 
151
92
  def is_identity(self) -> bool:
152
- orig: list[int | None] = list(range(self.ndims.source))
153
- dims = copy(orig)
154
- for op in self.operations:
155
- if isinstance(op, Insert):
156
- dims.insert(op.idx, None)
157
- elif isinstance(op, Remove):
158
- dims.pop(op.idx)
159
-
160
- return dims == orig
93
+ return not self.created and not self.dropped
161
94
 
162
95
  def to_affine(self) -> Affine | None:
163
96
  m = np.eye(self.ndims.source)
@@ -166,7 +99,8 @@ class ProjectAxis(Transform):
166
99
 
167
100
  def invert(self) -> Self | None:
168
101
  return type(self)(
169
- [op.invert() for op in reversed(self.operations)],
102
+ self.created,
103
+ self.dropped,
170
104
  source_ndim=self.ndims.target,
171
105
  target_ndim=self.ndims.source,
172
106
  spaces=self.spaces.invert(),