forked from NCAR/pynio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_xarray.py
370 lines (334 loc) · 11.6 KB
/
_xarray.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
#@+leo-ver=4-thin
#@+node:schmidli.20080321230001.1:@thin xarray.py
#@@language python
#@<< xarray documentation >>
#@+node:schmidli.20080321230412:<< xarray documentation >>
"""xarray module
Defines the class _xArray and some helper functions.
_xArray supports multilinear interpolation and masked indexing arrays
in addition to numpy's basic slicing and advanced selection mechanisms.
"""
#@-node:schmidli.20080321230412:<< xarray documentation >>
#@nl
#@<< xarray declarations >>
#@+node:schmidli.20080321230001.2:<< xarray declarations >>
from __future__ import division, print_function
import numpy as N
from numpy import asarray
__version__ = '0.1.1'
__all__ = ['_xArray', '_intp', '_rindex', '_rindex2', '_rline', '__version__']
#@-node:schmidli.20080321230001.2:<< xarray declarations >>
#@nl
#@+others
#@+node:schmidli.20080321230001.3:class _xArray
class _xArray(N.ndarray):
""" Extends numpy's ndarray class to support multilinear
interpolation and masked indexing arrays. In other words the
elements of the selection tuble may contain floating point
numbers and masked arrays.
"""
#@ @+others
#@+node:schmidli.20080321230001.4:__new__
def __new__(subtype, data, dtype=None, copy=True):
"""
Returns an _xArray object.
"""
if isinstance(data, N.ma.MaskedArray):
mask = data.mask
masked = True
fill_value = data._fill_value
data = data.filled(fill_value)
else:
mask = False
masked = False
fill_value = None
arr = N.array(data, dtype=dtype, copy=copy)
shape = arr.shape
ret = N.ndarray.__new__(subtype, shape, arr.dtype, buffer=arr)
ndim = arr.ndim
ret.masked = masked
ret.mask = mask
ret.fill_value = fill_value
return ret
#@-node:schmidli.20080321230001.4:__new__
#@+node:schmidli.20080321230001.5:__array_finalize__
def __array_finalize__(self, obj):
""" """
self.masked = False
self.mask = False
self.fill_value = None
#@-node:schmidli.20080321230001.5:__array_finalize__
#@+node:schmidli.20080321230001.6:__copy__
def __copy__(self):
""" """
ret = N.ndarray.__copy__(self)
ret.masked = self.masked
ret.mask = self.mask
ret.fill_value = self.fill_value
return ret
#@-node:schmidli.20080321230001.6:__copy__
#@+node:schmidli.20080321230001.7:__getitem__
def __getitem__(self, index):
"""x.__getitem__(y) <==> x[y]
"""
ret = _intp(asarray(self), index)
return ret
#@nonl
#@-node:schmidli.20080321230001.7:__getitem__
#@+node:schmidli.20080321230001.8:__str__
def __str__(self):
""" informal string representation """
if self.masked:
r = 'Masked: '
else:
r = ''
r += str(self.__array__())
return r
#@-node:schmidli.20080321230001.8:__str__
#@+node:schmidli.20080321230001.9:__repr__
def __repr__(self):
""" full string representation """
r = '_xArray(' + self.__str__() + ')'
return r
#@-node:schmidli.20080321230001.9:__repr__
#@+node:schmidli.20080321230001.10:getA
def getA(self):
""" base array """
return asarray(self)
A = property(getA)
#@nonl
#@-node:schmidli.20080321230001.10:getA
#@-others
#@nonl
#@-node:schmidli.20080321230001.3:class _xArray
#@+node:schmidli.20080321230001.12:_intp
def _intp(ar, sltup):
"""
Returns the result of applying an extended selection
tuple to the array.
In addition to numpy's basic slicing and advanced selection,
masked indexing arrays and multi-linear interpolation are
supported.
Linear interpolation along a specific axis is triggered
when the corresponding member of the selection tuple contains
an object (scalar, list, array) with non-integer numbers.
Examples, using shorthand notation:
v = ar[5]
v = ar[2.2,:,(4,5,6)]
v = ar[[2.5,6.2],0,:]
v = ar[:,[[2,2.2],[3,mv]],[[2],[3]],[[4,5]]]
"""
if not isinstance(sltup, tuple):
sltup = (sltup,)
if len(sltup) != ar.ndim:
raise ValueError('length of selection tuple does not match array dimension')
# determine first axis which requires interpolation
axis = None
for i in range(len(sltup)):
if isinstance(sltup[i], (N.ndarray, N.ma.MaskedArray)):
if sltup[i].dtype in (N.single, N.double):
axis = i
break
elif isinstance(sltup[i], slice):
pass
elif N.array(sltup[i]).dtype in (N.single, N.double):
axis = i
break
# if no interpolation required: apply selection and quit
if axis is None:
if len(sltup) == 0:
return ar[sltup]
elif ismasked_tuple(sltup):
sltup = list(sltup)
axis_mask = 0
for n in range(len(sltup)):
if isinstance(sltup[n], N.ma.MaskedArray):
mask = sltup[n].mask
sltup[n] = sltup[n].filled(0)
axis_mask = n
sltup = tuple(sltup)
r = ar[sltup]
#print ("r.shape, mask.shape:", r.shape, mask.shape, axis_mask)
if axis_mask > 0:
new_mask = N.zeros(r.shape, dtype='bool')
sl = (None,)*axis_mask + (slice(None),)
new_mask[:] = mask[sl]
mask = new_mask
if isinstance(r, N.ma.MaskedArray):
return N.ma.array(r, mask=mask | r.mask)
else:
return N.ma.array(r, mask=mask)
else:
return ar[sltup]
# calculate indices and weights
rj = sltup[axis]
j = N.floor(rj).astype(N.int)
j = N.clip(j, 0, ar.shape[axis]-2)
jp = j+1
f0j = rj-j
f1j = 1-f0j
sl = list(sltup)
slp = list(sltup)
sl[axis] = j
slp[axis] = jp
sl = tuple(sl); slp = tuple(slp)
res = _intp(ar, sl)*f1j + _intp(ar, slp)*f0j
return res
#@-node:schmidli.20080321230001.12:_intp
#@+node:schmidli.20080321230916:_rindex
def _rindex(ar, val, axis=0, ep=0.5, clip=True, round=False, squeeze=True):
"""
Assuming that ar is sorted in ascending order along axis, this
function returns real indices of where val would fit (using linear
interpolation).
ar[j,I] where I is a multi-dimensional index
val[nv] values
ep width of extrapolation zone
clip True: clip resulting indices to lie within a specified
range, i.e. -ep <= rj <= (n-1)+ep
False: set rj.mask = rj < -ep or rj > (n-1)+ep
res[nv,I]
print _rindex(N.arange(10), 5.5)
[ 5.5]
print _rindex(N.arange(10), -1)
[-0.5]
print _rindex(N.arange(10), [2,4.5])
[ 2. 4.5]
"""
# transpose input array if necessary
if axis != 0:
tr = N.arange(ar.ndim)
tr[0] = axis
tr[axis] = 0
ar = ar.transpose(tr).copy()
# bring input arrays into required shape
# ar[nj,ni] and val[nv]
ar_shape = list(ar.shape)
nj = ar_shape[0]
ni = ar.size//nj
ar.shape = (nj,ni)
is_scalar = N.isscalar(val)
val_ndim = N.array(val).ndim
val = N.atleast_1d(val)
nv = val.size
# determine the r-index
if ar.shape[0] == 1:
rj = N.zeros((1,ar.shape[1]), dtype=N.int)
else:
if ar[0,0] < ar[1,0]: # ascending order
j1 = N.apply_along_axis(N.searchsorted, 0, ar, val).reshape(nv,ni)
j1 = N.clip(j1, 1, nj-1)
j0 = j1-1
i = N.arange(ni).reshape(1,ni)
rj = j0 + 1.0*(val.reshape(nv,1) - ar[j0,i])/(ar[j1,i] - ar[j0,i])
if clip:
rj = N.clip(rj, -ep, (nj-1)+ep)
else:
mask = (rj<-ep)|(rj>nj-1+ep)
if mask.any():
rj = N.ma.array(rj, mask=mask)
else: #descending order
j1 = N.apply_along_axis(N.searchsorted, 0, ar[::-1], val).reshape(nv,ni)
j1 = nj - j1 - 1
j1 = j1.clip(0,nj-2)
j0 = j1+1
i = N.arange(ni).reshape(1,ni)
rj = j0 - 1.0*(val.reshape(nv,1) - ar[j0,i])/(ar[j1,i] - ar[j0,i])
if clip:
rj = N.clip(rj, -ep, (nj-1)+ep)
else:
mask = (rj<-ep)|(rj>nj-1+ep)
if mask.any():
rj = N.ma.array(rj, mask=mask)
# back to multdimensional
ar.shape = ar_shape
ar_shape[0] = nv
rj.shape = ar_shape
# and transpose if necessary
if axis != 0:
if isinstance(rj, N.ma.MaskedArray):
mask = rj.mask.transpose(tr)
data = rj.filled(0.).transpose(tr).copy()
rj = N.ma.array(data, mask=mask)
else:
rj = rj.transpose(tr).copy()
if round:
rj = N.round(rj).astype(N.int)
if is_scalar and squeeze:
rj_shape = list(rj.shape)
del(rj_shape[axis])
rj.shape = tuple(rj_shape)
#if len(ar_shape) == 1:
# rj = rj[0]
return rj
#@-node:schmidli.20080321230916:_rindex
#@+node:schmidli.20080322212349:_rindex2
def _rindex2(ar, val, axis=0, ep=0.5, clip=True, round=False):
"""
As function _rindex, but returns a complete selection tuple.
"""
val_ndim = N.array(val).ndim
rj = _rindex(ar, val, axis=axis, ep=ep, clip=clip, round=round,
squeeze=False)
# determine selection tuples for the remaining dimensions
rj_shape = list(rj.shape)
rtup = []
for i in xrange(rj.ndim):
idx = N.arange(rj.shape[i])
idx_shape = N.ones(rj.ndim)
idx_shape[i] = rj.shape[i]
idx.shape = idx_shape
rtup.append(idx)
rtup[axis] = rj
if val_ndim == 0:
for i in xrange(rj.ndim):
r_shape = list(rtup[i].shape)
if i != axis: del(r_shape[axis])
rtup[i].shape = r_shape
return tuple(rtup)
#@-node:schmidli.20080322212349:_rindex2
#@+node:schmidli.20080325135032:_rline
def _rline(edges, delta=None, ns=None):
"""
Returns a discretized polyline
->
edges[npts,ndim] coordinates of the edge points of the
line segments
delta length of a discretized segment
ns number of discretized segments
"""
edges = N.array(edges)
if edges.ndim != 2:
raise ValueError('edges must be 2-dimensional')
b = edges[1:,:] - edges[0:-1,:]
nlines = b.shape[0] # number of line segments
d = N.zeros(nlines, dtype=N.double)
for i in range(nlines):
d[i] = N.sqrt((b[i]**2).sum())
length = d.sum()
if delta is None: delta = 1
if ns is None: ns = int(length/delta) # number of discret segments
# number of discretized segments per line segment
n = N.zeros(nlines, dtype=N.int_)
for i in range(nlines):
n[i] = N.round(d[i]/length*ns)
rj = N.zeros((n.sum()+1,edges.shape[1]), dtype=N.double)
idx = N.zeros(nlines+1, dtype=N.int_)
idx[1:] = n.cumsum()
for i in xrange(nlines):
t = N.linspace(0,1,n[i]+1)
t.shape = (t.shape[0],1)
rj[idx[i]:idx[i+1]+1,:] = edges[i,:] + b[i,:]*t
return rj
#@-node:schmidli.20080325135032:_rline
#@+node:schmidli.20080321230001.13:ismasked_tuple
def ismasked_tuple(sltup):
""" """
for n in range(len(sltup)):
if isinstance(sltup[n], N.ma.MaskedArray):
return True
return False
#@-node:schmidli.20080321230001.13:ismasked_tuple
#@-others
#@-node:schmidli.20080321230001.1:@thin xarray.py
#@-leo