Skip to content

Commit a7c6523

Browse files
committed
Merge pull request #414 from effigies/write_morph
ENH: Add write_morph_data to freesurfer.io The first commit was in the midst of #249 and causing PEP8 warnings. Not otherwise relevant to Cifti, so I'm submitting as its own PR. Fixed the style and brought a little more in line with freesurfer.io functions.
2 parents b66d63b + 949768a commit a7c6523

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

nibabel/freesurfer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Reading functions for freesurfer files
22
"""
33

4-
from .io import read_geometry, read_morph_data, \
4+
from .io import read_geometry, read_morph_data, write_morph_data, \
55
read_annot, read_label, write_geometry, write_annot
66
from .mghformat import load, save, MGHImage

nibabel/freesurfer/io.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
from .. externals.six.moves import xrange
9+
from ..openers import Opener
910

1011

1112
def _fread3(fobj):
@@ -160,6 +161,52 @@ def read_morph_data(filepath):
160161
return curv
161162

162163

164+
def write_morph_data(file_like, values, fnum=0):
165+
"""Write Freesurfer morphometry data `values` to file-like `file_like`
166+
167+
Equivalent to FreeSurfer's `write_curv.m`_
168+
169+
See also:
170+
http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm#CurvNew
171+
172+
.. _write_curv.m: \
173+
https://github.com/neurodebian/freesurfer/blob/debian-sloppy/matlab/write_curv.m
174+
175+
Parameters
176+
----------
177+
file_like : file-like
178+
String containing path of file to be written, or file-like object, open
179+
in binary write (`'wb'` mode, implementing the `write` method)
180+
values : array-like
181+
Surface morphometry values
182+
183+
Shape must be (N,), (N, 1), (1, N) or (N, 1, 1)
184+
fnum : int, optional
185+
Number of faces in the associated surface
186+
"""
187+
magic_bytes = np.array([255, 255, 255], dtype=np.uint8)
188+
189+
vector = np.asarray(values)
190+
vnum = np.prod(vector.shape)
191+
if vector.shape not in ((vnum,), (vnum, 1), (1, vnum), (vnum, 1, 1)):
192+
raise ValueError("Invalid shape: argument values must be a vector")
193+
194+
i4info = np.iinfo('i4')
195+
if vnum > i4info.max:
196+
raise ValueError("Too many values for morphometry file")
197+
if not i4info.min <= fnum <= i4info.max:
198+
raise ValueError("Argument fnum must be between {0} and {1}".format(
199+
i4info.min, i4info.max))
200+
201+
with Opener(file_like, 'wb') as fobj:
202+
fobj.write(magic_bytes)
203+
204+
# vertex count, face count (unused), vals per vertex (only 1 supported)
205+
fobj.write(np.array([vnum, fnum, 1], dtype='>i4'))
206+
207+
fobj.write(vector.astype('>f4'))
208+
209+
163210
def read_annot(filepath, orig_ids=False):
164211
"""Read in a Freesurfer annotation from a .annot file.
165212

nibabel/freesurfer/tests/test_io.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010

1111
from nose.tools import assert_true
1212
import numpy as np
13-
from numpy.testing import assert_equal, dec
13+
from numpy.testing import assert_equal, assert_raises, dec
1414

1515
from .. import (read_geometry, read_morph_data, read_annot, read_label,
16-
write_geometry, write_annot)
16+
write_geometry, write_morph_data, write_annot)
1717

1818
from ...tests.nibabel_data import get_nibabel_data
19+
from ...fileslice import strided_scalar
1920

2021

2122
DATA_SDIR = 'fsaverage'
@@ -92,6 +93,33 @@ def test_morph_data():
9293
curv = read_morph_data(curv_path)
9394
assert_true(-1.0 < curv.min() < 0)
9495
assert_true(0 < curv.max() < 1.0)
96+
with InTemporaryDirectory():
97+
new_path = 'test'
98+
write_morph_data(new_path, curv)
99+
curv2 = read_morph_data(new_path)
100+
assert_equal(curv2, curv)
101+
102+
103+
def test_write_morph_data():
104+
"""Test write_morph_data edge cases"""
105+
values = np.arange(20, dtype='>f4')
106+
okay_shapes = [(20,), (20, 1), (20, 1, 1), (1, 20)]
107+
bad_shapes = [(10, 2), (1, 1, 20, 1, 1)]
108+
big_num = np.iinfo('i4').max + 1
109+
with InTemporaryDirectory():
110+
for shape in okay_shapes:
111+
write_morph_data('test.curv', values.reshape(shape))
112+
# Check ordering is preserved, regardless of shape
113+
assert_equal(values, read_morph_data('test.curv'))
114+
assert_raises(ValueError, write_morph_data, 'test.curv',
115+
np.zeros(shape), big_num)
116+
# Windows 32-bit overflows Python int
117+
if np.dtype(np.int) != np.dtype(np.int32):
118+
assert_raises(ValueError, write_morph_data, 'test.curv',
119+
strided_scalar((big_num,)))
120+
for shape in bad_shapes:
121+
assert_raises(ValueError, write_morph_data, 'test.curv',
122+
values.reshape(shape))
95123

96124

97125
@freesurfer_test

0 commit comments

Comments
 (0)