Skip to content

Commit cab9b71

Browse files
author
Ben Cipollini
committed
Make Gifti extend FileBasedImage, implement from_file_obj, to_file_obj.
1 parent 9a67e76 commit cab9b71

File tree

5 files changed

+140
-67
lines changed

5 files changed

+140
-67
lines changed

nibabel/gifti/gifti.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import numpy as np
1414

1515
from .. import xmlutils as xml
16+
from ..filebasedimages import FileBasedImage
1617
from ..nifti1 import data_type_codes, xform_codes, intent_codes
1718
from .util import (array_index_order_codes, gifti_encoding_codes,
1819
gifti_endian_codes, KIND2FMT)
@@ -384,7 +385,9 @@ def metadata(self):
384385
return self.meta.metadata
385386

386387

387-
class GiftiImage(xml.XmlSerializable):
388+
class GiftiImage(FileBasedImage, xml.XmlSerializable):
389+
files_types = (('image', '.gii'),)
390+
valid_exts = ('.gii',)
388391

389392
def __init__(self, meta=None, labeltable=None, darrays=None,
390393
version="1.0"):
@@ -529,3 +532,66 @@ def to_xml(self, enc='utf-8'):
529532
return b"""<?xml version="1.0" encoding="UTF-8"?>
530533
<!DOCTYPE GIFTI SYSTEM "http://www.nitrc.org/frs/download.php/115/gifti.dtd">
531534
""" + xml.XmlSerializable.to_xml(self, enc)
535+
536+
@classmethod
537+
def from_file_map(klass, file_map):
538+
""" Load a Gifti image from a file_map
539+
540+
Parameters
541+
file_map : string
542+
543+
Returns
544+
-------
545+
img : GiftiImage
546+
Returns a GiftiImage
547+
"""
548+
from .parse_gifti_fast import parse_gifti_file
549+
return parse_gifti_file(fptr=file_map['image'].get_prepare_fileobj('rb'))
550+
551+
def to_file_map(self, file_map=None):
552+
""" Save the current image to the specified file_map
553+
554+
Parameters
555+
----------
556+
file_map : string
557+
558+
Returns
559+
-------
560+
None
561+
562+
Notes
563+
-----
564+
We write all files with utf-8 encoding, and specify this at the top of the
565+
XML file with the ``encoding`` attribute.
566+
567+
The Gifti spec suggests using the following suffixes to your
568+
filename when saving each specific type of data:
569+
570+
.gii
571+
Generic GIFTI File
572+
.coord.gii
573+
Coordinates
574+
.func.gii
575+
Functional
576+
.label.gii
577+
Labels
578+
.rgba.gii
579+
RGB or RGBA
580+
.shape.gii
581+
Shape
582+
.surf.gii
583+
Surface
584+
.tensor.gii
585+
Tensors
586+
.time.gii
587+
Time Series
588+
.topo.gii
589+
Topology
590+
591+
The Gifti file is stored in endian convention of the current machine.
592+
"""
593+
# Our giftis are always utf-8 encoded - see GiftiImage.to_xml
594+
if file_map is None:
595+
file_map = self.file_map
596+
f = file_map['image'].get_prepare_fileobj('wb')
597+
f.write(self.to_xml())

nibabel/gifti/parse_gifti_fast.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def pending_data(self):
318318
return not self._char_blocks is None
319319

320320

321-
def parse_gifti_file(fname, buffer_size=None):
321+
def parse_gifti_file(fname=None, fptr=None, buffer_size=None):
322322
""" Parse gifti file named `fname`, return image
323323
324324
Parameters
@@ -334,28 +334,37 @@ def parse_gifti_file(fname, buffer_size=None):
334334
-------
335335
img : gifti image
336336
"""
337+
assert (fname is not None) + (fptr is not None) == 1, "Specify only fname or fptr, not both"
338+
339+
if fptr is None:
340+
with open(fname, 'rb') as datasource:
341+
return parse_gifti_file(fptr=datasource, buffer_size=buffer_size)
342+
else:
343+
datasource = fptr
344+
337345
if buffer_size is None:
338346
buffer_sz_val = 35000000
339347
else:
340348
buffer_sz_val = buffer_size
341-
with open(fname, 'rb') as datasource:
342-
parser = ParserCreate()
343-
parser.buffer_text = True
344-
try:
345-
parser.buffer_size = buffer_sz_val
346-
except AttributeError:
347-
if not buffer_size is None:
348-
raise ValueError('Cannot set buffer size for parser')
349-
HANDLER_NAMES = ['StartElementHandler',
350-
'EndElementHandler',
351-
'CharacterDataHandler']
352-
out = Outputter()
353-
for name in HANDLER_NAMES:
354-
setattr(parser, name, getattr(out, name))
355-
try:
356-
parser.ParseFile(datasource)
357-
except ExpatError:
358-
print('An expat error occured while parsing the Gifti file.')
349+
350+
parser = ParserCreate()
351+
parser.buffer_text = True
352+
try:
353+
parser.buffer_size = buffer_sz_val
354+
except AttributeError:
355+
if not buffer_size is None:
356+
raise ValueError('Cannot set buffer size for parser')
357+
HANDLER_NAMES = ['StartElementHandler',
358+
'EndElementHandler',
359+
'CharacterDataHandler']
360+
out = Outputter()
361+
for name in HANDLER_NAMES:
362+
setattr(parser, name, getattr(out, name))
363+
try:
364+
parser.ParseFile(datasource)
365+
except ExpatError:
366+
print('An expat error occured while parsing the Gifti file.')
367+
359368
# Reality check for pending data
360369
assert out.pending_data is False
361370
# update filename

