diff --git a/nibabel/gifti/__init__.py b/nibabel/gifti/__init__.py
index 55b23e8cd0..c9bf85b3a0 100644
--- a/nibabel/gifti/__init__.py
+++ b/nibabel/gifti/__init__.py
@@ -19,4 +19,4 @@
from .giftiio import read, write
from .gifti import (GiftiMetaData, GiftiNVPairs, GiftiLabelTable, GiftiLabel,
- GiftiCoordSystem, data_tag, GiftiDataArray, GiftiImage)
+ GiftiCoordSystem, GiftiDataArray, GiftiImage)
diff --git a/nibabel/gifti/gifti.py b/nibabel/gifti/gifti.py
index c472d6eff7..071f2c3968 100644
--- a/nibabel/gifti/gifti.py
+++ b/nibabel/gifti/gifti.py
@@ -12,6 +12,7 @@
import numpy as np
+from .. import xmlutils as xml
from ..nifti1 import data_type_codes, xform_codes, intent_codes
from .util import (array_index_order_codes, gifti_encoding_codes,
gifti_endian_codes, KIND2FMT)
@@ -22,7 +23,7 @@
import base64
-class GiftiMetaData(object):
+class GiftiMetaData(xml.XmlSerializable):
""" A list of GiftiNVPairs in stored in
the list self.data """
def __init__(self, nvpair=None):
@@ -50,18 +51,15 @@ def metadata(self):
self.data_as_dict[ele.name] = ele.value
return self.data_as_dict
- def to_xml(self):
- if len(self.data) == 0:
- return "\n"
- res = "\n"
+ def _to_xml_element(self):
+ metadata = xml.Element('MetaData')
for ele in self.data:
- nvpair = """
-\t
-\t
-\n""" % (ele.name, ele.value)
- res = res + nvpair
- res = res + "\n"
- return res
+ md = xml.SubElement(metadata, 'MD')
+ name = xml.SubElement(md, 'Name')
+ value = xml.SubElement(md, 'Value')
+ name.text = ele.name
+ value.text = ele.value
+ return metadata
def print_summary(self):
print(self.metadata)
@@ -77,7 +75,7 @@ def __init__(self, name='', value=''):
self.value = value
-class GiftiLabelTable(object):
+class GiftiLabelTable(xml.XmlSerializable):
def __init__(self):
self.labels = []
@@ -88,31 +86,22 @@ def get_labels_as_dict(self):
self.labels_as_dict[ele.key] = ele.label
return self.labels_as_dict
- def to_xml(self):
- if len(self.labels) == 0:
- return "\n"
- res = "\n"
+ def _to_xml_element(self):
+ labeltable = xml.Element('LabelTable')
for ele in self.labels:
- col = ''
- if not ele.red is None:
- col += ' Red="%s"' % str(ele.red)
- if not ele.green is None:
- col += ' Green="%s"' % str(ele.green)
- if not ele.blue is None:
- col += ' Blue="%s"' % str(ele.blue)
- if not ele.alpha is None:
- col += ' Alpha="%s"' % str(ele.alpha)
- lab = """\t\n""" % \
- (str(ele.key), col, ele.label)
- res = res + lab
- res = res + "\n"
- return res
+ label = xml.SubElement(labeltable, 'Label')
+ label.attrib['Key'] = str(ele.key)
+ label.text = ele.label
+ for attr in ['Red', 'Green', 'Blue', 'Alpha']:
+ if getattr(ele, attr.lower(), None) is not None:
+ label.attrib[attr] = str(getattr(ele, attr.lower()))
+ return labeltable
def print_summary(self):
print(self.get_labels_as_dict())
-class GiftiLabel(object):
+class GiftiLabel(xml.XmlSerializable):
key = int
label = str
# rgba
@@ -165,7 +154,7 @@ def _arr2txt(arr, elem_fmt):
return '\n'.join(fmt % tuple(row) for row in arr)
-class GiftiCoordSystem(object):
+class GiftiCoordSystem(xml.XmlSerializable):
dataspace = int
xformspace = int
xform = np.ndarray # 4x4 numpy array
@@ -179,19 +168,16 @@ def __init__(self, dataspace=0, xformspace=0, xform=None):
else:
self.xform = xform
- def to_xml(self):
- if self.xform is None:
- return "\n"
- res = ("""
-\t
-\t\n"""
- % (xform_codes.niistring[self.dataspace],
- xform_codes.niistring[self.xformspace]))
- res = res + "\n"
- res += _arr2txt(self.xform, '%10.6f')
- res = res + "\n"
- res = res + "\n"
- return res
+ def _to_xml_element(self):
+ coord_xform = xml.Element('CoordinateSystemTransformMatrix')
+ if self.xform is not None:
+ dataspace = xml.SubElement(coord_xform, 'DataSpace')
+ dataspace.text = xform_codes.niistring[self.dataspace]
+ xformed_space = xml.SubElement(coord_xform, 'TransformedSpace')
+ xformed_space.text = xform_codes.niistring[self.xformspace]
+ matrix_data = xml.SubElement(coord_xform, 'MatrixData')
+ matrix_data.text = _arr2txt(self.xform, '%10.6f')
+ return coord_xform
def print_summary(self):
print('Dataspace: ', xform_codes.niistring[self.dataspace])
@@ -199,8 +185,20 @@ def print_summary(self):
print('Affine Transformation Matrix: \n', self.xform)
+@np.deprecate_with_doc("This is an internal API that will be discontinued.")
def data_tag(dataarray, encoding, datatype, ordering):
- """ Creates the data tag depending on the required encoding """
+ class DataTag(xml.XmlSerializable):
+ def __init__(self, *args):
+ self.args = args
+ def _to_xml_element(self):
+ return _data_tag_element(*self.args)
+
+ return DataTag(dataarray, encoding, datatype, ordering).to_xml()
+
+
+def _data_tag_element(dataarray, encoding, datatype, ordering):
+ """ Creates the data tag depending on the required encoding,
+ returns as XML element"""
import zlib
ord = array_index_order_codes.npcode[ordering]
enclabel = gifti_encoding_codes.label[encoding]
@@ -215,10 +213,13 @@ def data_tag(dataarray, encoding, datatype, ordering):
raise NotImplementedError("In what format are the external files?")
else:
da = ''
- return "" + da + "\n"
+
+ data = xml.Element('Data')
+ data.text = da
+ return data
-class GiftiDataArray(object):
+class GiftiDataArray(xml.XmlSerializable):
# These are for documentation only; we don't use these class variables
intent = int
@@ -299,26 +300,37 @@ def from_array(klass,
cda.meta = GiftiMetaData.from_dict(meta)
return cda
- def to_xml(self):
+ def _to_xml_element(self):
# fix endianness to machine endianness
self.endian = gifti_endian_codes.code[sys.byteorder]
- result = ""
- result += self.to_xml_open()
- # write metadata
- if not self.meta is None:
- result += self.meta.to_xml()
- # write coord sys
- if not self.coordsys is None:
- result += self.coordsys.to_xml()
+
+ data_array = xml.Element('DataArray', attrib={
+ 'Intent': intent_codes.niistring[self.intent],
+ 'DataType': data_type_codes.niistring[self.datatype],
+ 'ArrayIndexingOrder': array_index_order_codes.label[self.ind_ord],
+ 'Dimensionality': str(self.num_dim),
+ 'Encoding': gifti_encoding_codes.specs[self.encoding],
+ 'Endian': gifti_endian_codes.specs[self.endian],
+ 'ExternalFileName': self.ext_fname,
+ 'ExternalFileOffset': self.ext_offset})
+ for di, dn in enumerate(self.dims):
+ data_array.attrib['Dim%d' % di] = str(dn)
+
+ if self.meta is not None:
+ data_array.append(self.meta._to_xml_element())
+ if self.coordsys is not None:
+ data_array.append(self.coordsys._to_xml_element())
# write data array depending on the encoding
dt_kind = data_type_codes.dtype[self.datatype].kind
- result += data_tag(self.data,
- gifti_encoding_codes.specs[self.encoding],
- KIND2FMT[dt_kind],
- self.ind_ord)
- result = result + self.to_xml_close()
- return result
+ data_array.append(
+ _data_tag_element(self.data,
+ gifti_encoding_codes.specs[self.encoding],
+ KIND2FMT[dt_kind],
+ self.ind_ord))
+ return data_array
+
+ @np.deprecate_with_doc("Use the to_xml() function instead.")
def to_xml_open(self):
out = """\n"
@@ -371,7 +384,8 @@ def metadata(self):
return self.meta.metadata
-class GiftiImage(object):
+class GiftiImage(xml.XmlSerializable):
+
def __init__(self, meta=None, labeltable=None, darrays=None,
version="1.0"):
if darrays is None:
@@ -497,17 +511,21 @@ def print_summary(self):
print(da.print_summary())
print('----end----')
- def to_xml(self):
+
+ def _to_xml_element(self):
+ GIFTI = xml.Element('GIFTI', attrib={
+ 'Version': self.version,
+ 'NumberOfDataArrays': str(self.numDA)})
+ if self.meta is not None:
+ GIFTI.append(self.meta._to_xml_element())
+ if self.labeltable is not None:
+ GIFTI.append(self.labeltable._to_xml_element())
+ for dar in self.darrays:
+ GIFTI.append(dar._to_xml_element())
+ return GIFTI
+
+ def to_xml(self, enc='utf-8'):
""" Return XML corresponding to image content """
- res = """
+ return b"""
-\n""" % (self.version,
- str(self.numDA))
- if not self.meta is None:
- res += self.meta.to_xml()
- if not self.labeltable is None:
- res += self.labeltable.to_xml()
- for dar in self.darrays:
- res += dar.to_xml()
- res += ""
- return res
+""" + xml.XmlSerializable.to_xml(self, enc)
diff --git a/nibabel/gifti/giftiio.py b/nibabel/gifti/giftiio.py
index ee68bf2629..2660267021 100644
--- a/nibabel/gifti/giftiio.py
+++ b/nibabel/gifti/giftiio.py
@@ -80,5 +80,5 @@ def write(image, filename):
The Gifti file is stored in endian convention of the current machine.
"""
# Our giftis are always utf-8 encoded - see GiftiImage.to_xml
- with codecs.open(filename, 'wb', encoding='utf-8') as f:
+ with open(filename, 'wb') as f:
f.write(image.to_xml())
diff --git a/nibabel/gifti/tests/test_gifti.py b/nibabel/gifti/tests/test_gifti.py
index cc9a09da12..9433c90219 100644
--- a/nibabel/gifti/tests/test_gifti.py
+++ b/nibabel/gifti/tests/test_gifti.py
@@ -4,17 +4,18 @@
import numpy as np
-from nibabel.gifti import giftiio
+from nibabel.externals.six import string_types
+from nibabel.gifti import (GiftiImage, GiftiDataArray, GiftiLabel,
+ GiftiLabelTable, GiftiMetaData, giftiio)
+from nibabel.gifti.gifti import data_tag
+from nibabel.nifti1 import data_type_codes, intent_codes
-from .test_giftiio import (DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4,
- DATA_FILE5, DATA_FILE6)
-from ..gifti import (GiftiImage, GiftiDataArray, GiftiLabel, GiftiLabelTable,
- GiftiMetaData)
-from ...nifti1 import data_type_codes, intent_codes
-from ...testing import clear_and_catch_warnings
from numpy.testing import (assert_array_almost_equal,
assert_array_equal)
from nose.tools import (assert_true, assert_false, assert_equal, assert_raises)
+from nibabel.testing import clear_and_catch_warnings
+from .test_giftiio import (DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4,
+ DATA_FILE5, DATA_FILE6)
def test_gifti_image():
@@ -70,6 +71,17 @@ def test_dataarray():
da = GiftiDataArray.from_array(bs_arr, 'triangle')
assert_equal(da.datatype, data_type_codes[arr.dtype])
+ # Smoke test on deprecated functions
+ da = GiftiDataArray.from_array(np.ones((1,)), 'triangle')
+ with clear_and_catch_warnings() as w:
+ warnings.filterwarnings('always', category=DeprecationWarning)
+ assert_true(isinstance(da.to_xml_open(), string_types))
+ assert_equal(len(w), 1)
+ with clear_and_catch_warnings() as w:
+ warnings.filterwarnings('once', category=DeprecationWarning)
+ assert_true(isinstance(da.to_xml_close(), string_types))
+ assert_equal(len(w), 1)
+
def test_labeltable():
img = GiftiImage()
@@ -163,3 +175,11 @@ def assign_labeltable(val):
def assign_metadata(val):
img.meta = val
assert_raises(TypeError, assign_metadata, 'not-a-meta')
+
+
+def test_data_tag_deprecated():
+ img = GiftiImage()
+ with clear_and_catch_warnings() as w:
+ warnings.filterwarnings('once', category=DeprecationWarning)
+ data_tag(np.array([]), 'ASCII', '%i', 1)
+ assert_equal(len(w), 1)
diff --git a/nibabel/xmlutils.py b/nibabel/xmlutils.py
new file mode 100644
index 0000000000..fa23466006
--- /dev/null
+++ b/nibabel/xmlutils.py
@@ -0,0 +1,25 @@
+# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
+# vi: set ft=python sts=4 ts=4 sw=4 et:
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+#
+# See COPYING file distributed along with the NiBabel package for the
+# copyright and license terms.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+"""
+Thin layer around xml.etree.ElementTree, to abstract nibabel xml support.
+"""
+from xml.etree.ElementTree import Element, SubElement, tostring
+
+
+class XmlSerializable(object):
+ """ Basic interface for serializing an object to xml"""
+
+ def _to_xml_element(self):
+ """ Output should be a xml.etree.ElementTree.Element"""
+ raise NotImplementedError()
+
+ def to_xml(self, enc='utf-8'):
+ """ Output should be an xml string with the given encoding.
+ (default: utf-8)"""
+ return tostring(self._to_xml_element(), enc)