Skip to content

Commit 1c0a017

Browse files
committed
DOC + RF: rewrite gifti docstrings; refactor init
Responding to comments on Ben's previous PR #403. Expand and fix docstrings and gifti attribute descriptions from gifti spec. Rewrite GiftiDataArray init to allow full initialization of object, that can be used from GiftiDataArray.from_array. The refactor of the init needs tests; I'll add these if y'all agree this init refactoring is the right thing to do.
1 parent 15a7dd8 commit 1c0a017

File tree

1 file changed

+175
-85
lines changed

1 file changed

+175
-85
lines changed

nibabel/gifti/gifti.py

Lines changed: 175 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
# copyright and license terms.
77
#
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9+
""" Classes defining Gifti objects
10+
11+
The Gifti specification was (at time of writing) available as a PDF download
12+
from http://www.nitrc.org/projects/gifti/
13+
"""
914
from __future__ import division, print_function, absolute_import
1015

1116
import sys
@@ -25,8 +30,8 @@
2530

2631

2732
class GiftiMetaData(xml.XmlSerializable):
28-
""" A list of GiftiNVPairs in stored in
29-
the list self.data """
33+
""" A sequence of GiftiNVPairs containing metadata for a gifti data array
34+
"""
3035

3136
def __init__(self, nvpair=None):
3237
self.data = []
@@ -68,16 +73,26 @@ def print_summary(self):
6873

6974

7075
class GiftiNVPairs(object):
71-
"""
72-
name = str
73-
value = str
76+
""" Gifti name / value pairs
77+
78+
Attributes
79+
----------
80+
name : str
81+
value : str
7482
"""
7583
def __init__(self, name='', value=''):
7684
self.name = name
7785
self.value = value
7886

7987

8088
class GiftiLabelTable(xml.XmlSerializable):
89+
""" Gifti label table: a sequence of key, label pairs
90+
91+
From the gifti spec dated 2011-01-14:
92+
The label table is used by DataArrays whose values are an key into the
93+
LabelTable's labels. A file should contain at most one LabelTable and
94+
it must be located in the file prior to any DataArray elements.
95+
"""
8196

8297
def __init__(self):
8398
self.labels = []
@@ -104,17 +119,34 @@ def print_summary(self):
104119

105120

106121
class GiftiLabel(xml.XmlSerializable):
107-
"""
108-
key = int
109-
label = str
110-
# rgba
111-
# freesurfer examples seem not to conform
112-
# to datatype "NIFTI_TYPE_RGBA32" because they
113-
# are floats, not unsigned 32-bit integers
114-
red = float
115-
green = float
116-
blue = float
117-
alpha = float
122+
""" Gifti label: association of integer key with optional RGBA values
123+
124+
Quotes are from the gifti spec dated 2011-01-14.
125+
126+
Attributes
127+
----------
128+
key : int
129+
(From the spec): "This required attribute contains a non-negative
130+
integer value. If a DataArray's Intent is NIFTI_INTENT_LABEL and a
131+
value in the DataArray is 'X', its corresponding label is the label
132+
with the Key attribute containing the value 'X'. In early versions of
133+
the GIFTI file format, the attribute Index was used instead of Key. If
134+
an Index attribute is encountered, it should be processed like the Key
135+
attribute."
136+
label : str
137+
red : None or float
138+
Optional value for red.
139+
green : None or float
140+
Optional value for green.
141+
blue : None or float
142+
Optional value for blue.
143+
alpha : None or float
144+
Optional value for alpha.
145+
146+
Notes
147+
-----
148+
freesurfer examples seem not to conform to datatype "NIFTI_TYPE_RGBA32"
149+
because they are floats, not 4 8-bit integers.
118150
"""
119151

