# 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.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
from __future__ import division, print_function, absolute_import
import sys
import numpy as np
from ..nifti1 import data_type_codes, xform_codes, intent_codes
from .util import (array_index_order_codes, gifti_encoding_codes,
gifti_endian_codes, KIND2FMT)
# {en,de}codestring in deprecated in Python3, but
# {en,de}codebytes not available in Python2.
# Therefore set the proper functions depending on the Python version.
import base64
class GiftiMetaData(object):
""" A list of GiftiNVPairs in stored in
the list self.data """
def __init__(self, nvpair = None):
self.data = []
if not nvpair is None:
self.data.append(nvpair)
@classmethod
def from_dict(klass, data_dict):
meda = klass()
for k,v in data_dict.items():
nv = GiftiNVPairs(k, v)
meda.data.append(nv)
return meda
def get_metadata(self):
""" Returns metadata as dictionary """
self.data_as_dict = {}
for ele in self.data:
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"
for ele in self.data:
nvpair = """
\t
\t\n""" % (ele.name, ele.value)
res = res + nvpair
res = res + "\n"
return res
def print_summary(self):
print(self.get_metadata())
class GiftiNVPairs(object):
name = str
value = str
def __init__(self, name = '', value = ''):
self.name = name
self.value = value
class GiftiLabelTable(object):
def __init__(self):
self.labels = []
def get_labels_as_dict(self):
self.labels_as_dict = {}
for ele in self.labels:
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"
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
def print_summary(self):
print(self.get_labels_as_dict())
class GiftiLabel(object):
key = int
label = str
# rgba
# freesurfer examples seem not to conform
# to datatype "NIFTI_TYPE_RGBA32" because they
# are floats, not unsigned 32-bit integers
red = float
green = float
blue = float
alpha = float
def __init__(self, key = 0, label = '', red = None,\
green = None, blue = None, alpha = None):
self.key = key
self.label = label
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
def get_rgba(self):
""" Returns RGBA as tuple """
return (self.red, self.green, self.blue, self.alpha)
def _arr2txt(arr, elem_fmt):
arr = np.asarray(arr)
assert arr.dtype.names is None
if arr.ndim == 1:
arr = arr[:, None]
fmt = ' '.join([elem_fmt] * arr.shape[1])
return '\n'.join(fmt % tuple(row) for row in arr)
class GiftiCoordSystem(object):
dataspace = int
xformspace = int
xform = np.ndarray # 4x4 numpy array
def __init__(self, dataspace = 0, xformspace = 0, xform = None):
self.dataspace = dataspace
self.xformspace = xformspace
if xform is None:
# create identity matrix
self.xform = np.identity(4)
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 print_summary(self):
print('Dataspace: ', xform_codes.niistring[self.dataspace])
print('XFormSpace: ', xform_codes.niistring[self.xformspace])
print('Affine Transformation Matrix: \n', self.xform)
def data_tag(dataarray, encoding, datatype, ordering):
""" Creates the data tag depending on the required encoding """
import zlib
ord = array_index_order_codes.npcode[ordering]
enclabel = gifti_encoding_codes.label[encoding]
if enclabel == 'ASCII':
da = _arr2txt(dataarray, datatype)
elif enclabel in ('B64BIN', 'B64GZ'):
out = dataarray.tostring(ord)
if enclabel == 'B64GZ':
out = zlib.compress(out)
da = base64.b64encode(out).decode()
elif enclabel == 'External':
raise NotImplementedError("In what format are the external files?")
else:
da = ''
return "" + da +"\n"
class GiftiDataArray(object):
# These are for documentation only; we don't use these class variables
intent = int
datatype = int
ind_ord = int
num_dim = int
dims = list
encoding = int
endian = int
ext_fname = str
ext_offset = str
data = np.ndarray
coordsys = GiftiCoordSystem
meta = GiftiMetaData
def __init__(self, data=None):
self.data = data
self.dims = []
self.meta = GiftiMetaData()
self.coordsys = GiftiCoordSystem()
self.ext_fname = ''
self.ext_offset = ''
@classmethod
def from_array(klass,
darray,
intent,
datatype = None,
encoding = "GIFTI_ENCODING_B64GZ",
endian = sys.byteorder,
coordsys = None,
ordering = "C",
meta = None):
""" Creates a new Gifti data array
Parameters
----------
darray : ndarray
NumPy data array
intent : string
NIFTI intent code, see nifti1.intent_codes
datatype : None or string, optional
NIFTI data type codes, see nifti1.data_type_codes
If None, the datatype of the NumPy array is taken.
encoding : string, optionaal
Encoding of the data, see util.gifti_encoding_codes;
default: GIFTI_ENCODING_B64GZ
endian : string, optional
The Endianness to store the data array. Should correspond to the
machine endianness. default: system byteorder
coordsys : GiftiCoordSystem, optional
If None, a identity transformation is taken.
ordering : string, optional
The ordering of the array. see util.array_index_order_codes;
default: RowMajorOrder - C ordering
meta : None or dict, optional
A dictionary for metadata information. If None, gives empty dict.
Returns
-------
da : instance of our own class
"""
if meta is None:
meta = {}
cda = klass(darray)
cda.num_dim = len(darray.shape)
cda.dims = list(darray.shape)
if datatype == None:
cda.datatype = data_type_codes.code[darray.dtype]
else:
cda.datatype = data_type_codes.code[datatype]
cda.intent = intent_codes.code[intent]
cda.encoding = gifti_encoding_codes.code[encoding]
cda.endian = gifti_endian_codes.code[endian]
if not coordsys is None:
cda.coordsys = coordsys
cda.ind_ord = array_index_order_codes.code[ordering]
cda.meta = GiftiMetaData.from_dict(meta)
return cda
def to_xml(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()
# 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
def to_xml_open(self):
out = """\n"""
di = ""
for i, n in enumerate(self.dims):
di = di + '\tDim%s=\"%s\"\n' % (str(i), str(n))
return out % (intent_codes.niistring[self.intent], \
data_type_codes.niistring[self.datatype], \
array_index_order_codes.label[self.ind_ord], \
str(self.num_dim), \
str(di), \
gifti_encoding_codes.specs[self.encoding], \
gifti_endian_codes.specs[self.endian], \
self.ext_fname,
self.ext_offset,
)
def to_xml_close(self):
return "\n"
def print_summary(self):
print('Intent: ', intent_codes.niistring[self.intent])
print('DataType: ', data_type_codes.niistring[self.datatype])
print('ArrayIndexingOrder: ',
array_index_order_codes.label[self.ind_ord])
print('Dimensionality: ', self.num_dim)
print('Dimensions: ', self.dims)
print('Encoding: ', gifti_encoding_codes.specs[self.encoding])
print('Endian: ', gifti_endian_codes.specs[self.endian])
print('ExternalFileName: ', self.ext_fname)
print('ExternalFileOffset: ', self.ext_offset)
if not self.coordsys == None:
print('----')
print('Coordinate System:')
print(self.coordsys.print_summary())
def get_metadata(self):
""" Returns metadata as dictionary """
return self.meta.get_metadata()
class GiftiImage(object):
numDA = int
version = str
filename = str
def __init__(self, meta = None, labeltable = None, darrays = None,
version = "1.0"):
if darrays is None:
darrays = []
self.darrays = darrays
if meta is None:
self.meta = GiftiMetaData()
else:
self.meta = meta
if labeltable is None:
self.labeltable = GiftiLabelTable()
else:
self.labeltable = labeltable
self.numDA = len(self.darrays)
self.version = version
# @classmethod
# def from_array(cls):
# pass
#def GiftiImage_fromarray(data, intent = GiftiIntentCode.NIFTI_INTENT_NONE, encoding=GiftiEncoding.GIFTI_ENCODING_B64GZ, endian = GiftiEndian.GIFTI_ENDIAN_LITTLE):
# """ Returns a GiftiImage from a Numpy array with a given intent code and
# encoding """
# @classmethod
# def from_vertices_and_triangles(cls):
# pass
# def from_vertices_and_triangles(cls, vertices, triangles, coordsys = None, \
# encoding = GiftiEncoding.GIFTI_ENCODING_B64GZ,\
# endian = GiftiEndian.GIFTI_ENDIAN_LITTLE):
# """ Returns a GiftiImage from two numpy arrays representing the vertices
# and the triangles. Additionally defining the coordinate system and encoding """
def get_labeltable(self):
return self.labeltable
def set_labeltable(self, labeltable):
""" Set the labeltable for this GiftiImage
Parameters
----------
labeltable : GiftiLabelTable
"""
if isinstance(labeltable, GiftiLabelTable):
self.labeltable = labeltable
else:
print("Not a valid GiftiLabelTable instance")
def get_metadata(self):
return self.meta
def set_metadata(self, meta):
""" Set the metadata for this GiftiImage
Parameters
----------
meta : GiftiMetaData
Returns
-------
None
"""
if isinstance(meta, GiftiMetaData):
self.meta = meta
print("New Metadata set. Be aware of changing "
"coordinate transformation!")
else:
print("Not a valid GiftiMetaData instance")
def add_gifti_data_array(self, dataarr):
""" Adds a data array to the GiftiImage
Parameters
----------
dataarr : GiftiDataArray
"""
if isinstance(dataarr, GiftiDataArray):
self.darrays.append(dataarr)
self.numDA += 1
else:
print("dataarr paramater must be of tzpe GiftiDataArray")
def remove_gifti_data_array(self, ith):
""" Removes the ith data array element from the GiftiImage """
self.darrays.pop(ith)
self.numDA -= 1
def remove_gifti_data_array_by_intent(self, intent):
""" Removes all the data arrays with the given intent type """
intent2remove = intent_codes.code[intent]
for dele in self.darrays:
if dele.intent == intent2remove:
self.darrays.remove(dele)
self.numDA -= 1
def getArraysFromIntent(self, intent):
""" Returns a a list of GiftiDataArray elements matching
the given intent """
it = intent_codes.code[intent]
return [x for x in self.darrays if x.intent == it]
def print_summary(self):
print('----start----')
print('Source filename: ', self.filename)
print('Number of data arrays: ', self.numDA)
print('Version: ', self.version)
if not self.meta == None:
print('----')
print('Metadata:')
print(self.meta.print_summary())
if not self.labeltable == None:
print('----')
print('Labeltable:')
print(self.labeltable.print_summary())
for i, da in enumerate(self.darrays):
print('----')
print('DataArray %s:' % i)
print(da.print_summary())
print('----end----')
def to_xml(self):
""" Return XML corresponding to image content """
res = """
\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