nibabel/gifti/tests/test_gifti.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44

55
import numpy as np
66

7+
import nibabel as nib
78
from nibabel.externals.six import string_types
89
from nibabel.gifti import (GiftiImage, GiftiDataArray, GiftiLabel,
9-
GiftiLabelTable, GiftiMetaData, giftiio)
10+
GiftiLabelTable, GiftiMetaData)
1011
from nibabel.gifti.gifti import data_tag
1112
from nibabel.nifti1 import data_type_codes, intent_codes
1213

1314
from numpy.testing import (assert_array_almost_equal,
1415
assert_array_equal)
1516
from nose.tools import (assert_true, assert_false, assert_equal, assert_raises)
1617
from nibabel.testing import clear_and_catch_warnings
17-
from .test_giftiio import (DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4,
18-
DATA_FILE5, DATA_FILE6)
18+
from .test_parse_gifti_fast import (DATA_FILE1, DATA_FILE2, DATA_FILE3,
19+
DATA_FILE4, DATA_FILE5, DATA_FILE6)
1920

2021

2122
def test_gifti_image():
@@ -142,7 +143,7 @@ def assign_rgba(gl, val):
142143
def test_print_summary():
143144
for fil in [DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4,
144145
DATA_FILE5, DATA_FILE6]:
145-
gimg = giftiio.read(fil)
146+
gimg = nib.load(fil)
146147
gimg.print_summary()
147148

148149

nibabel/gifti/tests/test_giftiio.py renamed to nibabel/gifti/tests/test_parse_gifti_fast.py

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import numpy as np
1616

1717
from ... import gifti as gi
18+
from ...loadsave import load, save
19+
1820
from ..util import gifti_endian_codes
1921
from ...nifti1 import xform_codes
2022

@@ -102,23 +104,23 @@ def test_read_ordering():
102104
# DATA_FILE1 has an expected darray[0].data shape of (3,3). However if we
103105
# read another image first (DATA_FILE2) then the shape is wrong
104106
# Read an image
105-
img2 = gi.read(DATA_FILE2)
107+
img2 = load(DATA_FILE2)
106108
assert_equal(img2.darrays[0].data.shape, (143479, 1))
107109
# Read image for which we know output shape
108-
img = gi.read(DATA_FILE1)
110+
img = load(DATA_FILE1)
109111
assert_equal(img.darrays[0].data.shape, (3,3))
110112

111113

112114
def test_load_metadata():
113115
for i, dat in enumerate(datafiles):
114-
img = gi.read(dat)
116+
img = load(dat)
115117
me = img.meta
116118
assert_equal(numDA[i], img.numDA)
117119
assert_equal(img.version,'1.0')
118120

119121

120122
def test_metadata_deprecations():
121-
img = gi.read(datafiles[0])
123+
img = load(datafiles[0])
122124
me = img.meta
123125

124126
# Test deprecation
@@ -133,12 +135,11 @@ def test_metadata_deprecations():
133135

134136

135137
def test_load_dataarray1():
136-
img1 = gi.read(DATA_FILE1)
137-
138+
img1 = load(DATA_FILE1)
138139
# Round trip
139140
with InTemporaryDirectory():
140-
gi.write(img1, 'test.gii')
141-
bimg = gi.read('test.gii')
141+
save(img1, 'test.gii')
142+
bimg = load('test.gii')
142143
for img in (img1, bimg):
143144
assert_array_almost_equal(img.darrays[0].data, DATA_FILE1_darr1)
144145
assert_array_almost_equal(img.darrays[1].data, DATA_FILE1_darr2)
@@ -152,40 +153,36 @@ def test_load_dataarray1():
152153

153154

154155
def test_load_dataarray2():
155-
img2 = gi.read(DATA_FILE2)
156-
156+
img2 = load(DATA_FILE2)
157157
# Round trip
158158
with InTemporaryDirectory():
159-
gi.write(img2, 'test.gii')
160-
bimg = gi.read('test.gii')
159+
save(img2, 'test.gii')
160+
bimg = load('test.gii')
161161
for img in (img2, bimg):
162162
assert_array_almost_equal(img.darrays[0].data[:10], DATA_FILE2_darr1)
163163

164164