120152
def __init__(self, key=0, label='', red=None, green=None, blue=None,
@@ -137,12 +169,12 @@ def rgba(self):
137169

138170
@rgba.setter
139171
def rgba(self, rgba):
140-
""" Set RGBA via tuple
172+
""" Set RGBA via sequence
141173
142174
Parameters
143175
----------
144-
rgba : tuple (red, green, blue, alpha)
145-
176+
rgba : length 4 sequence
177+
Sequence containing values for red, green, blue, alpha.
146178
"""
147179
if len(rgba) != 4:
148180
raise ValueError('rgba must be length 4.')
@@ -159,10 +191,38 @@ def _arr2txt(arr, elem_fmt):
159191

160192

161193
class GiftiCoordSystem(xml.XmlSerializable):
162-
"""
163-
dataspace = int
164-
xformspace = int
165-
xform = np.ndarray # 4x4 numpy array
194+
""" Gifti coordinate system transform matrix
195+
196+
Quotes are from the gifti spec dated 2011-01-14.
197+
198+
"For a DataArray with an Intent NIFTI_INTENT_POINTSET, this element
199+
describes the stereotaxic space of the data before and after the
200+
application of a transformation matrix. The most common stereotaxic
201+
space is the Talairach Space that places the origin at the anterior
202+
commissure and the negative X, Y, and Z axes correspond to left,
203+
posterior, and inferior respectively. At least one
204+
CoordinateSystemTransformMatrix is required in a DataArray with an
205+
intent of NIFTI_INTENT_POINTSET. Multiple
206+
CoordinateSystemTransformMatrix elements may be used to describe the
207+
transformation to multiple spaces."
208+
209+
Attributes
210+
----------
211+
dataspace : int
212+
From the spec: "Contains the stereotaxic space of a DataArray's data
213+
prior to application of the transformation matrix. The stereotaxic
214+
space should be one of:
215+
NIFTI_XFORM_UNKNOWN
216+
NIFTI_XFORM_SCANNER_ANAT
217+
NIFTI_XFORM_ALIGNED_ANAT
218+
NIFTI_XFORM_TALAIRACH
219+
NIFTI_XFORM_MNI_152"
220+
xformspace : int
221+
Spec: "Contains the stereotaxic space of a DataArray's data after
222+
application of the transformation matrix. See the DataSpace element for
223+
a list of stereotaxic spaces."
224+
xform : array-like shape (4, 4)
225+
Affine transformation matrix
166226
"""
167227

168228
def __init__(self, dataspace=0, xformspace=0, xform=None):
@@ -205,8 +265,8 @@ def _to_xml_element(self):
205265

206266

207267
def _data_tag_element(dataarray, encoding, datatype, ordering):
208-
""" Creates the data tag depending on the required encoding,
209-
returns as XML element"""
268+
""" Creates data tag with given `encoding`, returns as XML element
269+
"""
210270
import zlib
211271
ord = array_index_order_codes.npcode[ordering]
212272
enclabel = gifti_encoding_codes.label[encoding]
@@ -228,42 +288,84 @@ def _data_tag_element(dataarray, encoding, datatype, ordering):
228288

229289

230290
class GiftiDataArray(xml.XmlSerializable):
231-
"""
232-
# These are for documentation only; we don't use these class variables
233-
intent = int
234-
datatype = int
235-
ind_ord = int
236-
dims = list
237-
encoding = int
238-
endian = int
239-
ext_fname = str
240-
ext_offset = str
241-
data = np.ndarray
242-
coordsys = GiftiCoordSystem
243-
meta = GiftiMetaData
291+
""" Container for Gifti numerical data array and associated metadata
292+
293+
Quotes are from the gifti spec dated 2011-01-14.
294+
295+
Description of DataArray in spec:
296+
"This element contains the numeric data and its related metadata. The
297+
CoordinateSystemTransformMatrix child is only used when the DataArray's
298+
Intent is NIFTI_INTENT_POINTSET. FileName and FileOffset are required
299+
if the data is stored in an external file."
300+
301+
Attributes
302+
----------
303+
darray : None or ndarray
304+
Data array
305+
intent : int
306+
NIFTI intent code, see nifti1.intent_codes
307+
datatype : int
308+
NIFTI data type codes, see nifti1.data_type_codes. From the spec:
309+
"This required attribute describes the numeric type of the data
310+
contained in a Data Array and are limited to the types displayed in the
311+
table:
312+
313+
NIFTI_TYPE_UINT8 : Unsigned, 8-bit bytes.
314+
NIFTI_TYPE_INT32 : Signed, 32-bit integers.
315+
NIFTI_TYPE_FLOAT32 : 32-bit single precision floating point."
316+
317+
At the moment, we do not enforce that the datatype is one of these
318+
three.
319+
encoding : string
320+
Encoding of the data, see util.gifti_encoding_codes; default is
321+
GIFTI_ENCODING_B64GZ.
322+
endian : string
323+
The Endianness to store the data array. Should correspond to the
324+
machine endianness. Default is system byteorder.
325+
coordsys : :class:`GiftiCoordSystem` instance
326+
Input and output coordinate system with tranformation matrix between
327+
the two.
328+
ind_ord : int
329+
The ordering of the array. see util.array_index_order_codes. Default
330+
is RowMajorOrder - C ordering
331+
meta : :class:`GiftiMetaData` instance
332+
An instance equivalent to a dictionary for metadata information.
333+
ext_fname : str
334+
Filename in which data is stored, or empty string if no corresponding
335+
filename.
336+
ext_offset : int
337+
Position in bytes within `ext_fname` at which to start reading data.
244338
"""
245339

246-
def __init__(self, data=None,
340+
def __init__(self,
341+
data=None,
342+
intent='NIFTI_INTENT_NONE',
343+
datatype=None,
247344
encoding="GIFTI_ENCODING_B64GZ",
248345
endian=sys.byteorder,
249346
coordsys=None,
250347
ordering="C",
251-
meta=None):
348+
meta=None,
349+
ext_fname='',
350+
ext_offset=0):
252351
"""
253352
Returns a shell object that cannot be saved.
254353
"""
255-
self.data = data
256-
self.dims = []
257-
self.meta = meta or GiftiMetaData()
354+
self.data = None if data is None else np.asarray(data)
355+
self.intent = intent_codes.code[intent]
356+
if datatype is None:
357+
datatype = 'none' if self.data is None else self.data.dtype
358+
self.datatype = data_type_codes.code[datatype]
359+
self.encoding = gifti_encoding_codes.code[encoding]
360+
self.endian = gifti_endian_codes.code[endian]
258361
self.coordsys = coordsys or GiftiCoordSystem()
259-
self.ext_fname = ''
260-
self.ext_offset = ''
261-
self.intent = 0 # required attribute, NIFTI_INTENT_NONE
262-
self.datatype = 0 # required attribute, void/none
263-
# python/numpy default: column major order
264362
self.ind_ord = array_index_order_codes.code[ordering]
265-
self.encoding = encoding
266-
self.endian = endian
363+
self.meta = (GiftiMetaData() if meta is None else
364+
meta if isinstance(meta, GiftiMetaData) else
365+
GiftiMetaData.from_dict(meta))
366+
self.ext_fname = ext_fname
367+
self.ext_offset = ext_offset
368+
self.dims = [] if self.data is None else list(self.data.shape)
267369

268370
@property
269371
def num_dim(self):
@@ -308,22 +410,14 @@ def from_array(klass,
308410
-------
309411
da : instance of our own class
310412
"""
311-
if meta is None:
312-
meta = {}
313-
cda = klass(darray)
314-
cda.dims = list(darray.shape)
315-
if datatype is None:
316-
cda.datatype = data_type_codes.code[darray.dtype]
317-
else:
318-
cda.datatype = data_type_codes.code[datatype]
319-
cda.intent = intent_codes.code[intent]
320-
cda.encoding = gifti_encoding_codes.code[encoding]
321-
cda.endian = gifti_endian_codes.code[endian]
322-
if coordsys is not None:
323-
cda.coordsys = coordsys
324-
cda.ind_ord = array_index_order_codes.code[ordering]
325-
cda.meta = GiftiMetaData.from_dict(meta)
326-
return cda
413+
return klass(data=darray,
414+
intent=intent,
415+
datatype=datatype,
416+
encoding=encoding,
417+
endian=endian,
418+
coordsys=coordsys,
419+
ordering=ordering,
420+
meta=meta)
327421

328422
def _to_xml_element(self):
329423
# fix endianness to machine endianness
@@ -364,7 +458,7 @@ def to_xml_open(self):
364458
%s\tEncoding="%s"
365459
\tEndian="%s"
366460
\tExternalFileName="%s"
367-
\tExternalFileOffset="%s">\n"""
461+
\tExternalFileOffset="%d">\n"""
368462
di = ""
369463
for i, n in enumerate(self.dims):
370464
di = di + '\tDim%s=\"%s\"\n' % (str(i), str(n))
@@ -475,8 +569,7 @@ def labeltable(self, labeltable):
475569
476570
Parameters
477571
----------
478-
labeltable : GiftiLabelTable
479-
572+
labeltable : :class:`GiftiLabelTable` instance
480573
"""
481574
if not isinstance(labeltable, GiftiLabelTable):
482575
raise TypeError("Not a valid GiftiLabelTable instance")
@@ -500,11 +593,7 @@ def meta(self, meta):
500593
501594
Parameters
502595
----------
503-
meta : GiftiMetaData
504-
505-
Returns
506-
-------
507-
None
596+
meta : :class:`GiftiMetaData` instance
508597
"""
509598
if not isinstance(meta, GiftiMetaData):
510599
raise TypeError("Not a valid GiftiMetaData instance")
@@ -523,7 +612,7 @@ def add_gifti_data_array(self, dataarr):
523612
524613
Parameters
525614
----------
526-
dataarr : GiftiDataArray
615+
dataarr : :class:`GiftiDataArray` instance
527616
"""
528617
if not isinstance(dataarr, GiftiDataArray):
529618
raise TypeError("Not a valid GiftiDataArray instance")
@@ -541,11 +630,9 @@ def remove_gifti_data_array_by_intent(self, intent):
541630
self.darrays.remove(dele)
542631

543632
def get_arrays_from_intent(self, intent):
544-
""" Returns a a list of GiftiDataArray elements matching
545-
the given intent """
546-
633+
""" Return list of GiftiDataArray elements matching given intent
634+
"""
547635
it = intent_codes.code[intent]
548-
549636
return [x for x in self.darrays if x.intent == it]
550637

551638
@np.deprecate_with_doc("Use get_arrays_from_intent instead.")
@@ -594,7 +681,9 @@ def to_file_map(self, file_map=None):
594681
595682
Parameters
596683
----------
597-
file_map : string
684+
file_map : dict
685+
Dictionary with single key ``image`` with associated value which is
686+
a :class:`FileHolder` instance pointing to the image file.
598687
599688
Returns
600689
-------
@@ -610,17 +699,18 @@ def from_file_map(klass, file_map, buffer_size=35000000):
610699
""" Load a Gifti image from a file_map
611700
612701
Parameters
613-
file_map : string
702+
----------
703+
file_map : dict
704+
Dictionary with single key ``image`` with associated value which is
705+
a :class:`FileHolder` instance pointing to the image file.
614706
615707
Returns
616708
-------
617709
img : GiftiImage
618-
Returns a GiftiImage
619-
"""
710+
"""
620711
parser = klass.parser(buffer_size=buffer_size)
621712
parser.parse(fptr=file_map['image'].get_prepare_fileobj('rb'))
622-
img = parser.img
623-
return img
713+
return parser.img
624714

625715
@classmethod
626716
def from_filename(klass, filename, buffer_size=35000000):

0 commit comments

Comments
 (0)