-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathradialprofile.py
255 lines (218 loc) · 11 KB
/
radialprofile.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
import numpy as np
def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return_nr=False,
binsize=0.5, weights=None, steps=False, interpnan=False, left=None, right=None,
mask=None ):
"""
Calculate the azimuthally averaged radial profile.
image - The 2D image
center - The [x,y] pixel coordinates used as the center. The default is
None, which then uses the center of the image (including
fractional pixels).
stddev - if specified, return the azimuthal standard deviation instead of the average
returnradii - if specified, return (radii_array,radial_profile)
return_nr - if specified, return number of pixels per radius *and* radius
binsize - size of the averaging bin. Can lead to strange results if
non-binsize factors are used to specify the center and the binsize is
too large
weights - can do a weighted average instead of a simple average if this keyword parameter
is set. weights.shape must = image.shape. weighted stddev is undefined, so don't
set weights and stddev.
steps - if specified, will return a double-length bin array and radial
profile so you can plot a step-form radial profile (which more accurately
represents what's going on)
interpnan - Interpolate over NAN values, i.e. bins where there is no data?
left,right - passed to interpnan; they set the extrapolated values
mask - can supply a mask (boolean array same size as image with True for OK and False for not)
to average over only select data.
If a bin contains NO DATA, it will have a NAN value because of the
divide-by-sum-of-weights component. I think this is a useful way to denote
lack of data, but users let me know if an alternative is prefered...
"""
# Calculate the indices from the image
y, x = np.indices(image.shape)
if center is None:
center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0])
r = np.hypot(x - center[0], y - center[1])
if weights is None:
weights = np.ones(image.shape)
elif stddev:
raise ValueError("Weighted standard deviation is not defined.")
if mask is None:
mask = np.ones(image.shape,dtype='bool')
# obsolete elif len(mask.shape) > 1:
# obsolete mask = mask.ravel()
# the 'bins' as initially defined are lower/upper bounds for each bin
# so that values will be in [lower,upper)
nbins = int(np.round(r.max() / binsize)+1)
maxbin = nbins * binsize
bins = np.linspace(0,maxbin,nbins+1)
# but we're probably more interested in the bin centers than their left or right sides...
bin_centers = (bins[1:]+bins[:-1])/2.0
# how many per bin (i.e., histogram)?
# there are never any in bin 0, because the lowest index returned by digitize is 1
#nr = np.bincount(whichbin)[1:]
nr = np.histogram(r, bins, weights=mask.astype('int'))[0]
# recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) )
# radial_prof.shape = bin_centers.shape
if stddev:
# Find out which radial bin each point in the map belongs to
whichbin = np.digitize(r.flat,bins)
# This method is still very slow; is there a trick to do this with histograms?
radial_prof = np.array([image.flat[mask.flat*(whichbin==b)].std() for b in xrange(1,nbins+1)])
else:
radial_prof = np.histogram(r, bins, weights=(image*weights*mask))[0] / np.histogram(r, bins, weights=(mask*weights))[0]
if interpnan:
radial_prof = np.interp(bin_centers,bin_centers[radial_prof==radial_prof],radial_prof[radial_prof==radial_prof],left=left,right=right)
if steps:
xarr = np.array(zip(bins[:-1],bins[1:])).ravel()
yarr = np.array(zip(radial_prof,radial_prof)).ravel()
return xarr,yarr
elif returnradii:
return bin_centers,radial_prof
elif return_nr:
return nr,bin_centers,radial_prof
else:
return radial_prof
def azimuthalAverageBins(image,azbins,symmetric=None, center=None, **kwargs):
""" Compute the azimuthal average over a limited range of angles
kwargs are passed to azimuthalAverage """
y, x = np.indices(image.shape)
if center is None:
center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0])
r = np.hypot(x - center[0], y - center[1])
theta = np.arctan2(x - center[0], y - center[1])
theta[theta < 0] += 2*np.pi
theta_deg = theta*180.0/np.pi
if isinstance(azbins,np.ndarray):
pass
elif isinstance(azbins,int):
if symmetric == 2:
azbins = np.linspace(0,90,azbins)
theta_deg = theta_deg % 90
elif symmetric == 1:
azbins = np.linspace(0,180,azbins)
theta_deg = theta_deg % 180
elif azbins == 1:
return azbins,azimuthalAverage(image,center=center,returnradii=True,**kwargs)
else:
azbins = np.linspace(0,359.9999999999999,azbins)
else:
raise ValueError("azbins must be an ndarray or an integer")
azavlist = []
for blow,bhigh in zip(azbins[:-1],azbins[1:]):
mask = (theta_deg > (blow % 360)) * (theta_deg < (bhigh % 360))
rr,zz = azimuthalAverage(image,center=center,mask=mask,returnradii=True,**kwargs)
azavlist.append(zz)
return azbins,rr,azavlist
def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=False,
binsize=1.0, weights=None, steps=False, interpnan=False, left=None, right=None,
mask=None, symmetric=None ):
"""
Calculate the radially averaged azimuthal profile.
(this code has not been optimized; it could be speed boosted by ~20x)
image - The 2D image
center - The [x,y] pixel coordinates used as the center. The default is
None, which then uses the center of the image (including
fractional pixels).
stddev - if specified, return the radial standard deviation instead of the average
returnAz - if specified, return (azimuthArray,azimuthal_profile)
return_naz - if specified, return number of pixels per azimuth *and* azimuth
binsize - size of the averaging bin. Can lead to strange results if
non-binsize factors are used to specify the center and the binsize is
too large
weights - can do a weighted average instead of a simple average if this keyword parameter
is set. weights.shape must = image.shape. weighted stddev is undefined, so don't
set weights and stddev.
steps - if specified, will return a double-length bin array and azimuthal
profile so you can plot a step-form azimuthal profile (which more accurately
represents what's going on)
interpnan - Interpolate over NAN values, i.e. bins where there is no data?
left,right - passed to interpnan; they set the extrapolated values
mask - can supply a mask (boolean array same size as image with True for OK and False for not)
to average over only select data.
If a bin contains NO DATA, it will have a NAN value because of the
divide-by-sum-of-weights component. I think this is a useful way to denote
lack of data, but users let me know if an alternative is prefered...
"""
# Calculate the indices from the image
y, x = np.indices(image.shape)
if center is None:
center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0])
r = np.hypot(x - center[0], y - center[1])
theta = np.arctan2(x - center[0], y - center[1])
theta[theta < 0] += 2*np.pi
theta_deg = theta*180.0/np.pi
maxangle = 360
if weights is None:
weights = np.ones(image.shape)
elif stddev:
raise ValueError("Weighted standard deviation is not defined.")
if mask is None:
# mask is only used in a flat context
mask = np.ones(image.shape,dtype='bool').ravel()
elif len(mask.shape) > 1:
mask = mask.ravel()
# allow for symmetries
if symmetric == 2:
theta_deg = theta_deg % 90
maxangle = 90
elif symmetric == 1:
theta_deg = theta_deg % 180
maxangle = 180
# the 'bins' as initially defined are lower/upper bounds for each bin
# so that values will be in [lower,upper)
nbins = int(np.round(maxangle / binsize))
maxbin = nbins * binsize
bins = np.linspace(0,maxbin,nbins+1)
# but we're probably more interested in the bin centers than their left or right sides...
bin_centers = (bins[1:]+bins[:-1])/2.0
# Find out which azimuthal bin each point in the map belongs to
whichbin = np.digitize(theta_deg.flat,bins)
# how many per bin (i.e., histogram)?
# there are never any in bin 0, because the lowest index returned by digitize is 1
nr = np.bincount(whichbin)[1:]
# recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) )
# azimuthal_prof.shape = bin_centers.shape
if stddev:
azimuthal_prof = np.array([image.flat[mask*(whichbin==b)].std() for b in xrange(1,nbins+1)])
else:
azimuthal_prof = np.array([(image*weights).flat[mask*(whichbin==b)].sum() / weights.flat[mask*(whichbin==b)].sum() for b in xrange(1,nbins+1)])
#import pdb; pdb.set_trace()
if interpnan:
azimuthal_prof = np.interp(bin_centers,
bin_centers[azimuthal_prof==azimuthal_prof],
azimuthal_prof[azimuthal_prof==azimuthal_prof],
left=left,right=right)
if steps:
xarr = np.array(zip(bins[:-1],bins[1:])).ravel()
yarr = np.array(zip(azimuthal_prof,azimuthal_prof)).ravel()
return xarr,yarr
elif returnAz:
return bin_centers,azimuthal_prof
elif return_naz:
return nr,bin_centers,azimuthal_prof
else:
return azimuthal_prof
def radialAverageBins(image,radbins, corners=True, center=None, **kwargs):
""" Compute the radial average over a limited range of radii """
y, x = np.indices(image.shape)
if center is None:
center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0])
r = np.hypot(x - center[0], y - center[1])
if isinstance(radbins,np.ndarray):
pass
elif isinstance(radbins,int):
if radbins == 1:
return radbins,radialAverage(image,center=center,returnAz=True,**kwargs)
elif corners:
radbins = np.linspace(0,r.max(),radbins)
else:
radbins = np.linspace(0,np.max(np.abs(np.array([x-center[0],y-center[1]]))),radbins)
else:
raise ValueError("radbins must be an ndarray or an integer")
radavlist = []
for blow,bhigh in zip(radbins[:-1],radbins[1:]):
mask = (r<bhigh)*(r>blow)
az,zz = radialAverage(image,center=center,mask=mask,returnAz=True,**kwargs)
radavlist.append(zz)
return radbins,az,radavlist