165165
def test_load_dataarray3():
166-
img3 = gi.read(DATA_FILE3)
167-
166+
img3 = load(DATA_FILE3)
168167
with InTemporaryDirectory():
169-
gi.write(img3, 'test.gii')
170-
bimg = gi.read('test.gii')
168+
save(img3, 'test.gii')
169+
bimg = load('test.gii')
171170
for img in (img3, bimg):
172171
assert_array_almost_equal(img.darrays[0].data[30:50], DATA_FILE3_darr1)
173172

174173

175174
def test_load_dataarray4():
176-
img4 = gi.read(DATA_FILE4)
177-
175+
img4 = load(DATA_FILE4)
178176
# Round trip
179177
with InTemporaryDirectory():
180-
gi.write(img4, 'test.gii')
181-
bimg = gi.read('test.gii')
178+
save(img4, 'test.gii')
179+
bimg = load('test.gii')
182180
for img in (img4, bimg):
183181
assert_array_almost_equal(img.darrays[0].data[:10], DATA_FILE4_darr1)
184182

185183

186184
def test_dataarray5():
187-
img5 = gi.read(DATA_FILE5)
188-
185+
img5 = load(DATA_FILE5)
189186
for da in img5.darrays:
190187
assert_equal(gifti_endian_codes.byteorder[da.endian], 'little')
191188
assert_array_almost_equal(img5.darrays[0].data, DATA_FILE5_darr1)
@@ -204,8 +201,8 @@ def test_base64_written():
204201
assert_false(b'Base64Binary' in contents)
205202
assert_false(b'LittleEndian' in contents)
206203
# Round trip
207-
img5 = gi.read(DATA_FILE5)
208-
gi.write(img5, 'fixed.gii')
204+
img5 = load(DATA_FILE5)
205+
save(img5, 'fixed.gii')
209206
with open('fixed.gii', 'rb') as fobj:
210207
contents = fobj.read()
211208
# The bad codes have gone, replaced by the good ones
@@ -216,17 +213,17 @@ def test_base64_written():
216213
assert_true(b'LittleEndian' in contents)
217214
else:
218215
assert_true(b'BigEndian' in contents)
219-
img5_fixed = gi.read('fixed.gii')
216+
img5_fixed = load('fixed.gii')
220217
darrays = img5_fixed.darrays
221218
assert_array_almost_equal(darrays[0].data, DATA_FILE5_darr1)
222219
assert_array_almost_equal(darrays[1].data, DATA_FILE5_darr2)
223220

224221

225222
def test_readwritedata():
226-
img = gi.read(DATA_FILE2)
223+
img = load(DATA_FILE2)
227224
with InTemporaryDirectory():
228-
gi.write(img, 'test.gii')
229-
img2 = gi.read('test.gii')
225+
save(img, 'test.gii')
226+
img2 = load('test.gii')
230227
assert_equal(img.numDA,img2.numDA)
231228
assert_array_almost_equal(img.darrays[0].data,
232229
img2.darrays[0].data)
@@ -247,8 +244,7 @@ def test_write_newmetadata():
247244

248245

249246
def test_load_getbyintent():
250-
img = gi.read(DATA_FILE1)
251-
247+
img = load(DATA_FILE1)
252248
da = img.get_arrays_from_intent("NIFTI_INTENT_POINTSET")
253249
assert_equal(len(da), 1)
254250

@@ -268,12 +264,11 @@ def test_load_getbyintent():
268264

269265

270266
def test_load_labeltable():
271-
img6 = gi.read(DATA_FILE6)
272-
267+
img6 = load(DATA_FILE6)
273268
# Round trip
274269
with InTemporaryDirectory():
275-
gi.write(img6, 'test.gii')
276-
bimg = gi.read('test.gii')
270+
save(img6, 'test.gii')
271+
bimg = load('test.gii')
277272
for img in (img6, bimg):
278273
assert_array_almost_equal(img.darrays[0].data[:3], DATA_FILE6_darr1)
279274
assert_equal(len(img.labeltable.labels), 36)
@@ -288,7 +283,7 @@ def test_load_labeltable():
288283

289284

290285
def test_labeltable_deprecations():
291-
img = gi.read(DATA_FILE6)
286+
img = load(DATA_FILE6)
292287
lt = img.labeltable
293288

294289
# Test deprecation
@@ -307,7 +302,7 @@ def test_parse_dataarrays():
307302
img = gi.GiftiImage()
308303

309304
with InTemporaryDirectory():
310-
gi.write(img, fn)
305+
save(img, fn)
311306
with open(fn, 'r') as fp:
312307
txt = fp.read()
313308
# Make a bad gifti.
@@ -317,6 +312,6 @@ def test_parse_dataarrays():
317312

318313
with clear_and_catch_warnings() as w:
319314
warnings.filterwarnings('once', category=UserWarning)
320-
gi.read(fn)
315+
load(fn)
321316
assert_equal(len(w), 1)
322317
assert_equal(img.numDA, 0)

0 commit comments

Comments
 (0)