Skip to content

WIP: add GIFTI to nib.load/save (and enable non-SpatialImage classes in load/save) #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dist/
.tox/
.coverage
.ropeproject/
htmlcov/

# Logs and databases #
######################
Expand Down
78 changes: 74 additions & 4 deletions nibabel/gifti/gifti.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import numpy as np

from .. import xmlutils as xml
from ..filebasedimages import FileBasedImage
from ..nifti1 import data_type_codes, xform_codes, intent_codes
from .util import (array_index_order_codes, gifti_encoding_codes,
gifti_endian_codes, KIND2FMT)
Expand Down Expand Up @@ -190,6 +191,7 @@ def data_tag(dataarray, encoding, datatype, ordering):
class DataTag(xml.XmlSerializable):
def __init__(self, *args):
self.args = args

def _to_xml_element(self):
return _data_tag_element(*self.args)

Expand Down Expand Up @@ -384,10 +386,15 @@ def metadata(self):
return self.meta.metadata


class GiftiImage(xml.XmlSerializable):
class GiftiImage(FileBasedImage, xml.XmlSerializable):
valid_exts = ('.gii',)
files_types = (('image', '.gii'),)

def __init__(self, header=None, extra=None, file_map=None, meta=None,
labeltable=None, darrays=None, version="1.0"):
FileBasedImage.__init__(self, header=header, extra=extra,
file_map=file_map)

def __init__(self, meta=None, labeltable=None, darrays=None,
version="1.0"):
if darrays is None:
darrays = []
if meta is None:
Expand Down Expand Up @@ -511,7 +518,6 @@ def print_summary(self):
print(da.print_summary())
print('----end----')


def _to_xml_element(self):
GIFTI = xml.Element('GIFTI', attrib={
'Version': self.version,
Expand All @@ -529,3 +535,67 @@ def to_xml(self, enc='utf-8'):
return b"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE GIFTI SYSTEM "http://www.nitrc.org/frs/download.php/115/gifti.dtd">
""" + xml.XmlSerializable.to_xml(self, enc)

@classmethod
def from_file_map(klass, file_map):
""" Load a Gifti image from a file_map

Parameters
file_map : string

Returns
-------
img : GiftiImage
Returns a GiftiImage
"""
from .parse_gifti_fast import parse_gifti_file
return parse_gifti_file(
fptr=file_map['image'].get_prepare_fileobj('rb'))

def to_file_map(self, file_map=None):
""" Save the current image to the specified file_map

Parameters
----------
file_map : string

Returns
-------
None

Notes
-----
We write all files with utf-8 encoding, and specify this at the top of
the XML file with the ``encoding`` attribute.

The Gifti spec suggests using the following suffixes to your
filename when saving each specific type of data:

.gii
Generic GIFTI File
.coord.gii
Coordinates
.func.gii
Functional
.label.gii
Labels
.rgba.gii
RGB or RGBA
.shape.gii
Shape
.surf.gii
Surface
.tensor.gii
Tensors
.time.gii
Time Series
.topo.gii
Topology

The Gifti file is stored in endian convention of the current machine.
"""
# Our giftis are always utf-8 encoded - see GiftiImage.to_xml
if file_map is None:
file_map = self.file_map
f = file_map['image'].get_prepare_fileobj('wb')
f.write(self.to_xml())
17 changes: 7 additions & 10 deletions nibabel/gifti/giftiio.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
# Stephan Gerhard, Oktober 2010
##############

import os
import codecs

from .parse_gifti_fast import parse_gifti_file
import numpy as np


@np.deprecate_with_doc("Use nibabel.load() instead.")
def read(filename):
""" Load a Gifti image from a file

Expand All @@ -29,11 +27,11 @@ def read(filename):
img : GiftiImage
Returns a GiftiImage
"""
if not os.path.isfile(filename):
raise IOError("No such file or directory: '%s'" % filename)
return parse_gifti_file(filename)
from ..loadsave import load
return load(filename)


@np.deprecate_with_doc("Use nibabel.save() instead.")
def write(image, filename):
""" Save the current image to a new file

Expand Down Expand Up @@ -79,6 +77,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 open(filename, 'wb') as f:
f.write(image.to_xml())
from ..loadsave import save
return save(image, filename)
47 changes: 28 additions & 19 deletions nibabel/gifti/parse_gifti_fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def pending_data(self):
return not self._char_blocks is None


def parse_gifti_file(fname, buffer_size=None):
def parse_gifti_file(fname=None, fptr=None, buffer_size=None):
""" Parse gifti file named `fname`, return image

Parameters
Expand All @@ -334,28 +334,37 @@ def parse_gifti_file(fname, buffer_size=None):
-------
img : gifti image
"""
assert (fname is not None) + (fptr is not None) == 1, "Specify only fname or fptr, not both"

if fptr is None:
with open(fname, 'rb') as datasource:
return parse_gifti_file(fptr=datasource, buffer_size=buffer_size)
else:
datasource = fptr

if buffer_size is None:
buffer_sz_val = 35000000
else:
buffer_sz_val = buffer_size
with open(fname, 'rb') as datasource:
parser = ParserCreate()
parser.buffer_text = True
try:
parser.buffer_size = buffer_sz_val
except AttributeError:
if not buffer_size is None:
raise ValueError('Cannot set buffer size for parser')
HANDLER_NAMES = ['StartElementHandler',
'EndElementHandler',
'CharacterDataHandler']
out = Outputter()
for name in HANDLER_NAMES:
setattr(parser, name, getattr(out, name))
try:
parser.ParseFile(datasource)
except ExpatError:
print('An expat error occured while parsing the Gifti file.')

parser = ParserCreate()
parser.buffer_text = True
try:
parser.buffer_size = buffer_sz_val
except AttributeError:
if not buffer_size is None:
raise ValueError('Cannot set buffer size for parser')
HANDLER_NAMES = ['StartElementHandler',
'EndElementHandler',
'CharacterDataHandler']
out = Outputter()
for name in HANDLER_NAMES:
setattr(parser, name, getattr(out, name))
try:
parser.ParseFile(datasource)
except ExpatError:
print('An expat error occured while parsing the Gifti file.')

# Reality check for pending data
assert out.pending_data is False
# update filename
Expand Down
9 changes: 5 additions & 4 deletions nibabel/gifti/tests/test_gifti.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

import numpy as np

import nibabel as nib
from nibabel.externals.six import string_types
from nibabel.gifti import (GiftiImage, GiftiDataArray, GiftiLabel,
GiftiLabelTable, GiftiMetaData, giftiio)
GiftiLabelTable, GiftiMetaData)
from nibabel.gifti.gifti import data_tag
from nibabel.nifti1 import data_type_codes, intent_codes

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)
from .test_parse_gifti_fast import (DATA_FILE1, DATA_FILE2, DATA_FILE3,
DATA_FILE4, DATA_FILE5, DATA_FILE6)


def test_gifti_image():
Expand Down Expand Up @@ -142,7 +143,7 @@ def assign_rgba(gl, val):
def test_print_summary():
for fil in [DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4,
DATA_FILE5, DATA_FILE6]:
gimg = giftiio.read(fil)
gimg = nib.load(fil)
gimg.print_summary()


Expand Down
Loading