# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""
The volume data class
This class represents indexable data embedded in a 3D space
"""
from __future__ import absolute_import
import copy as copy
import numpy as np
# Local imports
from .volume_field import VolumeField
from ..transforms.transform import CompositionError
################################################################################
# class `VolumeData`
################################################################################
[docs]class VolumeData(VolumeField):
""" A class representing data embedded in a 3D space
This object has data stored in an array like, that knows how it is
mapped to a 3D "real-world space", and how it can change real-world
coordinate system.
Attributes
-----------
world_space: string
World space the data is embedded in. For instance `mni152`.
metadata: dictionnary
Optional, user-defined, dictionnary used to carry around
extra information about the data as it goes through
transformations. The class consistency of this information is
not maintained as the data is modified.
_data:
Private pointer to the data.
Notes
------
The data is stored in an undefined way: prescalings might need to
be applied to it before using it, or the data might be loaded on
demand. The best practice to access the data is not to access the
_data attribute, but to use the `get_data` method.
"""
#---------------------------------------------------------------------------
# Public attributes -- VolumeData interface
#---------------------------------------------------------------------------
# The interpolation logic used
interpolation = 'continuous'
#---------------------------------------------------------------------------
# Private attributes -- VolumeData interface
#---------------------------------------------------------------------------
# The data (ndarray-like)
_data = None
#---------------------------------------------------------------------------
# Public methods -- VolumeData interface
#---------------------------------------------------------------------------
[docs] def get_data(self):
""" Return data as a numpy array.
"""
return np.asanyarray(self._data)
[docs] def like_from_data(self, data):
""" Returns an volumetric data structure with the same
relationship between data and world space, and same metadata,
but different data.
Parameters
-----------
data: ndarray
"""
raise NotImplementedError
[docs] def resampled_to_img(self, target_image, interpolation=None):
""" Resample the data to be on the same voxel grid than the target
volume structure.
Parameters
----------
target_image : nipy image
Nipy image onto the voxel grid of which the data will be
resampled. This can be any kind of img understood by Nipy
(datasets, pynifti objects, nibabel object) or a string
giving the path to a nifti of analyse image.
interpolation : None, 'continuous' or 'nearest', optional
Interpolation type used when calculating values in
different word spaces. If None, the image's interpolation
logic is used.
Returns
-------
resampled_image : nipy_image
New nipy image with the data resampled.
Notes
-----
Both the target image and the original image should be
embedded in the same world space.
"""
if not hasattr(target_image, 'world_space'):
from ..converters import as_volume_img
target_image = as_volume_img(target_image)
if not target_image.world_space == self.world_space:
raise CompositionError(
"The two images are not embedded in the same world space")
x, y, z = target_image.get_world_coords()
new_data = self.values_in_world(x, y, z,
interpolation=interpolation)
new_img = target_image.like_from_data(new_data)
new_img.metadata = copy.copy(self.metadata)
return new_img
#---------------------------------------------------------------------------
# Private methods
#---------------------------------------------------------------------------
def _apply_transform(self, w2w_transform):
""" Method applying the transform: inner part of
transformed_with, used in subclassing.
"""
new_v2w_transform = \
self.get_transform().composed_with(w2w_transform)
new_img = copy.copy(self)
new_img._transform = new_v2w_transform
return new_img
def _get_interpolation_order(self, interpolation):
""" Inner method used to get the interpolation type for the
image.
"""
if interpolation is None:
interpolation = self.interpolation
if interpolation == 'continuous':
interpolation_order = 3
elif interpolation == 'nearest':
interpolation_order = 0
else:
raise ValueError("interpolation must be either 'continuous' "
"or 'nearest'")
return interpolation_order
# TODO: We need to implement (or check if implemented) hashing,
# weakref, pickling?
def __repr__(self):
options = np.get_printoptions()
np.set_printoptions(precision=5, threshold=64, edgeitems=2)
representation = \
'%s(\n data=%s,\n world_space=%s,\n interpolation=%s)' % (
self.__class__.__name__,
'\n '.join(repr(self._data).split('\n')),
repr(self.world_space),
repr(self.interpolation),
)
np.set_printoptions(**options)
return representation
def __copy__(self):
return self.like_from_data(self.get_data().copy())
def __deepcopy__(self, option):
""" Copy the Volume and the arrays and metadata it contains.
"""
out = self.__copy__()
out.metadata = copy.deepcopy(self.metadata)
return out
def __eq__(self, other):
return ( self.world_space == other.world_space
and self.get_transform() == other.get_transform()
and np.all(self.get_data() == other.get_data())
and self.interpolation == other.interpolation
)