xform
is a library to transform spatial data from one space to another and
provides a common interface to combine different types of transforms.
It was originally written for navis to transform neurons from one brain template space to another and then split off into a separate general-purpose package.
- various supported transforms (see below)
- chaining of transforms
- a template registry that tracks available transforms and plots paths to get from a given source to the desired target template space
- CMTK warp transforms
- Elastix warp transforms
- H5 deformation fields
- Landmark-based thin-plate spline transforms (powered by morphops)
- Landmark-based least-moving square transforms (powered by molesq)
- Affine transformations
$ pip3 install xform
Additional dependencies:
To use CMTK transforms, you need to have CMTK
installed and its binaries (specifically streamxform
) in a path where xform
can find them (e.g. /usr/local/bin
).
At the most basic level you can use individual transform from xform.transforms
:
AffineTransform
for affine transforms using a affine matrixCMTKtransform
for CMTK transformsElastixTransform
for Elastix transformsTPStransform
orMovingLeastSquaresTransform
for landmark-based transformsH5transform
for deformation-field transforms using Hdf5 files (specs)
A quick example that uses an affine transform to scale coordinates by a factor of 2:
>>> import xform
>>> import numpy as np
>>> # Generate the affine matrix
>>> m = np.diag([2, 2, 2, 2])
>>> # Create the transform
>>> tr = xform.AffineTransform(m)
>>> # Some 3D points to transform
>>> points = np.array([[1,1,1], [2,2,2], [3,3,3]])
>>> # Apply
>>> xf = tr.xform(points)
>>> xf
array([[2., 2., 2.],
[4., 4., 4.],
[6., 6., 6.]])
>>> # Transforms are invertible!
>>> (-tr).xform(xf)
array([[1., 1., 1.],
[2., 2., 2.],
[3., 3., 3.]])
If you find yourself in a situation where you need to chain some transforms,
you can use xform.transforms.TransformSequence
to combine transforms.
For example, let's say we have a CMTK transform that requires spatial data to be in microns but our data is in nanometers:
>>> from xform import CMTKtransform, AffineTransform, TransformSequence
>>> import numpy as np
>>> # Initialize CMTK transform
>>> cmtk = CMTKtransform('~/transform/target_source.list')
>>> # Create an affine transform to go from microns to nanometers
>>> aff = AffineTransform(np.diag([1e3, 1e3, 1e3, 1e3]))
>>> # Create a transform sequence
>>> tr = TransformSequence([-aff, cmtk])
>>> # Apply transform
>>> points = np.array([[1,1,1], [2,2,2], [3,3,3]])
>>> xf = tr.xform(points)
When working with many interconnected transforms (e.g. A->B, B->C, B->D, etc.),
you can register the individual transforms and let xform
plot the shortest
path to get from a given source to a given target for you:
>>> import xform
>>> from xform import CMTKtransform, AffineTransform, TransformRegistry
>>> import numpy as np
>>> # Create a transform registry
>>> registry = TransformRegistry()
>>> # Generate a couple transforms
>>> # Note that we now provide source and target labels
>>> tr1 = AffineTransform(np.diag([1e3, 1e3, 1e3, 1e3]),
... source_space='A', target_space='B')
>>> cmtk = CMTKtransform('~/transform/C_B.list',
... source_space='B', target_space='C')
>>> # Register the transforms
>>> registry.register_transform([tr1, cmtk])
>>> # Now you ask the registry for the required transforms to move between spaces
>>> path, trans_seq = registry.shortest_bridging_seq(source='A', target='C')
>>> path
array(['A', 'B', 'C'], dtype='<U1')
>>> trans_seq
TransformSequence with 2 transform(s)
TODO