Source code for nipy.labs.datasets.transforms.transform
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""
The base Transform class.
This class defines the Transform interface and can be subclassed to
define more clever composition logic.
"""
################################################################################
[docs]class CompositionError(Exception):
""" The Exception raised when composing transforms with non matching
respective input and output word spaces.
"""
pass
################################################################################
# Class `Transform`
################################################################################
[docs]class Transform(object):
"""
A transform is a representation of a transformation from one 3D space to
another. It is composed of a coordinate mapping, or its inverse, as well
as the name of the input and output spaces.
The Transform class is the base class for transformations and defines
the transform object API.
"""
# The name of the input space
input_space = ''
# The name of the output space
output_space = ''
# The coordinate mapping from input space to output space
mapping = None
# The inverse coordinate mapping from output space to input space
inverse_mapping = None
[docs] def __init__(self, input_space, output_space, mapping=None,
inverse_mapping=None):
""" Create a new transform object.
Parameters
----------
mapping: callable f(x, y, z)
Callable mapping coordinates from the input space to
the output space. It should take 3 numbers or arrays,
and return 3 numbers or arrays of the same shape.
inverse_mapping: callable f(x, y, z)
Callable mapping coordinates from the output space to
the input space. It should take 3 numbers or arrays,
and return 3 numbers or arrays of the same shape.
input_space: string
Name of the input space
output_space: string
Name of the output space
Notes
------
You need to supply either the mapping or the inverse mapping.
"""
if inverse_mapping is None and mapping is None:
raise ValueError(
'You need to supply either the coordinate mapping or '
'the inverse coordinate mapping'
)
if mapping is not None:
assert callable(mapping), \
'The mapping argument of a Transform must be callable'
if inverse_mapping is not None:
assert callable(inverse_mapping), \
'The inverse_mapping argument of a Transform must be callable'
self.mapping = mapping
self.inverse_mapping = inverse_mapping
self.input_space = input_space
self.output_space = output_space
#-------------------------------------------------------------------------
# Transform Interface
#-------------------------------------------------------------------------
[docs] def composed_with(self, transform):
""" Returns a new transform obtained by composing this transform
with the one provided.
Parameters
-----------
transform: nipy.core.transforms.transform object
The transform to compose with.
"""
self._check_composition(transform)
# We don't want to keep references on the transforms, in the
# closure of the new mapping so we extract their mapping
# outside of the definition of the new mapping
first_mapping = self.mapping
second_mapping = transform.mapping
if first_mapping is not None and second_mapping is not None:
def new_mapping(x, y, z):
""" Coordinate mapping from %s to %s.
""" % (self.input_space, transform.output_space)
return second_mapping(*first_mapping(x, y, z))
else:
new_mapping = None
first_inverse_mapping = self.inverse_mapping
second_inverse_mapping = transform.inverse_mapping
if ( first_inverse_mapping is not None
and second_inverse_mapping is not None):
def new_inverse_mapping(x, y, z):
""" Coordinate mapping from %s to %s.
""" % (transform.output_space, self.input_space)
return first_inverse_mapping(*second_inverse_mapping(x, y, z))
else:
new_inverse_mapping = None
if new_mapping is None and new_inverse_mapping is None:
raise CompositionError(
"""Composing two transforms with no chainable mapping:
%s
and
%s"""
% (self, transform)
)
return Transform(self.input_space,
transform.output_space,
mapping=new_mapping,
inverse_mapping=new_inverse_mapping,
)
[docs] def get_inverse(self):
""" Return the inverse transform.
"""
return self.__class__(
input_space = self.output_space,
output_space = self.input_space,
mapping = self.inverse_mapping,
inverse_mapping = self.mapping,
)
#-------------------------------------------------------------------------
# Private methods
#-------------------------------------------------------------------------
def _check_composition(self, transform):
""" Check that the given transform can be composed with this
one.
"""
if not transform.input_space == self.output_space:
raise CompositionError("The input space of the "
"second transform (%s) does not match the input space "
"of first transform (%s)" %
(transform.input_space, self.output_space)
)
def __repr__(self):
representation = \
'%s(\n input_space=%s,\n output_space=%s,\n mapping=%s,\n inverse_mapping=%s)' % (
self.__class__.__name__,
self.input_space,
self.output_space,
'\n '.join(repr(self.mapping).split('\n')),
'\n '.join(repr(self.inverse_mapping).split('\n')),
)
return representation
def __copy__(self):
""" Copy the transform
"""
return self.__class__(input_space=self.input_space,
output_space=self.output_space,
mapping=self.mapping,
inverse_mapping=self.inverse_mapping)
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.input_space == other.input_space
and self.output_space == other.output_space
and self.mapping == other.mapping
and self.inverse_mapping == other.inverse_mapping
)