Skip to content

Commit 4d8a6c0

Browse files
committed
Merge pull request #264 from matthew-brett/parrec-fix-refactor
MRG: some refactoring of PARREC API Add fake truncated file for testing, and test. Add permit_truncated as header attribute. Fix header copy to use permit_truncated. Add file loading API methods with PARREC-specific parameters. Make PARREC-specific parameters keyword-only.
2 parents 863d3d3 + 2040239 commit 4d8a6c0

File tree

8 files changed

+328
-11
lines changed

8 files changed

+328
-11
lines changed

bin/parrec2nii

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ def proc_file(infile, opts):
138138
# load the PAR header and data
139139
scaling = None if opts.scaling == 'off' else opts.scaling
140140
infile = fname_ext_ul_case(infile)
141-
pr_img = pr.load(infile, opts.permit_truncated, scaling)
141+
pr_img = pr.load(infile,
142+
permit_truncated=opts.permit_truncated,
143+
scaling=scaling)
142144
pr_hdr = pr_img.header
143145
raw_data = pr_img.dataobj.get_unscaled()
144146
affine = pr_hdr.get_affine(origin=opts.origin)

nibabel/keywordonly.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
""" Decorator for labeling keyword arguments as keyword only
2+
"""
3+
4+
from functools import wraps
5+
6+
def kw_only_func(n):
7+
""" Return function decorator enforcing maximum of `n` positional arguments
8+
"""
9+
def decorator(func):
10+
@wraps(func)
11+
def wrapper(*args, **kwargs):
12+
if len(args) > n:
13+
raise TypeError(
14+
'{0} takes at most {1} positional argument{2}'.format(
15+
func.__name__, n, 's' if n > 1 else ''))
16+
return func(*args, **kwargs)
17+
return wrapper
18+
return decorator
19+
20+
21+
def kw_only_meth(n):
22+
""" Return method decorator enforcing maximum of `n` positional arguments
23+
24+
The method has at least one positional argument ``self`` or ``cls``; allow
25+
for that.
26+
"""
27+
return kw_only_func(n+1)

nibabel/parrec.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
from copy import deepcopy
8383
import re
8484

