Skip to content

Commit 5f632fe

Browse files
committed
BF+TST: fix XML output from in-memory Gifti array
GiftiDataArrays created in memory (rather than loaded from disk) had ExternalFileOffset attribute value as an integer, but for valid XML, the values have to be strings. Closes gh-469.
1 parent a4724b7 commit 5f632fe

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

nibabel/gifti/gifti.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ def _to_xml_element(self):
436436
# fix endianness to machine endianness
437437
self.endian = gifti_endian_codes.code[sys.byteorder]
438438

439+
# All attribute values must be strings
439440
data_array = xml.Element('DataArray', attrib={
440441
'Intent': intent_codes.niistring[self.intent],
441442
'DataType': data_type_codes.niistring[self.datatype],
@@ -444,7 +445,7 @@ def _to_xml_element(self):
444445
'Encoding': gifti_encoding_codes.specs[self.encoding],
445446
'Endian': gifti_endian_codes.specs[self.endian],
446447
'ExternalFileName': self.ext_fname,
447-
'ExternalFileOffset': self.ext_offset})
448+
'ExternalFileOffset': str(self.ext_offset)})
448449
for di, dn in enumerate(self.dims):
449450
data_array.attrib['Dim%d' % di] = str(dn)
450451

nibabel/gifti/tests/test_gifti.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33
import warnings
44
import sys
5+
from io import BytesIO
56

67
import numpy as np
78

@@ -12,6 +13,7 @@
1213
GiftiCoordSystem)
1314
from nibabel.gifti.gifti import data_tag
1415
from nibabel.nifti1 import data_type_codes
16+
from nibabel.fileholders import FileHolder
1517

1618
from numpy.testing import (assert_array_almost_equal,
1719
assert_array_equal)
@@ -275,3 +277,114 @@ def test_data_tag_deprecated():
275277
warnings.filterwarnings('once', category=DeprecationWarning)
276278
data_tag(np.array([]), 'ASCII', '%i', 1)
277279
assert_equal(len(w), 1)
280+
281+
282+
def test_gifti_round_trip():
283+
# From section 14.4 in GIFTI Surface Data Format Version 1.0
284+
# (with some adaptations)
285+
286+
test_data = b'''<?xml version="1.0" encoding="UTF-8"?>
287+
<!DOCTYPE GIFTI SYSTEM "http://www.nitrc.org/frs/download.php/1594/gifti.dtd">
288+
<GIFTI
289+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
290+
xsi:noNamespaceSchemaLocation="http://www.nitrc.org/frs/download.php/1303/GIFTI_Caret.xsd"
291+
Version="1.0"
292+
NumberOfDataArrays="2">
293+
<MetaData>
294+
<MD>
295+
<Name><![CDATA[date]]></Name>
296+
<Value><![CDATA[Thu Nov 15 09:05:22 2007]]></Value>
297+
</MD>
298+
</MetaData>
299+
<LabelTable/>
300+
<DataArray Intent="NIFTI_INTENT_POINTSET"
301+
DataType="NIFTI_TYPE_FLOAT32"
302+
ArrayIndexingOrder="RowMajorOrder"
303+
Dimensionality="2"
304+
Dim0="4"
305+
Dim1="3"
306+
Encoding="ASCII"
307+
Endian="LittleEndian"
308+
ExternalFileName=""
309+
ExternalFileOffset="">
310+
<CoordinateSystemTransformMatrix>
311+
<DataSpace><![CDATA[NIFTI_XFORM_TALAIRACH]]></DataSpace>
312+
<TransformedSpace><![CDATA[NIFTI_XFORM_TALAIRACH]]></TransformedSpace>
313+
<MatrixData>
314+
1.000000 0.000000 0.000000 0.000000
315+
0.000000 1.000000 0.000000 0.000000
316+
0.000000 0.000000 1.000000 0.000000
317+
0.000000 0.000000 0.000000 1.000000
318+
</MatrixData>
319+
</CoordinateSystemTransformMatrix>
320+
<Data>
321+
10.5 0 0
322+
0 20.5 0
323+
0 0 30.5
324+
0 0 0
325+
</Data>
326+
</DataArray>
327+
<DataArray Intent="NIFTI_INTENT_TRIANGLE"
328+
DataType="NIFTI_TYPE_INT32"
329+
ArrayIndexingOrder="RowMajorOrder"
330+
Dimensionality="2"
331+
Dim0="4"
332+
Dim1="3"
333+
Encoding="ASCII"
334+
Endian="LittleEndian"
335+
ExternalFileName="" ExternalFileOffset="">
336+
<Data>
337+
0 1 2
338+
1 2 3
339+
0 1 3
340+
0 2 3
341+
</Data>
342+
</DataArray>
343+
</GIFTI>'''
344+
345+
exp_verts = np.zeros((4, 3))
346+
exp_verts[0, 0] = 10.5
347+
exp_verts[1, 1] = 20.5
348+
exp_verts[2, 2] = 30.5
349+
exp_faces = np.asarray([[0, 1, 2], [1, 2, 3], [0, 1, 3], [0, 2, 3]],
350+
dtype=np.int32)
351+
352+
def _check_gifti(gio):
353+
vertices = gio.get_arrays_from_intent('NIFTI_INTENT_POINTSET')[0].data
354+
faces = gio.get_arrays_from_intent('NIFTI_INTENT_TRIANGLE')[0].data
355+
assert_array_equal(vertices, exp_verts)
356+
assert_array_equal(faces, exp_faces)
357+
358+
bio = BytesIO()
359+
fmap = dict(image=FileHolder(fileobj=bio))
360+
361+
bio.write(test_data)
362+
bio.seek(0)
363+
gio = GiftiImage.from_file_map(fmap)
364+
_check_gifti(gio)
365+
# Write and read again
366+
bio.seek(0)
367+
gio.to_file_map(fmap)
368+
bio.seek(0)
369+
gio2 = GiftiImage.from_file_map(fmap)
370+
_check_gifti(gio2)
371+
372+
373+
def test_data_array_round_trip():
374+
# Test valid XML generated from new in-memory array
375+
# See: https://github.com/nipy/nibabel/issues/469
376+
verts = np.zeros((4, 3), np.float32)
377+
verts[0, 0] = 10.5
378+
verts[1, 1] = 20.5
379+
verts[2, 2] = 30.5
380+
381+
vertices = GiftiDataArray(verts)
382+
img = GiftiImage()
383+
img.add_gifti_data_array(vertices)
384+
bio = BytesIO()
385+
fmap = dict(image=FileHolder(fileobj=bio))
386+
bio.write(img.to_xml())
387+
bio.seek(0)
388+
gio = GiftiImage.from_file_map(fmap)
389+
vertices = gio.darrays[0].data
390+
assert_array_equal(vertices, verts)

0 commit comments

Comments
 (0)