2525
2626See :mod:`nibabel.tests.test_proxy_api` for proxy API conformance checks.
2727"""
28- import warnings
29-
3028import numpy as np
3129
30+ from .deprecated import deprecate_with_version
3231from .volumeutils import array_from_file , apply_read_scaling
3332from .fileslice import fileslice
3433from .keywordonly import kw_only_meth
@@ -45,14 +44,17 @@ class ArrayProxy(object):
4544 of the numpy dtypes, starting at a given file position ``offset`` with
4645 single ``slope`` and ``intercept`` scaling to produce output values.
4746
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:
4951
5052 * get_data_shape
5153 * get_data_dtype
5254 * get_data_offset
5355 * get_slope_inter
5456
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
5658 when the deprecated 'header' propoerty goes away.
5759
5860 This implementation allows us to deal with Analyze and its variants,
@@ -64,17 +66,32 @@ class ArrayProxy(object):
6466 """
6567 # Assume Fortran array memory layout
6668 order = 'F'
69+ _header = None
6770
6871 @kw_only_meth (2 )
69- def __init__ (self , file_like , header , mmap = True ):
72+ def __init__ (self , file_like , spec , mmap = True ):
7073 """ Initialize array proxy instance
7174
7275 Parameters
7376 ----------
7477 file_like : object
7578 File-like object or filename. If file-like object, should implement
7679 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
7895 Header object implementing ``get_data_shape``, ``get_data_dtype``,
7996 ``get_data_offset``, ``get_slope_inter``
8097 mmap : {True, False, 'c', 'r'}, optional, keyword only
@@ -90,22 +107,30 @@ def __init__(self, file_like, header, mmap=True):
90107 if mmap not in (True , False , 'c' , 'r' ):
91108 raise ValueError ("mmap should be one of {True, False, 'c', 'r'}" )
92109 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+
93125 # 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 )
100129 self ._mmap = mmap
101- # Reference to original header; we will remove this soon
102- self ._header = header .copy ()
103130
104131 @property
132+ @deprecate_with_version ('ArrayProxy.header deprecated' , '2.2' , '3.0' )
105133 def header (self ):
106- warnings .warn ('We will remove the header property from proxies soon' ,
107- FutureWarning ,
108- stacklevel = 2 )
109134 return self ._header
110135
111136 @property
@@ -162,6 +187,29 @@ def __getitem__(self, slicer):
162187 # Upcast as necessary for big slopes, intercepts
163188 return apply_read_scaling (raw_data , self ._slope , self ._inter )
164189
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+
165213
166214def is_proxy (obj ):
167215 """ Return True if `obj` is an array proxy
0 commit comments