85+
from .keywordonly import kw_only_meth
8586
from .spatialimages import SpatialImage, Header
8687
from .eulerangles import euler2mat
8788
from .volumeutils import Recoder, array_from_file, BinOpener
@@ -462,6 +463,18 @@ def _data_from_rec(rec_fileobj, in_shape, dtype, slice_indices, out_shape,
462463

463464
class PARRECArrayProxy(object):
464465
def __init__(self, file_like, header, scaling):
466+
""" Initialize PARREC array proxy
467+
468+
Parameters
469+
----------
470+
file_like : file-like object
471+
Filename or object implementing ``read, seek, tell``
472+
header : PARRECHeader instance
473+
Implementing ``get_data_shape, get_data_dtype``,
474+
``get_sorted_slice_indices``, ``get_data_scaling``
475+
scaling : {'fp', 'dv'}
476+
Type of scaling to use - see header ``get_data_scaling`` method.
477+
"""
465478
self.file_like = file_like
466479
# Copies of values needed to read array
467480
self._shape = header.get_data_shape()
@@ -512,6 +525,7 @@ def __init__(self, info, image_defs, permit_truncated=False):
512525
"""
513526
self.general_info = info.copy()
514527
self.image_defs = image_defs.copy()
528+
self.permit_truncated = permit_truncated
515529
_truncation_checks(info, image_defs, permit_truncated)
516530
# charge with basic properties to be able to use base class
517531
# functionality
@@ -538,7 +552,8 @@ def from_fileobj(klass, fileobj, permit_truncated=False):
538552

539553
def copy(self):
540554
return PARRECHeader(deepcopy(self.general_info),
541-
self.image_defs.copy())
555+
self.image_defs.copy(),
556+
self.permit_truncated)
542557

543558
def as_analyze_map(self):
544559
"""Convert PAR parameters to NIFTI1 format"""
@@ -886,7 +901,8 @@ class PARRECImage(SpatialImage):
886901
ImageArrayProxy = PARRECArrayProxy
887902

888903
@classmethod
889-
def from_file_map(klass, file_map, permit_truncated, scaling):
904+
@kw_only_meth(1)
905+
def from_file_map(klass, file_map, permit_truncated=False, scaling='dv'):
890906
pt = permit_truncated
891907
with file_map['header'].get_prepare_fileobj('rt') as hdr_fobj:
892908
hdr = klass.header_class.from_fileobj(hdr_fobj,
@@ -896,8 +912,15 @@ def from_file_map(klass, file_map, permit_truncated, scaling):
896912
return klass(data, hdr.get_affine(), header=hdr, extra=None,
897913
file_map=file_map)
898914

915+
@classmethod
916+
@kw_only_meth(1)
917+
def from_filename(klass, filename, permit_truncated=False, scaling='dv'):
918+
file_map = klass.filespec_to_file_map(filename)
919+
return klass.from_file_map(file_map,
920+
permit_truncated=permit_truncated,
921+
scaling=scaling)
922+
923+
load = from_filename
924+
899925

900-
def load(filename, permit_truncated=False, scaling='dv'):
901-
file_map = PARRECImage.filespec_to_file_map(filename)
902-
return PARRECImage.from_file_map(file_map, permit_truncated, scaling)
903-
load.__doc__ = PARRECImage.load.__doc__
926+
load = PARRECImage.load

nibabel/tests/data/README.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
##################
2+
Nibabel data files
3+
##################
4+
5+
``phantom_truncated.REC`` is a copy of ``phantom_EPI_asc_CLEAR_2_1.REC``.
6+
7+
``phantom_truncated.PAR`` is a slightly edited copy of
8+
``phantom_EPI_asc_CLEAR_2_1.PAR``.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# === DATA DESCRIPTION FILE ======================================================
2+
#
3+
# CAUTION - Investigational device.
4+
# Limited by Federal Law to investigational use.
5+
#
6+
# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1
7+
#
8+
# CLINICAL TRYOUT Research image export tool V4.2
9+
#
10+
# === GENERAL INFORMATION ========================================================
11+
#
12+
. Patient name : phantom
13+
. Examination name : Konvertertest
14+
. Protocol name : EPI_asc CLEAR
15+
. Examination date/time : 2014.02.14 / 09:00:57
16+
. Series Type : Image MRSERIES
17+
. Acquisition nr : 2
18+
. Reconstruction nr : 1
19+
. Scan Duration [sec] : 14
20+
. Max. number of cardiac phases : 1
21+
. Max. number of echoes : 1
22+
. Max. number of slices/locations : 9
23+
. Max. number of dynamics : 4
24+
. Max. number of mixes : 1
25+
. Patient position : Head First Supine
26+
. Preparation direction : Anterior-Posterior
27+
. Technique : FEEPI
28+
. Scan resolution (x, y) : 64 39
29+
. Scan mode : MS
30+
. Repetition time [ms] : 2000.000
31+
. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000
32+
. Water Fat shift [pixels] : 11.050
33+
. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000
34+
. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032
35+
. Flow compensation <0=no 1=yes> ? : 0
36+
. Presaturation <0=no 1=yes> ? : 0
37+
. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000
38+
. MTC <0=no 1=yes> ? : 0
39+
. SPIR <0=no 1=yes> ? : 1
40+
. EPI factor <0,1=no EPI> : 39
41+
. Dynamic scan <0=no 1=yes> ? : 1
42+
. Diffusion <0=no 1=yes> ? : 0
43+
. Diffusion echo time [ms] : 0.0000
44+
. Max. number of diffusion values : 1
45+
. Max. number of gradient orients : 1
46+
. Number of label types <0=no ASL> : 0
47+
#
48+
# === PIXEL VALUES =============================================================
49+
# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console
50+
# RS = rescale slope, RI = rescale intercept, SS = scale slope
51+
# DV = PV * RS + RI FP = DV / (RS * SS)
52+
#
53+
# === IMAGE INFORMATION DEFINITION =============================================
54+
# The rest of this file contains ONE line per image, this line contains the following information:
55+
#
56+
# slice number (integer)
57+
# echo number (integer)
58+
# dynamic scan number (integer)
59+
# cardiac phase number (integer)
60+
# image_type_mr (integer)
61+
# scanning sequence (integer)
62+
# index in REC file (in images) (integer)
63+
# image pixel size (in bits) (integer)
64+
# scan percentage (integer)
65+
# recon resolution (x y) (2*integer)
66+
# rescale intercept (float)
67+
# rescale slope (float)
68+
# scale slope (float)
69+
# window center (integer)
70+
# window width (integer)
71+
# image angulation (ap,fh,rl in degrees ) (3*float)
72+
# image offcentre (ap,fh,rl in mm ) (3*float)
73+
# slice thickness (in mm ) (float)
74+
# slice gap (in mm ) (float)
75+
# image_display_orientation (integer)
76+
# slice orientation ( TRA/SAG/COR ) (integer)
77+
# fmri_status_indication (integer)
78+
# image_type_ed_es (end diast/end syst) (integer)
79+
# pixel spacing (x,y) (in mm) (2*float)
80+
# echo_time (float)
81+
# dyn_scan_begin_time (float)
82+
# trigger_time (float)
83+
# diffusion_b_factor (float)
84+
# number of averages (integer)
85+
# image_flip_angle (in degrees) (float)
86+
# cardiac frequency (bpm) (integer)
87+
# minimum RR-interval (in ms) (integer)
88+
# maximum RR-interval (in ms) (integer)
89+
# TURBO factor <0=no turbo> (integer)
90+
# Inversion delay (in ms) (float)
91+
# diffusion b value number (imagekey!) (integer)
92+
# gradient orientation number (imagekey!) (integer)
93+
# contrast type (string)
94+
# diffusion anisotropy type (string)
95+
# diffusion (ap, fh, rl) (3*float)
96+
# label type (ASL) (imagekey!) (integer)
97+
#
98+
# === IMAGE INFORMATION ==========================================================
99+
# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty
100+
101+
1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
102+
2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
103+
3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
104+
4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
105+
5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
106+
6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
107+
7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
108+
8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
109+
9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
110+
1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
111+
2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
112+
3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
113+
4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
114+
5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
115+
6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
116+
7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
117+
8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
118+
9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
119+
1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
120+
2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
121+
3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
122+
4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
123+
5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
124+
6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
125+
7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
126+
8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
127+
9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1
128+
129+
# === END OF DATA DESCRIPTION FILE ===============================================
216 KB
Binary file not shown.

nibabel/tests/test_keywordonly.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
""" Test kw_only decorators """
2+
3+
from ..keywordonly import kw_only_func, kw_only_meth
4+
5+
from nose.tools import assert_true, assert_false, assert_equal, assert_raises
6+
7+
8+
def test_kw_only_func():
9+
# Test decorator
10+
def func(an_arg):
11+
"My docstring"
12+
return an_arg
13+
assert_equal(func(1), 1)
14+
assert_raises(TypeError, func, 1, 2)
15+
dec_func = kw_only_func(1)(func)
16+
assert_equal(dec_func(1), 1)
17+
assert_raises(TypeError, dec_func, 1, 2)
18+
assert_raises(TypeError, dec_func, 1, akeyarg=3)
19+
assert_equal(dec_func.__doc__, 'My docstring')
20+
@kw_only_func(1)
21+
def kw_func(an_arg, a_kwarg='thing'):
22+
"Another docstring"
23+
return an_arg, a_kwarg
24+
assert_equal(kw_func(1), (1, 'thing'))
25+
assert_raises(TypeError, kw_func, 1, 2)
26+
assert_equal(kw_func(1, a_kwarg=2), (1, 2))
27+
assert_raises(TypeError, kw_func, 1, akeyarg=3)
28+
assert_equal(kw_func.__doc__, 'Another docstring')
29+
class C(object):
30+
@kw_only_meth(1)
31+
def kw_meth(self, an_arg, a_kwarg='thing'):
32+
"Method docstring"
33+
return an_arg, a_kwarg
34+
c = C()
35+
assert_equal(c.kw_meth(1), (1, 'thing'))
36+
assert_raises(TypeError, c.kw_meth, 1, 2)
37+
assert_equal(c.kw_meth(1, a_kwarg=2), (1, 2))
38+
assert_raises(TypeError, c.kw_meth, 1, akeyarg=3)
39+
assert_equal(c.kw_meth.__doc__, 'Method docstring')

0 commit comments

Comments
 (0)