transformnd 0.1.0__py3-none-any.whl
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/__init__.py +24 -0
- transformnd/adapters/__init__.py +34 -0
- transformnd/adapters/base.py +167 -0
- transformnd/adapters/bounding_box.py +16 -0
- transformnd/adapters/pandas.py +46 -0
- transformnd/adapters/polars.py +44 -0
- transformnd/adapters/shapely.py +124 -0
- transformnd/base.py +441 -0
- transformnd/extents/__init__.py +0 -0
- transformnd/extents/base.py +10 -0
- transformnd/extents/bounding_box.py +36 -0
- transformnd/graph.py +192 -0
- transformnd/py.typed +0 -0
- transformnd/transforms/__init__.py +19 -0
- transformnd/transforms/affine.py +512 -0
- transformnd/transforms/bijection.py +68 -0
- transformnd/transforms/by_dimension.py +133 -0
- transformnd/transforms/map_axis.py +67 -0
- transformnd/transforms/moving_least_squares.py +66 -0
- transformnd/transforms/reflection.py +207 -0
- transformnd/transforms/simple.py +172 -0
- transformnd/transforms/thinplate.py +83 -0
- transformnd/util.py +251 -0
- transformnd-0.1.0.dist-info/METADATA +133 -0
- transformnd-0.1.0.dist-info/RECORD +26 -0
- transformnd-0.1.0.dist-info/WHEEL +4 -0
transformnd/util.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Utilities used elsewhere in the package."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
# required for TypeVar(default=) argument
|
|
8
|
+
from typing_extensions import TypeVar
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from array_api_compat import array_namespace
|
|
12
|
+
|
|
13
|
+
UNSPECIFIED_SPACE_NAME = "???"
|
|
14
|
+
|
|
15
|
+
ArrayT = TypeVar("ArrayT", default=np.ndarray)
|
|
16
|
+
|
|
17
|
+
TransformSignature = Callable[[ArrayT], ArrayT]
|
|
18
|
+
"""Type annotation of a function which can be used as a transform."""
|
|
19
|
+
|
|
20
|
+
SpaceRef = Hashable
|
|
21
|
+
"""Type annotation of things which can be used to refer to spaces"""
|
|
22
|
+
|
|
23
|
+
SpaceTuple = tuple[SpaceRef | None, SpaceRef | None]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def none_eq(a: Any | None, b: Any | None) -> bool:
|
|
27
|
+
"""Check whether either is None or both are equal.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
a : Optional[Any]
|
|
32
|
+
b : Optional[Any]
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
bool
|
|
37
|
+
"""
|
|
38
|
+
return a == b or a is None or b is None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
NO_DEFAULT = object()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def chain_or(*args: Any | None, default=NO_DEFAULT) -> Any:
|
|
45
|
+
"""Return the first of *args which is not None.
|
|
46
|
+
|
|
47
|
+
Can either error or return a default if there are no non-None args.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
default : any, optional
|
|
52
|
+
By default, raises a ValueError if *args are exhausted.
|
|
53
|
+
If given, returns the given value instead.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
Any
|
|
58
|
+
One of the given args, or the default.
|
|
59
|
+
|
|
60
|
+
Raises
|
|
61
|
+
------
|
|
62
|
+
ValueError
|
|
63
|
+
If `default` is not given and there are no non-None args.
|
|
64
|
+
"""
|
|
65
|
+
for arg in args:
|
|
66
|
+
if arg is not None:
|
|
67
|
+
return arg
|
|
68
|
+
if default is NO_DEFAULT:
|
|
69
|
+
raise ValueError("No non-None arguments")
|
|
70
|
+
return default
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def same_or_none(*args: Any, default=NO_DEFAULT) -> Any:
|
|
74
|
+
"""Check args are the same or None.
|
|
75
|
+
|
|
76
|
+
If so, return the non-None value.
|
|
77
|
+
Otherwise, raise a ValueError.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
default : Any, optional
|
|
82
|
+
If given, return this instead of an error
|
|
83
|
+
if all *args are None.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
Any
|
|
88
|
+
The non-None arg value.
|
|
89
|
+
|
|
90
|
+
Raises
|
|
91
|
+
------
|
|
92
|
+
ValueError
|
|
93
|
+
Arguments are not None, or the same.
|
|
94
|
+
ValueError
|
|
95
|
+
No non-None arguments found and no default given.
|
|
96
|
+
"""
|
|
97
|
+
prev = None
|
|
98
|
+
|
|
99
|
+
for arg in args:
|
|
100
|
+
if arg is None:
|
|
101
|
+
continue
|
|
102
|
+
if prev is not None and prev != arg:
|
|
103
|
+
raise ValueError("Arguments are not None or the same")
|
|
104
|
+
prev = arg
|
|
105
|
+
|
|
106
|
+
if prev is None:
|
|
107
|
+
if default is NO_DEFAULT:
|
|
108
|
+
raise ValueError("No non-None arguments found")
|
|
109
|
+
return default
|
|
110
|
+
|
|
111
|
+
return prev
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def window[T](iterable: Iterable[T], length: int) -> Iterator[tuple[T, ...]]:
|
|
115
|
+
"""Sliding window over iterable.
|
|
116
|
+
|
|
117
|
+
e.g. `(it[0], it[1]), (it[1], it[2]), (it[2], it[3]), ...`
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
iterable : Iterable
|
|
122
|
+
length : int
|
|
123
|
+
Length of windows to return.
|
|
124
|
+
|
|
125
|
+
Yields
|
|
126
|
+
-------
|
|
127
|
+
Tuple[Any, ...]
|
|
128
|
+
"""
|
|
129
|
+
it = iter(iterable)
|
|
130
|
+
q: deque[Any] = deque(maxlen=length)
|
|
131
|
+
for _ in range(length):
|
|
132
|
+
try:
|
|
133
|
+
item = next(it)
|
|
134
|
+
except StopIteration:
|
|
135
|
+
return
|
|
136
|
+
q.append(item)
|
|
137
|
+
yield tuple(q)
|
|
138
|
+
for item in it:
|
|
139
|
+
q.append(item)
|
|
140
|
+
yield tuple(q)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def check_ndim(given_ndim: int, supported_ndim: set[int] | None) -> None:
|
|
144
|
+
"""Raise a ValueError if dimensionality is unsupported.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
given_ndim : int
|
|
149
|
+
The dimensionality to check.
|
|
150
|
+
supported_ndim : Optional[Set[int]]
|
|
151
|
+
Which dimensions are supported.
|
|
152
|
+
If None, the check passes.
|
|
153
|
+
|
|
154
|
+
Raises
|
|
155
|
+
------
|
|
156
|
+
ValueError
|
|
157
|
+
If supported dimensions are defined and given_ndim is not in them.
|
|
158
|
+
"""
|
|
159
|
+
if supported_ndim is not None and given_ndim not in supported_ndim:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Transform supported for {format_dims(supported_ndim)}, not {given_ndim}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def format_dims(supported: set[int] | None) -> str:
|
|
166
|
+
"""Format supported dimensions for e.g. error messages.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
supported : Iterable[int]
|
|
171
|
+
The supported dimensions.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
str
|
|
176
|
+
e.g. "2D/3D/4D"
|
|
177
|
+
"""
|
|
178
|
+
if supported is None:
|
|
179
|
+
return "ND"
|
|
180
|
+
if not len(supported):
|
|
181
|
+
return "nullD"
|
|
182
|
+
return "/".join(f"{d}D" for d in sorted(supported))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def space_str(space: SpaceRef | None) -> str:
|
|
186
|
+
if space is None:
|
|
187
|
+
return UNSPECIFIED_SPACE_NAME
|
|
188
|
+
else:
|
|
189
|
+
return str(space)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def is_square(arr: ArrayT) -> bool:
|
|
193
|
+
"""Check whether an array is 2D and has the same number of rows as columns"""
|
|
194
|
+
xp = array_namespace(arr)
|
|
195
|
+
ndim, shape = xp.ndim(arr), xp.shape(arr)
|
|
196
|
+
return ndim == 2 and shape[0] == shape[1]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def dim_intersection(
|
|
200
|
+
dims1: set[int] | None, dims2: set[int] | None, error_on_empty: bool = False
|
|
201
|
+
) -> set[int] | None:
|
|
202
|
+
"""Find the intersection between two sets of constraints.
|
|
203
|
+
|
|
204
|
+
None means no constraints.
|
|
205
|
+
If `error_on_empty` is truthy and there is no intersection, raise an error.
|
|
206
|
+
"""
|
|
207
|
+
if dims1 is None:
|
|
208
|
+
out = dims2
|
|
209
|
+
elif dims2 is None:
|
|
210
|
+
out = dims1
|
|
211
|
+
else:
|
|
212
|
+
out = dims1.intersection(dims2)
|
|
213
|
+
if error_on_empty and out is not None and len(out) == 0:
|
|
214
|
+
raise ValueError(f"incompatible dimensions: {dims1} ∩ {dims2}")
|
|
215
|
+
return out
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def invert_spaces(spaces: SpaceTuple) -> SpaceTuple:
|
|
219
|
+
"""Invert the given (source, target) space tuple."""
|
|
220
|
+
return (spaces[1], spaces[0])
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def are_coords(coords: ArrayT, ndim: set[int] | None = None):
|
|
224
|
+
xp = array_namespace(coords)
|
|
225
|
+
if xp.ndim(coords) != 2:
|
|
226
|
+
raise ValueError("Coords must be a 2D array")
|
|
227
|
+
check_ndim(xp.shape(coords)[1], ndim)
|
|
228
|
+
return coords
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def to_single_ndim(ndim: None | int = None, ndims: None | set[int] = None) -> int:
|
|
232
|
+
"""Select a single ndim from the given options.
|
|
233
|
+
|
|
234
|
+
Error if a single dimension cannot be selected;
|
|
235
|
+
i.e. both are None or there is a conflict.
|
|
236
|
+
|
|
237
|
+
Useful when converting a transformation with multi-dimensionality support
|
|
238
|
+
(e.g. a scalar translation) into one with single-dimensionality support
|
|
239
|
+
(e.g. an affine).
|
|
240
|
+
"""
|
|
241
|
+
if ndim is None:
|
|
242
|
+
if ndims is None:
|
|
243
|
+
raise ValueError("no ndims specified")
|
|
244
|
+
if len(ndims) != 1:
|
|
245
|
+
raise ValueError(f"needs exactly one ndim, got {ndims}")
|
|
246
|
+
return list(ndims).pop()
|
|
247
|
+
|
|
248
|
+
if ndims is None or ndim in ndims:
|
|
249
|
+
return ndim
|
|
250
|
+
|
|
251
|
+
raise ValueError(f"dimensionality conflict: {ndim} not in {ndims}")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: transformnd
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ND coordinate transformations
|
|
5
|
+
Author: Chris Barnes
|
|
6
|
+
Author-email: Chris Barnes <chris.barnes@gerbi-gmb.de>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: numpy>=2
|
|
9
|
+
Requires-Dist: networkx>=3
|
|
10
|
+
Requires-Dist: array-api-compat>=1.14
|
|
11
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
12
|
+
Requires-Dist: molesq>=0.4.0 ; extra == 'movingleastsquares'
|
|
13
|
+
Requires-Dist: pandas>=3.0.2 ; extra == 'pandas'
|
|
14
|
+
Requires-Dist: polars>=1.40.1 ; extra == 'polars'
|
|
15
|
+
Requires-Dist: shapely>=2.1.2 ; extra == 'shapely'
|
|
16
|
+
Requires-Dist: morphops>=0.1.13 ; extra == 'thinplatesplines'
|
|
17
|
+
Requires-Python: >=3.12, <4.0
|
|
18
|
+
Provides-Extra: movingleastsquares
|
|
19
|
+
Provides-Extra: pandas
|
|
20
|
+
Provides-Extra: polars
|
|
21
|
+
Provides-Extra: shapely
|
|
22
|
+
Provides-Extra: thinplatesplines
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# transformnd
|
|
26
|
+
|
|
27
|
+
[](https://github.com/psf/black)
|
|
28
|
+
[](https://github.com/clbarnes/transformnd/blob/main/LICENSE)
|
|
29
|
+
[](https://github.com/clbarnes/transformnd/actions/workflows/ci.yaml)
|
|
30
|
+
[](https://clbarnes.github.io/transformnd/)
|
|
31
|
+
|
|
32
|
+
A library providing an API for coordinate transformations,
|
|
33
|
+
as well as some common transforms.
|
|
34
|
+
The goal is to allow downstream applications which require such transformations
|
|
35
|
+
(e.g. image registration) to be generic over anything inheriting from `transformnd.Transform`.
|
|
36
|
+
|
|
37
|
+
The base classes and utilities are very lightweight with few dependencies, for use as an API; additional transforms and features use extras.
|
|
38
|
+
|
|
39
|
+
Heavily inspired by/ cribbed directly from
|
|
40
|
+
[Philipp Schlegel's work in navis](https://github.com/schlegelp/navis/tree/master/navis/transforms);
|
|
41
|
+
co-developed with [xform](https://github.com/schlegelp/xform/) as a red team prototype.
|
|
42
|
+
|
|
43
|
+
`N` coordinates in `D` dimensions are given as a numpy array of shape `(N, D)`.
|
|
44
|
+
|
|
45
|
+
`Transform` subclasses which are restricted to certain dimensionalities
|
|
46
|
+
can specify this in their `ndim` class variable.
|
|
47
|
+
Instances of `Transform` subclasses can further restrict their `ndim`.
|
|
48
|
+
Use `self._validate_coords(coords)` in `__call__` to ensure the coordinates
|
|
49
|
+
are of valid type and dimensions.
|
|
50
|
+
|
|
51
|
+
Additionally, `transformnd` provides an interface for transforming types other than NxD numpy arrays,
|
|
52
|
+
and implements these adapters for a few common types.
|
|
53
|
+
|
|
54
|
+
See the [tutorial here](https://github.com/clbarnes/transformnd/blob/main/examples/tutorial.py).
|
|
55
|
+
It is a [marimo](https://marimo.io) notebook.
|
|
56
|
+
Open it with `uv run --group tutorial marimo edit examples/tutorial.py`.
|
|
57
|
+
|
|
58
|
+
## Implemented transforms
|
|
59
|
+
|
|
60
|
+
- Identity (`transformnd.transforms.Identity`)
|
|
61
|
+
- Translation (`transformnd.transforms.Translate`)
|
|
62
|
+
- Scale (`transformnd.transforms.Scale`)
|
|
63
|
+
- Reflection (`transformnd.transforms.Reflect`)
|
|
64
|
+
- Affine (`transformnd.transforms.Affine`)
|
|
65
|
+
- Can be composed efficiently with `@` operator; the right hand operand is effectively applied first
|
|
66
|
+
- MapAxis (`transformnd.transforms.MapAxis`): permute coordinate axes
|
|
67
|
+
- ByDimension (`transformnd.transforms.ByDimension`): apply transformations to subsets of coordinate axes
|
|
68
|
+
- Moving Least Squares, affine (`transformnd.transforms.moving_least_squares.MovingLeastSquares`)
|
|
69
|
+
- uses `movingleastsquares` extra
|
|
70
|
+
- Thin Plate Splines (`transformnd.transforms.thinplate.ThinPlateSplines`)
|
|
71
|
+
- uses `thinplatesplines` extra
|
|
72
|
+
|
|
73
|
+
Arbitrary transforms can be composed into a `TransformSequence` with `transform1 | transform2`.
|
|
74
|
+
A graph of transforms between defined spaces can be traversed using the `TransformGraph`.
|
|
75
|
+
|
|
76
|
+
## Implemented adapters
|
|
77
|
+
|
|
78
|
+
- Numpy arrays of shape `(..., D, ...)` (`transformnd.adapters.ReshapeAdapter`)
|
|
79
|
+
- `meshio.Mesh` (`transformnd.adapters.meshio.MeshAdapter`)
|
|
80
|
+
- `pandas.DataFrame` (`transformnd.adapters.pandas.PandasAdapter`)
|
|
81
|
+
- Takes a subset of columns as a coordinate array
|
|
82
|
+
- `polars.DataFrame` (`transformnd.adapters.polars.PolarsAdapter`)
|
|
83
|
+
- Similar to the pandas adapter
|
|
84
|
+
- Currently, only scalar columns are supported (e.g. not a single struct column with fields `x`, `y`, `z`)
|
|
85
|
+
- Geometries from `shapely` (`transformnd.adapters.shapely.GeometryAdapter`)
|
|
86
|
+
- Objects composed of transformable attributes (`transformnd.adapters.AttrAdapter`).
|
|
87
|
+
|
|
88
|
+
## Additional transforms and adapters
|
|
89
|
+
|
|
90
|
+
Contributions of additional transforms and adapters are welcome!
|
|
91
|
+
Even if they're only thin wrappers around an external library,
|
|
92
|
+
the downstream ecosystem benefits from a consistent API.
|
|
93
|
+
|
|
94
|
+
Such external transformation libraries should be specified as "extras",
|
|
95
|
+
and be contained in a submodule so that they are not immediately imported
|
|
96
|
+
with `transformnd`.
|
|
97
|
+
Dependencies for new adapters do not need to be included in `transformnd`'s dependencies,
|
|
98
|
+
but should be specified in the `requirements.txt` for tests.
|
|
99
|
+
|
|
100
|
+
Alternatively, consider adopting `transformnd`'s base classes in your own library,
|
|
101
|
+
and have your transformation instantly compatible for downstream users.
|
|
102
|
+
|
|
103
|
+
Methods which MUST be implemented:
|
|
104
|
+
|
|
105
|
+
- `__init__`: should validate parameters and set `self.ndim` if the parameters constrain the dimensionality
|
|
106
|
+
- `apply`: should call `_validate_coords` method early to check that the given coordinates are the correct shape
|
|
107
|
+
|
|
108
|
+
Methods which SHOULD be implemented if applicable:
|
|
109
|
+
|
|
110
|
+
- `to_device`: if any of the transformation's parameters need to be placed on a specific device (e.g. affine matrices on the GPU)
|
|
111
|
+
- `is_identity`: if you can cheaply check whether your transformation is an identity transformation. The base class implementation returns `False`.
|
|
112
|
+
- `into_affine`: if your transformation can be represented as an affine matrix. The base class implementation returns `None`.
|
|
113
|
+
- `invert`: if your transformation can be inverted (default None if not)
|
|
114
|
+
- This automatically implements `__invert__` (the `~my_transform` operator), which raises NotImplemented if `invert` would return `None`.
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
- Use [`uv`](https://docs.astral.sh/uv/) for environment and dependency management.
|
|
119
|
+
- `uv sync` to set up the environment.
|
|
120
|
+
- Use [`prek`](https://prek.j178.dev/) for running pre-commit hooks.
|
|
121
|
+
- `prek install-hooks && prek run --all-files` to get started.
|
|
122
|
+
- Use [`just`](https://github.com/casey/just) for common development tasks (format, lint, test, generate docs, run benchmarks).
|
|
123
|
+
- `just` to list commands.
|
|
124
|
+
- Docs are generated with `pdoc` (use `just doc`) and hosted on ReadTheDocs
|
|
125
|
+
|
|
126
|
+
## Thanks
|
|
127
|
+
|
|
128
|
+
Thanks to contributors
|
|
129
|
+
|
|
130
|
+
- [Francesca Drummer](https://github.com/FrancescaDr)
|
|
131
|
+
- [Lorenzo Cerrone](https://github.com/lorenzocerrone)
|
|
132
|
+
- [Maks Hess](https://github.com/MaksHess)
|
|
133
|
+
- [Silvia Maria Macrì](https://github.com/SilviaMariaMacri)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
transformnd/__init__.py,sha256=gF4j7VK_dTmzFsPo-SFn3tm7DJb4yHvyHQQA-RL9DcE,531
|
|
2
|
+
transformnd/adapters/__init__.py,sha256=qfMnCnLEs61i0NaiLwR4qfrXzL_4SjdaU7ACwSeuImg,932
|
|
3
|
+
transformnd/adapters/base.py,sha256=jD4Zfc75LCxqAE8b28NCYSnhLFmRmiPlIgbguSsRPl0,5125
|
|
4
|
+
transformnd/adapters/bounding_box.py,sha256=l45aOJj8HYFPFU7TIaoytLGvpyWxff7BqJ9WK_MnB2Y,593
|
|
5
|
+
transformnd/adapters/pandas.py,sha256=b5RMv8DiUy_hxpVoZxzjLtqrt2wNOHE1K-C-8sYCOag,1248
|
|
6
|
+
transformnd/adapters/polars.py,sha256=pNPWJnC0CRa5JWt1jrLoEJ_6b_xq47AMjEt_7L2io1c,1206
|
|
7
|
+
transformnd/adapters/shapely.py,sha256=TPh3vvPH4lfGY8QTo1ICO3WECX01wcJCAFE119ONBeY,4329
|
|
8
|
+
transformnd/base.py,sha256=sQoXBzegpKk_x8wi41wvVaiHso6qOchD2mbwm_9rpys,12498
|
|
9
|
+
transformnd/extents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
transformnd/extents/base.py,sha256=Wuh6YwaHnV-nAcFAQYG4CuqlJxTBIp7jG3IkshwB9S8,254
|
|
11
|
+
transformnd/extents/bounding_box.py,sha256=FEObnmmFdCA2SMkpTeY5C7_xfyCQZfJzBglRC1X9s7o,1273
|
|
12
|
+
transformnd/graph.py,sha256=EMaM5rn4wtoA7GuWU5XKbkNtlpucf8eiePYKVVLTtYk,5900
|
|
13
|
+
transformnd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
transformnd/transforms/__init__.py,sha256=0w0GZvSQQHBPGoQsLwhkM-vtXvl19z0WJC9QW6KzZYI,398
|
|
15
|
+
transformnd/transforms/affine.py,sha256=5xEjZVQYbQ2tOqAYmGUF04KWKQTMhPLvn9WuY-OID24,15828
|
|
16
|
+
transformnd/transforms/bijection.py,sha256=WU96sOMkV2GhrVQPoqqvwGx7efumw5FdlimlO2MgAVg,1888
|
|
17
|
+
transformnd/transforms/by_dimension.py,sha256=p5Taiv9ng1A_kzg1OWvbQYv3qkchV9876ndN82aNsRE,4742
|
|
18
|
+
transformnd/transforms/map_axis.py,sha256=oFMC3MZ-lx1oElG3PvwiTeL53UatDmUkAXFmsLa5KRY,2033
|
|
19
|
+
transformnd/transforms/moving_least_squares.py,sha256=CcrtIxMPWu_cIupCWIy0Ia-yeBKA7HSKIO7kqUx7bgg,2121
|
|
20
|
+
transformnd/transforms/reflection.py,sha256=Xt-TfGroTC8VnLhhlVOFfiFZczVDxyc12uwd4vQLAlg,5792
|
|
21
|
+
transformnd/transforms/simple.py,sha256=mgBhVH6vwhOY4-9u7b1y07wGE4VOpElQudJTubuwkTs,5048
|
|
22
|
+
transformnd/transforms/thinplate.py,sha256=Q99q0k_EO9hzkGUOa4mMKO5UqK7n5LmUFDe1XFUws-M,2485
|
|
23
|
+
transformnd/util.py,sha256=-gTuhpWkmJK8sNc6VT50rBU-lwoEL3ncyJMYl8Qfy_c,6451
|
|
24
|
+
transformnd-0.1.0.dist-info/WHEEL,sha256=i9aSRDivn5iP9LaR1BLQX2GNAuriQWPsFwbbWygTX2k,81
|
|
25
|
+
transformnd-0.1.0.dist-info/METADATA,sha256=ufGvhllwl2G0Q8bsGfGlZv75kit-rv0K2hLxlnV5ho8,6775
|
|
26
|
+
transformnd-0.1.0.dist-info/RECORD,,
|