Skip to content

Commit 9af9f4f

Browse files
matthew-bretteffigies
authored andcommitted
RF: refactor Cifti2Image API
Refactor Cifti2Image API to use dataobj convention. This allows the image to use array proxies, when we have learned to reshape array proxies, which should not take long. Make explicit the distinction between `nifti_header` and `extra`. Add `file_map` as an input argument.
1 parent 2dffa52 commit 9af9f4f

File tree

3 files changed

+66
-52
lines changed

3 files changed

+66
-52
lines changed

nibabel/cifti2/cifti2.py

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
import re
2222
import collections
2323

24-
import numpy as np
25-
2624
from .. import xmlutils as xml
27-
from ..filebasedimages import FileBasedHeader, FileBasedImage
28-
from ..nifti2 import Nifti2Image
25+
from ..filebasedimages import FileBasedHeader
26+
from ..dataobj_images import DataobjImage
27+
from ..nifti2 import Nifti2Image, Nifti2Header
28+
from ..arrayproxy import reshape_dataobj
2929

3030

3131
def _float_01(val):
@@ -939,62 +939,63 @@ def _to_xml_element(self):
939939
cifti.append(mat_xml)
940940
return cifti
941941

942-
@classmethod
943-
def from_header(klass, header=None):
944-
if header is None:
945-
return klass()
946-
if type(header) == klass:
947-
return header.copy()
948-
raise ValueError('header is not a Cifti2Header')
949-
950942
@classmethod
951943
def may_contain_header(klass, binaryblock):
952944
from .parse_cifti2 import _Cifti2AsNiftiHeader
953945
return _Cifti2AsNiftiHeader.may_contain_header(binaryblock)
954946

955947

