# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
""" Define the Image class and functions to work with Image instances
* fromarray : create an Image instance from an ndarray (deprecated in favor of
using the Image constructor)
* subsample : slice an Image instance (deprecated in favor of image slicing)
* rollimg : roll an image axis to given location
* synchronized_order : match coordinate systems between images
* iter_axis : make iterator to iterate over an image axis
* is_image : test for an object obeying the Image API
"""
from __future__ import print_function
from __future__ import absolute_import
import warnings
from copy import copy
from itertools import chain
import numpy as np
from nibabel.onetime import setattr_on_read
# These imports are used in the fromarray and subsample functions only, not in
# Image
from ..reference.coordinate_map import (AffineTransform, CoordinateSystem,
input_axis_index)
from ..reference.array_coords import ArrayCoordMap
# Legacy repr printing from numpy.
from nipy.testing import legacy_printing as setup_module # noqa
[docs]class Image(object):
""" The `Image` class provides the core object type used in nipy.
An `Image` represents a volumetric brain image and provides means
for manipulating the image data. Most functions in the image module
operate on `Image` objects.
Notes
-----
Images can be created through the module functions. See nipy.io for
image IO such as ``load`` and ``save``
Examples
--------
Load an image from disk
>>> from nipy.testing import anatfile
>>> from nipy.io.api import load_image
>>> img = load_image(anatfile)
Make an image from an array. We need to make a meaningful coordinate map
for the image.
>>> arr = np.zeros((21,64,64), dtype=np.int16)
>>> cmap = AffineTransform('kji', 'zxy', np.eye(4))
>>> img = Image(arr, cmap)
"""
_doc = {}
# Dictionary to store docs for attributes that are properties. We
# want these docs to conform with our documentation standard, but
# they need to be passed into the property function. Defining
# them separately allows us to do this without a lot of clutter
# in the property line.
###################################################################
#
# Attributes
#
###################################################################
metadata = {}
_doc['metadata'] = "Dictionary containing additional information."
coordmap = AffineTransform(CoordinateSystem('ijk'),
CoordinateSystem('xyz'),
np.diag([3,5,7,1]))
_doc['coordmap'] = "Affine transform mapping from axes coordinates to reference coordinates."
[docs] @setattr_on_read
def shape(self):
return self._data.shape
_doc['shape'] = "Shape of data array."
[docs] @setattr_on_read
def ndim(self):
return len(self._data.shape)
_doc['ndim'] = "Number of data dimensions."
[docs] @setattr_on_read
def reference(self):
return self.coordmap.function_range
_doc['reference'] = "Reference coordinate system."
[docs] @setattr_on_read
def axes(self):
return self.coordmap.function_domain
_doc['axes'] = "Axes of image."
[docs] @setattr_on_read
def affine(self):
if hasattr(self.coordmap, "affine"):
return self.coordmap.affine
raise AttributeError('Nonlinear transform does not have an affine.')
_doc['affine'] = "Affine transformation if one exists."
###################################################################
#
# Properties
#
###################################################################
def _getheader(self):
# data loaded from a file may have a header
warnings.warn("Please don't use ``img.header``; use"
"``img.metadata['header'] instead",
DeprecationWarning,
stacklevel=2)
hdr = self.metadata.get('header')
if hdr is None:
raise AttributeError('Image created from arrays '
'may not have headers.')
return hdr
def _setheader(self, header):
warnings.warn("Please don't use ``img.header``; use"
"``img.metadata['header'] instead",
DeprecationWarning,
stacklevel=2)
self.metadata['header'] = header
_doc['header'] = \
"""The file header structure for this image, if available. This interface
will soon go away - you should use ``img.metadata['header'] instead.
"""
header = property(_getheader, _setheader, doc=_doc['header'])
###################################################################
#
# Constructor
#
###################################################################
[docs] def __init__(self, data, coordmap, metadata=None):
"""Create an `Image` object from array and `CoordinateMap` object.
Images are often created through the ``load_image`` function in the nipy
base namespace.
Parameters
----------
data : array-like
object that as attribute ``shape`` and returns an array from
``np.asarray(data)``
coordmap : `AffineTransform` object
coordmap mapping the domain (input) voxel axes of the image to the
range (reference, output) axes - usually mm in real world space
metadata : dict, optional
Freeform metadata for image. Most common contents is ``header``
from nifti etc loaded images.
See Also
--------
load_image : load ``Image`` from a file
save_image : save ``Image`` to a file
"""
if metadata is None:
metadata = {}
else: # Shallow copy
metadata = copy(metadata)
ndim = len(data.shape)
if not isinstance(coordmap, AffineTransform):
raise ValueError('coordmap must be an AffineTransform')
# self._data is an array-like object. It must have a shape attribute
# (see above) and return an array from np.array(data)
self._data = data
self.coordmap = coordmap
if coordmap.function_domain.ndim != ndim:
raise ValueError('the number of axes implied by the coordmap do '
'not match the number of axes of the data')
self.metadata = metadata
###################################################################
#
# Methods
#
###################################################################
[docs] def reordered_reference(self, order=None):
""" Return new Image with reordered output coordinates
New Image coordmap has reordered output coordinates. This does
not transpose the data.
Parameters
----------
order : None, sequence, optional
sequence of int (giving indices) or str (giving names) - expressing
new order of coordmap output coordinates. None (the default)
results in reversed ordering.
Returns
-------
r_img : object
Image of same class as `self`, with reordered output coordinates.
Examples
--------
>>> cmap = AffineTransform.from_start_step(
... 'ijk', 'xyz', [1, 2, 3], [4, 5, 6], 'domain', 'range')
>>> im = Image(np.empty((30,40,50)), cmap)
>>> im_reordered = im.reordered_reference([2,0,1])
>>> im_reordered.shape
(30, 40, 50)
>>> im_reordered.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('i', 'j', 'k'), name='domain', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('z', 'x', 'y'), name='range', coord_dtype=float64),
affine=array([[ 0., 0., 6., 3.],
[ 4., 0., 0., 1.],
[ 0., 5., 0., 2.],
[ 0., 0., 0., 1.]])
)
"""
if order is None:
order = list(range(self.ndim))[::-1]
elif type(order[0]) == type(''):
order = [self.reference.index(s) for s in order]
new_cmap = self.coordmap.reordered_range(order)
return self.__class__.from_image(self, coordmap=new_cmap)
[docs] def reordered_axes(self, order=None):
""" Return a new Image with reordered input coordinates.
This transposes the data as well.
Parameters
----------
order : None, sequence, optional
Sequence of int (giving indices) or str (giving names) - expressing
new order of coordmap output coordinates. None (the default)
results in reversed ordering.
Returns
-------
r_img : object
Image of same class as `self`, with reordered output coordinates.
Examples
--------
>>> cmap = AffineTransform.from_start_step(
... 'ijk', 'xyz', [1, 2, 3], [4, 5, 6], 'domain', 'range')
>>> cmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('i', 'j', 'k'), name='domain', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z'), name='range', coord_dtype=float64),
affine=array([[ 4., 0., 0., 1.],
[ 0., 5., 0., 2.],
[ 0., 0., 6., 3.],
[ 0., 0., 0., 1.]])
)
>>> im = Image(np.empty((30,40,50)), cmap)
>>> im_reordered = im.reordered_axes([2,0,1])
>>> im_reordered.shape
(50, 30, 40)
>>> im_reordered.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('k', 'i', 'j'), name='domain', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z'), name='range', coord_dtype=float64),
affine=array([[ 0., 4., 0., 1.],
[ 0., 0., 5., 2.],
[ 6., 0., 0., 3.],
[ 0., 0., 0., 1.]])
)
"""
if order is None:
order = list(range(self.ndim))[::-1]
elif type(order[0]) == type(''):
order = [self.axes.index(s) for s in order]
new_cmap = self.coordmap.reordered_domain(order)
# Only transpose if we have to so as to avoid calling
# self.get_data
if order != list(range(self.ndim)):
new_data = np.transpose(self.get_data(), order)
else:
new_data = self._data
return self.__class__.from_image(self,
data=new_data,
coordmap=new_cmap)
[docs] def renamed_axes(self, **names_dict):
r""" Return a new image with input (domain) axes renamed
Axes renamed according to the input dictionary.
Parameters
----------
\*\*names_dict : dict
with keys being old names, and values being new names
Returns
-------
newimg : Image
An Image with the same data, having its axes renamed.
Examples
--------
>>> data = np.random.standard_normal((11,9,4))
>>> im = Image(data, AffineTransform.from_params('ijk', 'xyz', np.identity(4), 'domain', 'range'))
>>> im_renamed = im.renamed_axes(i='slice')
>>> print(im_renamed.axes)
CoordinateSystem(coord_names=('slice', 'j', 'k'), name='domain', coord_dtype=float64)
"""
new_cmap = self.coordmap.renamed_domain(names_dict)
return self.__class__.from_image(self, coordmap=new_cmap)
[docs] def renamed_reference(self, **names_dict):
r""" Return new image with renamed output (range) coordinates
Coordinates renamed according to the dictionary
Parameters
----------
\*\*names_dict : dict
with keys being old names, and values being new names
Returns
-------
newimg : Image
An Image with the same data, having its output coordinates renamed.
Examples
--------
>>> data = np.random.standard_normal((11,9,4))
>>> im = Image(data, AffineTransform.from_params('ijk', 'xyz', np.identity(4), 'domain', 'range'))
>>> im_renamed_reference = im.renamed_reference(x='newx', y='newy')
>>> print(im_renamed_reference.reference)
CoordinateSystem(coord_names=('newx', 'newy', 'z'), name='range', coord_dtype=float64)
"""
new_cmap = self.coordmap.renamed_range(names_dict)
return self.__class__.from_image(self, coordmap=new_cmap)
def __setitem__(self, index, value):
"""Setting values of an image, set values in the data array."""
warnings.warn("Please don't use ``img[x] = y``; use "
"``img.get_data()[x] = y`` instead",
DeprecationWarning,
stacklevel=2)
self._data[index] = value
def __array__(self):
"""Return data as a numpy array."""
warnings.warn('Please use get_data instead - will be deprecated',
DeprecationWarning,
stacklevel=2)
return self.get_data()
[docs] def get_data(self):
"""Return data as a numpy array."""
return np.asanyarray(self._data)
def __getitem__(self, slice_object):
""" Slicing an image returns an Image.
Parameters
----------
slice_object: int, slice or sequence of slice
An object representing a numpy 'slice'.
Returns
-------
img_subsampled: Image
An Image with data self.get_data()[slice_object] and an
appropriately corrected CoordinateMap.
Examples
--------
>>> from nipy.io.api import load_image
>>> from nipy.testing import funcfile
>>> im = load_image(funcfile)
>>> frame3 = im[:,:,:,3]
>>> np.allclose(frame3.get_data(), im.get_data()[:,:,:,3])
True
"""
data = self.get_data()[slice_object]
g = ArrayCoordMap(self.coordmap, self.shape)[slice_object]
coordmap = g.coordmap
if coordmap.function_domain.ndim > 0:
return self.__class__.from_image(self,
data=data,
coordmap=coordmap)
else:
return data
def __iter__(self):
""" Images do not have default iteration
This is because it's not obvious that axis 0 is the right axis to
iterate over. For example, we often want to iterate over the time or
volume axis, and this is more likely to be axis 3
"""
raise TypeError("Images do not have default iteration; "
"you can use ``iter_axis(img, axis)`` instead.")
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.shape == other.shape
and np.all(self.get_data() == other.get_data())
and np.all(self.affine == other.affine)
and (self.axes.coord_names == other.axes.coord_names))
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
options = np.get_printoptions()
np.set_printoptions(precision=6, threshold=64, edgeitems=2)
representation = \
'Image(\n data=%s,\n coordmap=%s)' % (
'\n '.join(repr(self._data).split('\n')),
'\n '.join(repr(self.coordmap).split('\n')))
np.set_printoptions(**options)
return representation
[docs] @classmethod
def from_image(klass, img, data=None, coordmap=None, metadata=None):
""" Classmethod makes new instance of this `klass` from instance `img`
Parameters
----------
data : array-like
object that as attribute ``shape`` and returns an array from
``np.asarray(data)``
coordmap : `AffineTransform` object
coordmap mapping the domain (input) voxel axes of the image to the
range (reference, output) axes - usually mm in real world space
metadata : dict, optional
Freeform metadata for image. Most common contents is ``header``
from nifti etc loaded images.
Returns
-------
img : `klass` instance
New image with data from `data`, coordmap from `coordmap` maybe
metadata from `metadata`
Notes
-----
Subclasses of ``Image`` with different semantics for ``__init__`` will
need to override this classmethod.
Examples
--------
>>> from nipy import load_image
>>> from nipy.core.api import Image
>>> from nipy.testing import anatfile
>>> aimg = load_image(anatfile)
>>> arr = np.arange(24).reshape((2,3,4))
>>> img = Image.from_image(aimg, data=arr)
"""
if data is None:
data = img._data
if coordmap is None:
coordmap = copy(img.coordmap)
if metadata is None:
metadata = copy(img.metadata)
return klass(data, coordmap, metadata)
[docs]class SliceMaker(object):
""" This class just creates slice objects for image resampling
It only has a __getitem__ method that returns its argument.
XXX Wouldn't need this if there was a way
XXX to do this
XXX subsample(img, [::2,::3,10:1:-1])
XXX
XXX Could be something like this Subsample(img)[::2,::3,10:1:-1]
"""
def __getitem__(self, index):
return index
slice_maker = SliceMaker()
[docs]def subsample(img, slice_object):
""" Subsample an image
Please don't use this function, but use direct image slicing instead. That
is, replace::
frame3 = subsample(im, slice_maker[:,:,:,3])
with::
frame3 = im[:,:,:,3]
Parameters
----------
img : Image
slice_object: int, slice or sequence of slice
An object representing a numpy 'slice'.
Returns
-------
img_subsampled: Image
An Image with data img.get_data()[slice_object] and an appropriately
corrected CoordinateMap.
Examples
--------
>>> from nipy.io.api import load_image
>>> from nipy.testing import funcfile
>>> from nipy.core.api import subsample, slice_maker
>>> im = load_image(funcfile)
>>> frame3 = subsample(im, slice_maker[:,:,:,3])
>>> np.allclose(frame3.get_data(), im.get_data()[:,:,:,3])
True
"""
warnings.warn('subsample is deprecated, please use image '
'slicing instead (e.g. img[:,:,1]',
DeprecationWarning,
stacklevel=2)
return img.__getitem__(slice_object)
[docs]def fromarray(data, innames, outnames):
"""Create an image from array `data`, and input/output coordinate names
The mapping between the input and output coordinate names is the identity
matrix.
Please don't use this routine, but instead prefer::
from nipy.core.api import Image, AffineTransform
img = Image(data, AffineTransform(innames, outnames, np.eye(4)))
where ``4`` is ``len(innames) + 1``.
Parameters
----------
data : numpy array
A numpy array of three dimensions.
innames : sequence
a list of input axis names
innames : sequence
a list of output axis names
Returns
-------
image : An `Image` object
See Also
--------
load : function for loading images
save : function for saving images
Examples
--------
>>> img = fromarray(np.zeros((2,3,4)), 'ijk', 'xyz')
>>> img.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('i', 'j', 'k'), name='', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z'), name='', coord_dtype=float64),
affine=array([[ 1., 0., 0., 0.],
[ 0., 1., 0., 0.],
[ 0., 0., 1., 0.],
[ 0., 0., 0., 1.]])
)
"""
warnings.warn('fromarray is deprecated, please use the Image '
'constructor instead',
DeprecationWarning,
stacklevel=2)
ndim = len(data.shape)
coordmap = AffineTransform.from_start_step(innames,
outnames,
(0.,)*ndim,
(1.,)*ndim)
return Image(data, coordmap)
@np.deprecate_with_doc('Please use rollimg instead')
def rollaxis(img, axis, inverse=False):
""" Roll `axis` backwards, until it lies in the first position.
It also reorders the reference coordinates by the same ordering.
This is done to preserve a diagonal affine matrix if image.affine
is diagonal. It also makes it possible to unambiguously specify
an axis to roll along in terms of either a reference name (i.e. 'z')
or an axis name (i.e. 'slice').
This function is deprecated; please use ``rollimg`` instead.
Parameters
----------
img : Image
Image whose axes and reference coordinates are to be reordered
by rolling.
axis : str or int
Axis to be rolled, can be specified by name or as an integer.
inverse : bool, optional
If inverse is True, then axis must be an integer and the first axis is
returned to the position axis. This keyword is deprecated and we'll
remove it in a future version of nipy.
Returns
-------
newimg : Image
Image with reordered axes and reference coordinates.
Examples
--------
>>> data = np.zeros((30,40,50,5))
>>> affine_transform = AffineTransform.from_params('ijkl', 'xyzt', np.diag([1,2,3,4,1]))
>>> im = Image(data, affine_transform)
>>> im.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('i', 'j', 'k', 'l'), name='', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z', 't'), name='', coord_dtype=float64),
affine=array([[ 1., 0., 0., 0., 0.],
[ 0., 2., 0., 0., 0.],
[ 0., 0., 3., 0., 0.],
[ 0., 0., 0., 4., 0.],
[ 0., 0., 0., 0., 1.]])
)
>>> im_t_first = rollaxis(im, 't')
>>> np.diag(im_t_first.affine)
array([ 4., 1., 2., 3., 1.])
>>> im_t_first.shape
(5, 30, 40, 50)
>>> im_t_first.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('l', 'i', 'j', 'k'), name='', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('t', 'x', 'y', 'z'), name='', coord_dtype=float64),
affine=array([[ 4., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0.],
[ 0., 0., 2., 0., 0.],
[ 0., 0., 0., 3., 0.],
[ 0., 0., 0., 0., 1.]])
)
"""
if inverse not in (True, False):
raise ValueError('Inverse should be True or False; did you mean to '
'use the ``rollimg` function instead?')
if isinstance(axis, int) and axis < 0:
axis = img.ndim + axis
if inverse:
if type(axis) != type(0):
raise ValueError('If carrying out inverse rolling, '
'axis must be an integer')
order = list(range(1, img.ndim))
order.insert(axis, 0)
return img.reordered_axes(order).reordered_reference(order)
if axis not in chain(range(img.axes.ndim),
img.axes.coord_names,
img.reference.coord_names):
raise ValueError('axis must be an axis number,'
'an axis name or a reference name')
# Find out which index axis corresonds to
in_index = out_index = -1
if type(axis) == type(''):
try:
in_index = img.axes.index(axis)
except:
pass
try:
out_index = img.reference.index(axis)
except:
pass
if in_index > 0 and out_index > 0 and in_index != out_index:
raise ValueError('ambiguous choice of axis -- it exists '
'both in as an axis name and a '
'reference name')
if in_index >= 0:
axis = in_index
else:
axis = out_index
if axis == -1:
axis += img.axes.ndim
order = list(range(img.ndim))
order.remove(axis)
order.insert(0, axis)
return img.reordered_axes(order).reordered_reference(order)
[docs]def rollimg(img, axis, start=0, fix0=True):
""" Roll `axis` backwards in the inputs, until it lies before `start`
Parameters
----------
img : Image
Image whose axes and reference coordinates are to be reordered by
rollimg.
axis : str or int
Axis to be rolled, can be specified by name or as an integer. If an
integer, axis is an input axis. If a name, can be name of input or
output axis. If an output axis, we search for the closest matching
input axis, and raise an AxisError if this fails.
start : str or int, optional
position before which to roll axis `axis`. Default to 0. Can again be
an integer (input axis) or name of input or output axis.
fix0 : bool, optional
Whether to allow for zero scaling when searching for an input axis
matching an output axis. Useful for images where time scaling is 0.
Returns
-------
newimg : Image
Image with reordered input axes and corresponding data.
Examples
--------
>>> data = np.zeros((30,40,50,5))
>>> affine_transform = AffineTransform('ijkl', 'xyzt', np.diag([1,2,3,4,1]))
>>> im = Image(data, affine_transform)
>>> im.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('i', 'j', 'k', 'l'), name='', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z', 't'), name='', coord_dtype=float64),
affine=array([[ 1., 0., 0., 0., 0.],
[ 0., 2., 0., 0., 0.],
[ 0., 0., 3., 0., 0.],
[ 0., 0., 0., 4., 0.],
[ 0., 0., 0., 0., 1.]])
)
>>> im_t_first = rollimg(im, 't')
>>> im_t_first.shape
(5, 30, 40, 50)
>>> im_t_first.coordmap
AffineTransform(
function_domain=CoordinateSystem(coord_names=('l', 'i', 'j', 'k'), name='', coord_dtype=float64),
function_range=CoordinateSystem(coord_names=('x', 'y', 'z', 't'), name='', coord_dtype=float64),
affine=array([[ 0., 1., 0., 0., 0.],
[ 0., 0., 2., 0., 0.],
[ 0., 0., 0., 3., 0.],
[ 4., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1.]])
)
"""
axis = input_axis_index(img.coordmap, axis, fix0)
start = input_axis_index(img.coordmap, start, fix0)
order = list(range(img.ndim))
order.remove(axis)
if axis < start:
start -= 1
order.insert(start, axis)
return img.reordered_axes(order)
[docs]def iter_axis(img, axis, asarray=False):
""" Return generator to slice an image `img` over `axis`
Parameters
----------
img : ``Image`` instance
axis : int or str
axis identifier, either name or axis number
asarray : {False, True}, optional
Returns
-------
g : generator
such that list(g) returns a list of slices over `axis`. If `asarray` is
`False` the slices are images. If `asarray` is True, slices are the
data from the images.
Examples
--------
>>> data = np.arange(24).reshape((4,3,2))
>>> img = Image(data, AffineTransform('ijk', 'xyz', np.eye(4)))
>>> slices = list(iter_axis(img, 'j'))
>>> len(slices)
3
>>> slices[0].shape
(4, 2)
>>> slices = list(iter_axis(img, 'k', asarray=True))
>>> slices[1].sum() == data[:,:,1].sum()
True
"""
rimg = rollimg(img, axis)
for i in range(rimg.shape[0]):
if asarray:
yield rimg[i].get_data()
else:
yield rimg[i]
[docs]def synchronized_order(img, target_img,
axes=True,
reference=True):
""" Reorder reference and axes of `img` to match target_img.
Parameters
----------
img : Image
target_img : Image
axes : bool, optional
If True, synchronize the order of the axes.
reference : bool, optional
If True, synchronize the order of the reference coordinates.
Returns
-------
newimg : Image
An Image satisfying newimg.axes == target.axes (if axes == True),
newimg.reference == target.reference (if reference == True).
Examples
--------
>>> data = np.random.standard_normal((3,4,7,5))
>>> im = Image(data, AffineTransform.from_params('ijkl', 'xyzt', np.diag([1,2,3,4,1])))
>>> im_scrambled = im.reordered_axes('iljk').reordered_reference('txyz')
>>> im == im_scrambled
False
>>> im_unscrambled = synchronized_order(im_scrambled, im)
>>> im == im_unscrambled
True
The images don't have to be the same shape
>>> data2 = np.random.standard_normal((3,11,9,4))
>>> im2 = Image(data, AffineTransform.from_params('ijkl', 'xyzt', np.diag([1,2,3,4,1])))
>>> im_scrambled2 = im2.reordered_axes('iljk').reordered_reference('xtyz')
>>> im_unscrambled2 = synchronized_order(im_scrambled2, im)
>>> im_unscrambled2.coordmap == im.coordmap
True
or have the same coordmap
>>> data3 = np.random.standard_normal((3,11,9,4))
>>> im3 = Image(data3, AffineTransform.from_params('ijkl', 'xyzt', np.diag([1,9,3,-2,1])))
>>> im_scrambled3 = im3.reordered_axes('iljk').reordered_reference('xtyz')
>>> im_unscrambled3 = synchronized_order(im_scrambled3, im)
>>> im_unscrambled3.axes == im.axes
True
>>> im_unscrambled3.reference == im.reference
True
>>> im_unscrambled4 = synchronized_order(im_scrambled3, im, axes=False)
>>> im_unscrambled4.axes == im.axes
False
>>> im_unscrambled4.axes == im_scrambled3.axes
True
>>> im_unscrambled4.reference == im.reference
True
"""
# Caution, we can't just use target_img.reference because other subclasses
# of Image may not have all axes in the .reference attribute.
target_axes = target_img.axes # = target_img.coordmap.function_domain
# the below not necessarily == target_image.reference
target_reference = target_img.coordmap.function_range
if axes:
img = img.reordered_axes(target_axes.coord_names)
if reference:
img = img.reordered_reference(target_reference.coord_names)
return img
[docs]def is_image(obj):
''' Returns true if this object obeys the Image API
This allows us to test for something that is duck-typing an image.
For now an array must have a 'coordmap' attribute, and a callable
'get_data' attribute.
Parameters
----------
obj : object
object for which to test API
Returns
-------
is_img : bool
True if object obeys image API
Examples
--------
>>> from nipy.testing import anatfile
>>> from nipy.io.api import load_image
>>> img = load_image(anatfile)
>>> is_image(img)
True
>>> class C(object): pass
>>> c = C()
>>> is_image(c)
False
'''
if not hasattr(obj, 'coordmap') or not hasattr(obj, 'metadata'):
return False
return callable(getattr(obj, 'get_data'))