25
25
26
26
See :mod:`nibabel.tests.test_proxy_api` for proxy API conformance checks.
27
27
"""
28
- import warnings
29
-
30
28
import numpy as np
31
29
30
+ from .deprecated import deprecate_with_version
32
31
from .volumeutils import array_from_file , apply_read_scaling
33
32
from .fileslice import fileslice
34
33
from .keywordonly import kw_only_meth
@@ -45,14 +44,17 @@ class ArrayProxy(object):
45
44
of the numpy dtypes, starting at a given file position ``offset`` with
46
45
single ``slope`` and ``intercept`` scaling to produce output values.
47
46
48
- The class ``__init__`` requires a ``header`` object with methods:
47
+ The class ``__init__`` requires a spec which defines how the data will be
48
+ read and rescaled. The spec may be a tuple of length 2 - 5, containing the
49
+ shape, storage dtype, offset, slope and intercept, or a ``header`` object
50
+ with methods:
49
51
50
52
* get_data_shape
51
53
* get_data_dtype
52
54
* get_data_offset
53
55
* get_slope_inter
54
56
55
- The header should also have a 'copy' method. This requirement will go away
57
+ A header should also have a 'copy' method. This requirement will go away
56
58
when the deprecated 'header' propoerty goes away.
57
59
58
60
This implementation allows us to deal with Analyze and its variants,
@@ -64,17 +66,32 @@ class ArrayProxy(object):
64
66
"""
65
67
# Assume Fortran array memory layout
66
68
order = 'F'
69
+ _header = None
67
70
68
71
@kw_only_meth (2 )
69
- def __init__ (self , file_like , header , mmap = True ):
72
+ def __init__ (self , file_like , spec , mmap = True ):
70
73
""" Initialize array proxy instance
71
74
72
75
Parameters
73
76
----------
74
77
file_like : object
75
78
File-like object or filename. If file-like object, should implement
76
79
at least ``read`` and ``seek``.
77
- header : object
80
+ spec : object or tuple
81
+ Tuple must have length 2-5, with the following values.
82
+ - shape : tuple
83
+ tuple of ints describing shape of data
84
+ - storage_dtype : dtype specifier
85
+ dtype of array inside proxied file, or input to ``numpy.dtype``
86
+ to specify array dtype
87
+ - offset : int
88
+ Offset, in bytes, of data array from start of file
89
+ (default: 0)
90
+ - slope : float
91
+ Scaling factor for resulting data (default: 1.0)
92
+ - inter : float
93
+ Intercept for rescaled data (default: 0.0)
94
+ OR
78
95
Header object implementing ``get_data_shape``, ``get_data_dtype``,
79
96
``get_data_offset``, ``get_slope_inter``
80
97
mmap : {True, False, 'c', 'r'}, optional, keyword only
@@ -90,22 +107,30 @@ def __init__(self, file_like, header, mmap=True):
90
107
if mmap not in (True , False , 'c' , 'r' ):
91
108
raise ValueError ("mmap should be one of {True, False, 'c', 'r'}" )
92
109
self .file_like = file_like
110
+ if hasattr (spec , 'get_data_shape' ):
111
+ slope , inter = spec .get_slope_inter ()
112
+ par = (spec .get_data_shape (),
113
+ spec .get_data_dtype (),
114
+ spec .get_data_offset (),
115
+ 1. if slope is None else slope ,
116
+ 0. if inter is None else inter )
117
+ # Reference to original header; we will remove this soon
118
+ self ._header = spec .copy ()
119
+ elif 2 <= len (spec ) <= 5 :
120
+ optional = (0 , 1. , 0. )
121
+ par = spec + optional [len (spec ) - 2 :]
122
+ else :
123
+ raise TypeError ('spec must be tuple of length 2-5 or header object' )
124
+
93
125
# Copies of values needed to read array
94
- self ._shape = header .get_data_shape ()
95
- self ._dtype = header .get_data_dtype ()
96
- self ._offset = header .get_data_offset ()
97
- self ._slope , self ._inter = header .get_slope_inter ()
98
- self ._slope = 1.0 if self ._slope is None else self ._slope
99
- self ._inter = 0.0 if self ._inter is None else self ._inter
126
+ self ._shape , self ._dtype , self ._offset , self ._slope , self ._inter = par
127
+ # Permit any specifier that can be interpreted as a numpy dtype
128
+ self ._dtype = np .dtype (self ._dtype )
100
129
self ._mmap = mmap
101
- # Reference to original header; we will remove this soon
102
- self ._header = header .copy ()
103
130
104
131
@property
132
+ @deprecate_with_version ('ArrayProxy.header deprecated' , '2.2' , '3.0' )
105
133
def header (self ):
106
- warnings .warn ('We will remove the header property from proxies soon' ,
107
- FutureWarning ,
108
- stacklevel = 2 )
109
134
return self ._header
110
135
111
136
@property
@@ -162,6 +187,29 @@ def __getitem__(self, slicer):
162
187
# Upcast as necessary for big slopes, intercepts
163
188
return apply_read_scaling (raw_data , self ._slope , self ._inter )
164
189
190
+ def reshape (self , shape ):
191
+ ''' Return an ArrayProxy with a new shape, without modifying data '''
192
+ size = np .prod (self ._shape )
193
+
194
+ # Calculate new shape if not fully specified
195
+ from operator import mul
196
+ from functools import reduce
197
+ n_unknowns = len ([e for e in shape if e == - 1 ])
198
+ if n_unknowns > 1 :
199
+ raise ValueError ("can only specify one unknown dimension" )
200
+ elif n_unknowns == 1 :
201
+ known_size = reduce (mul , shape , - 1 )
202
+ unknown_size = size // known_size
203
+ shape = tuple (unknown_size if e == - 1 else e for e in shape )
204
+
205
+ if np .prod (shape ) != size :
206
+ raise ValueError ("cannot reshape array of size {:d} into shape "
207
+ "{!s}" .format (size , shape ))
208
+ return self .__class__ (file_like = self .file_like ,
209
+ spec = (shape , self ._dtype , self ._offset ,
210
+ self ._slope , self ._inter ),
211
+ mmap = self ._mmap )
212
+
165
213
166
214
def is_proxy (obj ):
167
215
""" Return True if `obj` is an array proxy
0 commit comments