956-
class Cifti2Image(FileBasedImage):
957-
""" Class for single file CIfTI2 format image
948+
class Cifti2Image(DataobjImage):
949+
""" Class for single file CIFTI2 format image
958950
"""
959951
header_class = Cifti2Header
960952
valid_exts = Nifti2Image.valid_exts
961953
files_types = Nifti2Image.files_types
962954
makeable = False
963955
rw = True
964956

965-
def __init__(self, data=None, header=None, nifti_header=None):
957+
def __init__(self,
958+
dataobj=None,
959+
header=None,
960+
nifti_header=None,
961+
extra=None,
962+
file_map=None):
966963
''' Initialize image
967964
968-
The image is a combination of (array, affine matrix, header, nifti_header),
969-
with optional metadata in `extra`, and filename / file-like objects
970-
contained in the `file_map` mapping.
965+
The image is a combination of (dataobj, header), with optional metadata
966+
in `nifti_header` (a NIfTI2 header). There may be more metadata in the
967+
mapping `extra`. Filename / file-like objects can also go in the
968+
`file_map` mapping.
971969
972970
Parameters
973971
----------
974972
dataobj : object
975-
Object containg image data. It should be some object that retuns an
976-
array from ``np.asanyarray``. It should have a ``shape`` attribute
977-
or property
978-
header : Cifti2Header object
979-
nifti_header : None or mapping or nifti2 header instance, optional
980-
metadata for this image format
973+
Object containing image data. It should be some object that returns
974+
an array from ``np.asanyarray``. It should have a ``shape``
975+
attribute or property.
976+
header : Cifti2Header instance
977+
Header with data for / from XML part of CIFTI2 format.
978+
nifti_header : None or mapping or NIfTI2 header instance, optional
979+
Metadata for NIfTI2 component of this format.
980+
extra : None or mapping
981+
Extra metadata not captured by `header` or `nifti_header`.
982+
file_map : mapping, optional
983+
Mapping giving file information for this image format.
981984
'''
982-
self._header = header if header is not None else Cifti2Header()
983-
self.data = data
984-
self.extra = nifti_header
985-
986-
def get_data(self):
987-
return self.data
985+
super(Cifti2Image, self).__init__(dataobj, header=header,
986+
extra=extra, file_map=file_map)
987+
self._nifti_header = Nifti2Header.from_header(nifti_header)
988988

989989
@property
990-
def shape(self):
991-
return self.data.shape
990+
def nifti_header(self):
991+
return self._nifti_header
992992

993993
@classmethod
994994
def from_file_map(klass, file_map):
995995
""" Load a Cifti2 image from a file_map
996996
997997
Parameters
998+
----------
998999
file_map : file_map
9991000
10001001
Returns
@@ -1011,51 +1012,54 @@ def from_file_map(klass, file_map):
10111012
cifti_header = item.get_content()
10121013
break
10131014
else:
1014-
raise ValueError('Nifti2 header does not contain a CIFTI2 '
1015+
raise ValueError('NIfTI2 header does not contain a CIFTI2 '
10151016
'extension')
10161017

1017-
# Construct cifti image
1018-
cifti_img = Cifti2Image(data=nifti_img.dataobj[0, 0, 0, 0],
1019-
header=cifti_header,
1020-
nifti_header=nifti_img.header)
1021-
cifti_img.file_map = nifti_img.file_map
1022-
return cifti_img
1018+
# Construct cifti image.
1019+
# User array proxy object where possible
1020+
dataobj = nifti_img.dataobj
1021+
return Cifti2Image(reshape_dataobj(dataobj, dataobj.shape[4:]),
1022+
header=cifti_header,
1023+
nifti_header=nifti_img.header,
1024+
file_map=file_map)
10231025

10241026
@classmethod
10251027
def from_image(klass, img):
10261028
''' Class method to create new instance of own class from `img`
10271029
10281030
Parameters
10291031
----------
1030-
img : ``spatialimage`` instance
1031-
In fact, an object with the API of ``FileBasedImage``.
1032+
img : instance
1033+
In fact, an object with the API of :class:`DataobjImage`.
10321034
10331035
Returns
10341036
-------
1035-
cimg : ``spatialimage`` instance
1036-
Image, of our own class
1037+
cimg : instance
1038+
Image, of our own class
10371039
'''
10381040
if isinstance(img, klass):
10391041
return img
1040-
else:
1041-
raise NotImplementedError
1042+
raise NotImplementedError
10421043

10431044
def to_file_map(self, file_map=None):
1044-
""" Save the current image to the specified file_map
1045+
""" Write image to `file_map` or contained ``self.file_map``
10451046
10461047
Parameters
10471048
----------
1048-
file_map : string
1049+
file_map : None or mapping, optional
1050+
files mapping. If None (default) use object's ``file_map``
1051+
attribute instead.
10491052
10501053
Returns
10511054
-------
10521055
None
10531056
"""
10541057
from .parse_cifti2 import Cifti2Extension
1055-
header = self.extra
1058+
header = self._nifti_header
10561059
extension = Cifti2Extension(content=self.header.to_xml())
10571060
header.extensions.append(extension)
1058-
data = np.reshape(self.data, (1, 1, 1, 1) + self.data.shape)
1061+
data = reshape_dataobj(self.dataobj,
1062+
(1, 1, 1, 1) + self.dataobj.shape)
10591063
# If qform not set, reset pixdim values so Nifti2 does not complain
10601064
if header['qform_code'] == 0:
10611065
header['pixdim'][:4] = 1

nibabel/cifti2/tests/test_cifti2.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from nose.tools import assert_true, assert_equal, assert_raises, assert_is_none
1212

13+
from nibabel.tests.test_dataobj_images import TestDataobjAPI as _TDA
14+
1315

1416
def compare_xml_leaf(str1, str2):
1517
x1 = ElementTree.fromstring(str1)
@@ -107,6 +109,7 @@ def test_cifti2_labeltable():
107109
assert_raises(ValueError, lt.__setitem__, 0, ('foo', 1.0, -1, 0, 1))
108110
assert_raises(ValueError, lt.__setitem__, 0, ('foo', 1.0, 0, -0.1, 1))
109111

112+
110113
def test_cifti2_label():
111114
lb = ci.Cifti2Label()
112115
lb.label = 'Test'

nibabel/cifti2/tests/test_cifti2io.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,17 @@ def test_read_internal():
5858

5959

6060
@needs_nibabel_data('nitest-cifti2')
61-
def test_read():
61+
def test_read_and_proxies():
6262
img2 = nib.load(DATA_FILE6)
6363
assert_true(isinstance(img2.header, ci.Cifti2Header))
6464
assert_equal(img2.shape, (1, 91282))
65+
# While we cannot reshape arrayproxies, all images are in-memory
66+
assert_true(img2.in_memory)
67+
data = img2.get_data()
68+
assert_true(data is img2.dataobj)
69+
# Uncaching has no effect, images are always array images
70+
img2.uncache()
71+
assert_true(data is img2.get_data())
6572

6673

6774
@needs_nibabel_data('nitest-cifti2')
@@ -95,7 +102,7 @@ def test_readwritedata():
95102
else:
96103
assert_equal(len(map1.label_table),
97104
len(map2.label_table))
98-
assert_array_almost_equal(img.data, img2.data)
105+
assert_array_almost_equal(img.dataobj, img2.dataobj)
99106

100107

101108
@needs_nibabel_data('nitest-cifti2')
@@ -122,7 +129,7 @@ def test_nibabel_readwritedata():
122129
else:
123130
assert_equal(len(map1.label_table),
124131
len(map2.label_table))
125-
assert_array_almost_equal(img.data, img2.data)
132+
assert_array_almost_equal(img.dataobj, img2.dataobj)
126133

127134

128135
@needs_nibabel_data('nitest-cifti2')

0 commit comments

Comments
 (0)