sodetlib 0.6.1rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sodetlib/__init__.py +22 -0
- sodetlib/_version.py +21 -0
- sodetlib/constants.py +13 -0
- sodetlib/det_config.py +709 -0
- sodetlib/noise.py +624 -0
- sodetlib/operations/__init__.py +5 -0
- sodetlib/operations/bias_dets.py +551 -0
- sodetlib/operations/bias_steps.py +1248 -0
- sodetlib/operations/bias_wave.py +688 -0
- sodetlib/operations/complex_impedance.py +651 -0
- sodetlib/operations/iv.py +716 -0
- sodetlib/operations/optimize.py +189 -0
- sodetlib/operations/squid_curves.py +641 -0
- sodetlib/operations/tracking.py +624 -0
- sodetlib/operations/uxm_relock.py +406 -0
- sodetlib/operations/uxm_setup.py +783 -0
- sodetlib/py.typed +0 -0
- sodetlib/quality_control.py +415 -0
- sodetlib/resonator_fitting.py +508 -0
- sodetlib/stream.py +291 -0
- sodetlib/tes_param_correction.py +579 -0
- sodetlib/util.py +880 -0
- sodetlib-0.6.1rc1.data/scripts/jackhammer +761 -0
- sodetlib-0.6.1rc1.dist-info/LICENSE +25 -0
- sodetlib-0.6.1rc1.dist-info/METADATA +6 -0
- sodetlib-0.6.1rc1.dist-info/RECORD +28 -0
- sodetlib-0.6.1rc1.dist-info/WHEEL +5 -0
- sodetlib-0.6.1rc1.dist-info/top_level.txt +1 -0
sodetlib/noise.py
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
from scipy.signal import welch
|
|
2
|
+
from scipy.optimize import curve_fit
|
|
3
|
+
from sodetlib.util import get_asd, cprint
|
|
4
|
+
import sodetlib as sdl
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def noise_model(freq, wl, n, f_knee):
|
|
11
|
+
"""
|
|
12
|
+
Crude model for noise modeling
|
|
13
|
+
|
|
14
|
+
Args
|
|
15
|
+
----
|
|
16
|
+
freq : float
|
|
17
|
+
independent variable in function (frequency Hz)
|
|
18
|
+
wl : float
|
|
19
|
+
White-noise level.
|
|
20
|
+
n : float
|
|
21
|
+
Exponent of 1/f^(n/2) component.
|
|
22
|
+
f_knee : float
|
|
23
|
+
Frequency at which white noise = 1/f^n component
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
y : float
|
|
27
|
+
dependent variable in function noise in (pA/rtHz)
|
|
28
|
+
"""
|
|
29
|
+
y = wl*np.sqrt((f_knee/freq)**n+1)
|
|
30
|
+
return y
|
|
31
|
+
|
|
32
|
+
def fit_noise_asd(f, Axx, wl_f_range=(10,30), p0=None):
|
|
33
|
+
"""
|
|
34
|
+
Return model fit for a ASD.
|
|
35
|
+
|
|
36
|
+
Args
|
|
37
|
+
-----
|
|
38
|
+
|
|
39
|
+
f : float array
|
|
40
|
+
The frequency information.
|
|
41
|
+
Axx : float array
|
|
42
|
+
The power spectral data.
|
|
43
|
+
wl_f_range: float tuple
|
|
44
|
+
tuple contains (f_low, f_high), f_high is used to determine the
|
|
45
|
+
range of the ASD to fit to the noise model and if fit fails then the
|
|
46
|
+
white noise is calculated as the median between f_low and f_high.
|
|
47
|
+
p0 : float array or None, optional, default None
|
|
48
|
+
Initial guess for fitting ASDs. If None, sets to ``p0=[100.0,0.5,0.01]``
|
|
49
|
+
which corresponds to:
|
|
50
|
+
|
|
51
|
+
- white-noise level in pA/rtHz
|
|
52
|
+
- exponent of 1/f^n component
|
|
53
|
+
- knee
|
|
54
|
+
- frequency in Hz
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
|
|
59
|
+
popt : float array
|
|
60
|
+
The fit parameters - [white_noise_level, n, f_knee].
|
|
61
|
+
"""
|
|
62
|
+
if p0 is None:
|
|
63
|
+
p0 = [100.,0.5,0.01]
|
|
64
|
+
|
|
65
|
+
bounds_low = [0.,0.,0.] # constrain 1/f^n to be red spectrum
|
|
66
|
+
bounds_high = [np.inf,np.inf,np.inf]
|
|
67
|
+
bounds = (bounds_low,bounds_high)
|
|
68
|
+
|
|
69
|
+
fit_idxmax = np.nanargmin(np.abs(f-wl_f_range[1]))
|
|
70
|
+
try:
|
|
71
|
+
popt, pcov = curve_fit(noise_model, f[1:fit_idxmax], Axx[1:fit_idxmax],
|
|
72
|
+
p0=p0, bounds=bounds)
|
|
73
|
+
except Exception:
|
|
74
|
+
idxmin = np.nanargmin(np.abs(f-wl_f_range[0]))
|
|
75
|
+
wl = np.median(Axx[idxmin:fit_idxmax])
|
|
76
|
+
popt = [wl, np.nan, np.nan]
|
|
77
|
+
pcov = None
|
|
78
|
+
return popt
|
|
79
|
+
|
|
80
|
+
def get_noise_params(am, wl_f_range=(10,30),
|
|
81
|
+
fit=False, nperdecade=10, **asd_args):
|
|
82
|
+
"""
|
|
83
|
+
Function to calculate the ASD from an axis manager and then calculate the
|
|
84
|
+
white noise, and fknee (and n-index of 1/f^{n/2} if fit=True). The fit=True
|
|
85
|
+
option is a lot slower.
|
|
86
|
+
|
|
87
|
+
Args
|
|
88
|
+
----
|
|
89
|
+
am: AxisManager
|
|
90
|
+
axis manager loaded using G3tSmurf with timestamps and signal keys.
|
|
91
|
+
wl_f_range: float tuple
|
|
92
|
+
tuple contains (f_low, f_high), if fit=True see `fit_noise_ASD`.
|
|
93
|
+
The white noise is calculated as the median of the ASD (pA/rtHz)
|
|
94
|
+
between f_low and f_high.
|
|
95
|
+
fit: bool
|
|
96
|
+
if true will fit the ASD using `fit_noise_ASD` function
|
|
97
|
+
nperdecade: int
|
|
98
|
+
number of bins per decade (i.e. between 0.01 to 0.1 or 0.1 to 1) to
|
|
99
|
+
use to average the ASD over to avoid peaks skewing the search for
|
|
100
|
+
fknee when not using the fit=True option. If nperdecade = 10 for
|
|
101
|
+
example then the bins between 0.01 and 1 would be:
|
|
102
|
+
np.concatenate((np.linspace(0.01,0.1,10),np.linspace(0.1,1,10)[1:]))
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
outdict: dict
|
|
106
|
+
dictionary that contains all of the calculated noise parameters by
|
|
107
|
+
channel, band averaged white noise levels, and f, axx ndarrays from
|
|
108
|
+
the calculated ASD the keys are:
|
|
109
|
+
|
|
110
|
+
noise_pars: ndarray
|
|
111
|
+
shape is [nchans,3] the 3 items in axis=1 are: 0 = white noise,
|
|
112
|
+
1 = n (1/f index nan if fit=False), and 2 = fknee.
|
|
113
|
+
band_medians: ndarray
|
|
114
|
+
shape is [8,1] median white noise level for each band.
|
|
115
|
+
f: ndarray
|
|
116
|
+
frequency array from welch periodogram
|
|
117
|
+
axx: ndarray
|
|
118
|
+
square root of welch output PSD shape is [nchans,len(f)]
|
|
119
|
+
bincenters: ndarray
|
|
120
|
+
Frequencies where the low frequency spectrum is binned when using
|
|
121
|
+
`Fit = False`, if `Fit=True` then just the array of frequencies
|
|
122
|
+
that can be used to plot the fit results in `lowfn`.
|
|
123
|
+
lowfn: ndarray
|
|
124
|
+
shape = [nchans, len(bincenters)]. Binned noise levels
|
|
125
|
+
(if `Fit=False`) or fit noise levels (if Fit=`True`) for the low
|
|
126
|
+
frequency part of the ASD.
|
|
127
|
+
low_f_10mHz: ndarray
|
|
128
|
+
shape = [nchans].Ratio of the ASD at 10mHz relative to a reference
|
|
129
|
+
low-f component that has a white noise component = 65 pA/rtHz and
|
|
130
|
+
low-f scaling of 1/f^{1/2}.
|
|
131
|
+
"""
|
|
132
|
+
f, axx = get_asd(am, **asd_args)
|
|
133
|
+
idx10mHz = np.nanargmin(np.abs(f-0.01))
|
|
134
|
+
nlref_10mHz = 65*np.sqrt((0.1/f[idx10mHz])+1)
|
|
135
|
+
noise_pars = np.zeros((np.shape(axx)[0],3))
|
|
136
|
+
if fit == False:
|
|
137
|
+
#Find white noise
|
|
138
|
+
fmask = (wl_f_range[0] < f) & (f < wl_f_range[1])
|
|
139
|
+
wls = np.median(axx[:, fmask], axis=1)
|
|
140
|
+
band_medians = np.zeros(8)
|
|
141
|
+
for i in range(8):
|
|
142
|
+
m = am.ch_info.band == i
|
|
143
|
+
band_medians[i] = np.median(wls[m])
|
|
144
|
+
|
|
145
|
+
#Calculate f_knee
|
|
146
|
+
decades = np.arange(np.floor(np.log10(f[1])),2)
|
|
147
|
+
binedges = np.asarray([])
|
|
148
|
+
for i, dec in enumerate(decades[:-1]):
|
|
149
|
+
if i == 0:
|
|
150
|
+
binedges = np.concatenate(
|
|
151
|
+
(binedges, np.linspace(10**dec, 10**decades[i+1],
|
|
152
|
+
nperdecade)))
|
|
153
|
+
else:
|
|
154
|
+
binedges = np.concatenate(
|
|
155
|
+
(binedges, np.linspace(10**dec, 10**decades[i+1],
|
|
156
|
+
nperdecade)[1:]))
|
|
157
|
+
start_bin = min(np.argsort(np.abs(binedges-f[1]))[0:2])
|
|
158
|
+
binedges = binedges[start_bin:]
|
|
159
|
+
bincenters = binedges[:-1]+np.diff(binedges)/2
|
|
160
|
+
lowfn = np.zeros((len(wls),len(bincenters)))
|
|
161
|
+
for ii,be in enumerate(binedges[:-1]):
|
|
162
|
+
m = (f > be) & (f < binedges[ii+1])
|
|
163
|
+
lowfn[:,ii] = np.median(axx[:,m],axis = 1)
|
|
164
|
+
fknees = [
|
|
165
|
+
bincenters[np.nanargmin(np.abs(lowfn[idx]-wls[idx]*np.sqrt(2)))]
|
|
166
|
+
for idx in range(len(wls))
|
|
167
|
+
]
|
|
168
|
+
fknees = np.asarray(fknees)
|
|
169
|
+
m = [np.nanmax(lowfn[idx]) < np.sqrt(2)*wls[idx]
|
|
170
|
+
for idx in range(len(wls))]
|
|
171
|
+
fknees[m] = f[1]
|
|
172
|
+
#Set noise_pars to have same structure as when we fit.
|
|
173
|
+
noise_pars[:, 0] = wls
|
|
174
|
+
noise_pars[:, 1] = np.full(len(wls),np.nan)
|
|
175
|
+
noise_pars[:, 2] = fknees
|
|
176
|
+
else:
|
|
177
|
+
bincenters = np.logspace(np.log10(f[1]),np.log10(wl_f_range[1]),30)
|
|
178
|
+
lowfn = np.zeros((len(noise_pars[:,0]),len(bincenters)))
|
|
179
|
+
for i, aaxx in enumerate(axx):
|
|
180
|
+
noise_pars[i,:] = fit_noise_asd(f, aaxx, wl_f_range=wl_f_range)
|
|
181
|
+
lowfn[i,:] = noise_model(bincenters,*noise_pars[i,:])
|
|
182
|
+
band_medians = np.zeros(8)
|
|
183
|
+
for i in range(8):
|
|
184
|
+
m = am.ch_info.band == i
|
|
185
|
+
band_medians[i] = np.median(noise_pars[m,0])
|
|
186
|
+
nl_10mHz_rat = axx[:,idx10mHz]/nlref_10mHz
|
|
187
|
+
outdict = {'noise_pars': noise_pars,
|
|
188
|
+
'bands': am.ch_info.band,
|
|
189
|
+
'channels': am.ch_info.channel,
|
|
190
|
+
'band_medians': band_medians,
|
|
191
|
+
'f': f,
|
|
192
|
+
'axx': axx,
|
|
193
|
+
'bincenters': bincenters,
|
|
194
|
+
'lowfn': lowfn,
|
|
195
|
+
'low_f_10mHz': nl_10mHz_rat}
|
|
196
|
+
return outdict
|
|
197
|
+
|
|
198
|
+
def plot_band_noise(am, nbins=40, noisedict=None, wl_f_range=(10,30),
|
|
199
|
+
fit=False, nperdecade=10, show_plot=True, save_plot=False,
|
|
200
|
+
save_dir=None, **asd_args):
|
|
201
|
+
"""
|
|
202
|
+
Makes a summary plot w/ subplots per band of a histogram of the white noise
|
|
203
|
+
levels and another plot with histograms of the fknees. If an axis AxisManager
|
|
204
|
+
is passed without a noisedict then `get_noise_params` will be called to
|
|
205
|
+
generate a noisedict otherwise those parameters will be skipped.
|
|
206
|
+
|
|
207
|
+
Args
|
|
208
|
+
----
|
|
209
|
+
am: AxisManager
|
|
210
|
+
axis manager loaded using G3tSmurf with timestamps and signal keys
|
|
211
|
+
to be analyzed.
|
|
212
|
+
nbins: int
|
|
213
|
+
number of bins in the histograms.
|
|
214
|
+
noisedict: dict
|
|
215
|
+
dictionary returned by `get_noise_params` see that docstring for
|
|
216
|
+
details on dictionary keys.
|
|
217
|
+
wl_f_range: float tuple
|
|
218
|
+
tuple contains (f_low, f_high), if fit=True see `fit_noise_asd`.
|
|
219
|
+
The white noise is calculated as the median of the ASD (pA/rtHz)
|
|
220
|
+
between f_low and f_high.
|
|
221
|
+
fit: bool
|
|
222
|
+
if true will fit the ASD using `fit_noise_asd` function
|
|
223
|
+
nperdecade: int
|
|
224
|
+
used to calculate fknee, see `get_noise_params` doc string.
|
|
225
|
+
show_plot: bool
|
|
226
|
+
plot only displayed if true.
|
|
227
|
+
save_plot: bool
|
|
228
|
+
plot only saved if true. If true then `save_dir` is required to
|
|
229
|
+
save properly otherwise will just return without saving.
|
|
230
|
+
save_dir: str
|
|
231
|
+
directory where plots are saved. Required if `save_plot` is True.
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
fig_wnl: `matplotlib.figure.Figure`
|
|
235
|
+
matplotlib figure object for white noise plot
|
|
236
|
+
axes_wnl: `matplotlib.axes.Axes`
|
|
237
|
+
matplotlib axes object for white noise plot
|
|
238
|
+
fig_fk: `matplotlib.figure.Figure`
|
|
239
|
+
matplotlib figure object for fknee plot
|
|
240
|
+
axes_fk: `matplotlib.axes.Axes`
|
|
241
|
+
matplotlib axes object for fknee plot
|
|
242
|
+
"""
|
|
243
|
+
if save_plot:
|
|
244
|
+
if save_dir==None:
|
|
245
|
+
raise ValueError('save_dir must be provided to save plots.')
|
|
246
|
+
bands = am.ch_info.band
|
|
247
|
+
ctime = int(am.timestamps[0])
|
|
248
|
+
|
|
249
|
+
if noisedict == None:
|
|
250
|
+
noisedict = get_noise_params(am, wl_f_range=wl_f_range, fit=fit,
|
|
251
|
+
nperdecade=nperdecade, **asd_args)
|
|
252
|
+
wls = noisedict['noise_pars'][:,0]
|
|
253
|
+
fknees = noisedict['noise_pars'][:,2]
|
|
254
|
+
band_medians = noisedict['band_medians']
|
|
255
|
+
|
|
256
|
+
#Only turns off interactive mode inside with so that won't effect setting.
|
|
257
|
+
try:
|
|
258
|
+
isinteractive = plt.isinteractive
|
|
259
|
+
plt.ioff()
|
|
260
|
+
|
|
261
|
+
#Plot white noise histograms
|
|
262
|
+
fig_wnl, axes_wnl = plt.subplots(4, 2, figsize=(16, 8),
|
|
263
|
+
gridspec_kw={'hspace': 0})
|
|
264
|
+
fig_wnl.patch.set_facecolor('white')
|
|
265
|
+
bins = np.logspace(1, 4, nbins)
|
|
266
|
+
max_bins = 0
|
|
267
|
+
|
|
268
|
+
for b in range(8):
|
|
269
|
+
ax = axes_wnl[b % 4, b // 4]
|
|
270
|
+
m = bands == b
|
|
271
|
+
x = ax.hist(wls[m], bins=bins)
|
|
272
|
+
text = f"Median: {band_medians[b]:0.1f} pA/rtHz\n"
|
|
273
|
+
text += f"Chans pictured: {np.sum(x[0]):0.0f}"
|
|
274
|
+
ax.text(0.7, .7, text, transform=ax.transAxes)
|
|
275
|
+
ax.axvline(np.median(wls[m]), color='red')
|
|
276
|
+
max_bins = max(np.max(x[0]), max_bins)
|
|
277
|
+
ax.set(xscale='log', ylabel=f'Band {b}')
|
|
278
|
+
|
|
279
|
+
axes_wnl[0][0].set(title="AMC 0")
|
|
280
|
+
axes_wnl[0][1].set(title="AMC 1")
|
|
281
|
+
axes_wnl[-1][0].set(xlabel="White Noise (pA/rt(Hz))")
|
|
282
|
+
axes_wnl[-1][1].set(xlabel="White Noise (pA/rt(Hz))")
|
|
283
|
+
for _ax in axes_wnl:
|
|
284
|
+
for ax in _ax:
|
|
285
|
+
ax.set(ylim=(0, max_bins * 1.1))
|
|
286
|
+
plt.suptitle(
|
|
287
|
+
f'Total yield {len(wls)}, Overall median noise {np.nanmedian(wls):0.1f} pA/rtHz')
|
|
288
|
+
if save_plot:
|
|
289
|
+
plt.savefig(os.path.join(save_dir,
|
|
290
|
+
f'{ctime}_white_noise_summary.png'))
|
|
291
|
+
if show_plot:
|
|
292
|
+
plt.show()
|
|
293
|
+
else:
|
|
294
|
+
plt.close()
|
|
295
|
+
|
|
296
|
+
#Plot f_knee histograms
|
|
297
|
+
fig_fk, axes_fk = plt.subplots(4, 2, figsize=(16, 8),
|
|
298
|
+
gridspec_kw={'hspace': 0})
|
|
299
|
+
fig_fk.patch.set_facecolor('white')
|
|
300
|
+
bins = np.logspace(np.floor(np.log10(np.min(fknees[fknees>0]))),
|
|
301
|
+
np.ceil(np.log10(np.max(fknees[fknees>0]))),
|
|
302
|
+
nbins)
|
|
303
|
+
max_bins = 0
|
|
304
|
+
|
|
305
|
+
for b in range(8):
|
|
306
|
+
ax = axes_fk[b % 4, b // 4]
|
|
307
|
+
m = (bands == b) & (fknees > 0)
|
|
308
|
+
x = ax.hist(fknees[m], bins=bins)
|
|
309
|
+
text = f"Median: {np.median(fknees[m]):0.2f}\n"
|
|
310
|
+
text += f"Chans pictured: {np.sum(x[0]):0.0f}"
|
|
311
|
+
ax.text(0.72, .7, text, transform=ax.transAxes)
|
|
312
|
+
ax.axvline(np.median(fknees[m]), color='red')
|
|
313
|
+
max_bins = max(np.max(x[0]), max_bins)
|
|
314
|
+
ax.set(xscale='log', ylabel=f'Band {b}')
|
|
315
|
+
|
|
316
|
+
axes_fk[0][0].set(title="AMC 0")
|
|
317
|
+
axes_fk[0][1].set(title="AMC 1")
|
|
318
|
+
axes_fk[-1][0].set(xlabel="$f_{knee}$ (Hz)")
|
|
319
|
+
axes_fk[-1][1].set(xlabel="$f_{knee}$ (Hz)")
|
|
320
|
+
for _ax in axes_fk:
|
|
321
|
+
for ax in _ax:
|
|
322
|
+
ax.set(ylim=(0, max_bins * 1.1))
|
|
323
|
+
if show_plot:
|
|
324
|
+
plt.show()
|
|
325
|
+
if save_plot:
|
|
326
|
+
plt.savefig(os.path.join(save_dir,f'{ctime}_fknee_summary.png'))
|
|
327
|
+
if not(show_plot):
|
|
328
|
+
plt.close()
|
|
329
|
+
|
|
330
|
+
#Plot ASDs
|
|
331
|
+
fig_asd, axes_asd = plt.subplots(4, 2, figsize=(16, 8),
|
|
332
|
+
gridspec_kw={'hspace': 0})
|
|
333
|
+
fig_asd.patch.set_facecolor('white')
|
|
334
|
+
|
|
335
|
+
min_x, max_x = (1, 0)
|
|
336
|
+
for b in range(8):
|
|
337
|
+
ax = axes_asd[b % 4, b // 4]
|
|
338
|
+
m = bands == b
|
|
339
|
+
med_wl = np.nanmedian(wls[m])
|
|
340
|
+
f_arr = np.tile(noisedict['f'], (sum(m),1))
|
|
341
|
+
x = ax.loglog(f_arr.T, noisedict['axx'][m].T, color='C0', alpha=0.1)
|
|
342
|
+
ax.axhline(med_wl, color='red', alpha=0.6,
|
|
343
|
+
label=f'Med. WL: {med_wl:.1f} pA/rtHz')
|
|
344
|
+
ax.set(ylabel=f'Band {b}\nASD (pA/rtHz)')
|
|
345
|
+
ax.grid(linestyle='--', which='both')
|
|
346
|
+
ax.legend(loc='upper right')
|
|
347
|
+
min_x = min(ax.get_xlim()[0], min_x)
|
|
348
|
+
max_x = max(ax.get_xlim()[1], max_x)
|
|
349
|
+
|
|
350
|
+
axes_asd[0][0].set(title="AMC 0")
|
|
351
|
+
axes_asd[0][1].set(title="AMC 1")
|
|
352
|
+
axes_asd[-1][0].set(xlabel="Frequency (Hz)")
|
|
353
|
+
axes_asd[-1][1].set(xlabel="Frequency (Hz)")
|
|
354
|
+
for _ax in axes_asd:
|
|
355
|
+
for ax in _ax:
|
|
356
|
+
ax.set(xlim=[min_x, max_x], ylim=[1, 5e3])
|
|
357
|
+
if save_plot:
|
|
358
|
+
plt.savefig(os.path.join(save_dir,
|
|
359
|
+
f'{ctime}_band_asds.png'))
|
|
360
|
+
if show_plot:
|
|
361
|
+
plt.show()
|
|
362
|
+
else:
|
|
363
|
+
plt.close()
|
|
364
|
+
|
|
365
|
+
finally:
|
|
366
|
+
if isinteractive:
|
|
367
|
+
plt.ion()
|
|
368
|
+
return fig_wnl, axes_wnl, fig_fk, axes_fk, fig_asd, axes_asd
|
|
369
|
+
|
|
370
|
+
def plot_channel_noise(am, rc, save_dir=None, noisedict=None, wl_f_range=(10,30),
|
|
371
|
+
fit=False, show_plot=False, save_plot=False, nperdecade=10,
|
|
372
|
+
plot1overfregion=False, **asd_args):
|
|
373
|
+
"""
|
|
374
|
+
Function for plotting the tod and psd with white noise and fknee identified
|
|
375
|
+
for a single channel.
|
|
376
|
+
|
|
377
|
+
Args
|
|
378
|
+
----
|
|
379
|
+
am: `sotodlib.core.AxisManager`
|
|
380
|
+
axis manager loaded using G3tSmurf with timestamps and signal keys
|
|
381
|
+
to be analyzed.
|
|
382
|
+
rc: int
|
|
383
|
+
Readout channel (i.e. index of am.signal) to plot.
|
|
384
|
+
noisedict: dict
|
|
385
|
+
dictionary returned by `get_noise_params` see that docstring for
|
|
386
|
+
details on dictionary keys.
|
|
387
|
+
wl_f_range: float tuple
|
|
388
|
+
tuple contains (f_low, f_high), if fit=True see `fit_noise_asd`.
|
|
389
|
+
The white noise is calculated as the median of the ASD (pA/rtHz)
|
|
390
|
+
between f_low and f_high.
|
|
391
|
+
fit: bool
|
|
392
|
+
if true will fit the ASD using `fit_noise_asd` function
|
|
393
|
+
show_plot: bool
|
|
394
|
+
plot only displayed if true.
|
|
395
|
+
save_plot: bool
|
|
396
|
+
plot only saved if true.
|
|
397
|
+
nperdecade: int
|
|
398
|
+
used to calculate fknee, see `get_noise_params` doc string.
|
|
399
|
+
plot1overfregion: bool
|
|
400
|
+
if true plots a line and shaded region that represents the SO
|
|
401
|
+
passing low-f requirement (i.e. fknee set by wl = 65pA/rtHz and
|
|
402
|
+
slope must be <= 1/f^{1/2} in the ASD)
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
fig: `matplotlib.figure.Figure`
|
|
406
|
+
matplotlib figure object for plot
|
|
407
|
+
axes: `matplotlib.axes.Axes`
|
|
408
|
+
matplotlib axes object for plot
|
|
409
|
+
"""
|
|
410
|
+
if (save_plot) & (save_dir==None):
|
|
411
|
+
raise ValueError('Must provide save path, exiting.')
|
|
412
|
+
|
|
413
|
+
if noisedict == None:
|
|
414
|
+
noisedict = get_noise_params(am, wl_f_range=wl_f_range, fit=fit,
|
|
415
|
+
nperdecade=nperdecade, **asd_args)
|
|
416
|
+
f,axx = noisedict['f'],noisedict['axx']
|
|
417
|
+
noise_pars = noisedict['noise_pars']
|
|
418
|
+
|
|
419
|
+
#Only turns off in with so that doesn't effect outside setting.
|
|
420
|
+
try:
|
|
421
|
+
isinteractive = plt.isinteractive
|
|
422
|
+
plt.ioff()
|
|
423
|
+
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
|
|
424
|
+
|
|
425
|
+
plt.figure()
|
|
426
|
+
fig, axes = plt.subplots(2, 1)
|
|
427
|
+
ax1 = axes[0]
|
|
428
|
+
ax1.plot((am.timestamps-am.timestamps[0]),am.signal[rc])
|
|
429
|
+
ax1.set_xlabel('Elapsed Time [sec]',fontsize = 14)
|
|
430
|
+
ax1.set_ylabel('Signal [pA]',fontsize = 14)
|
|
431
|
+
band = am.ch_info.band[rc]
|
|
432
|
+
chan = am.ch_info.channel[rc]
|
|
433
|
+
ttlstr = f'Band {band}, Channel {chan}'
|
|
434
|
+
ax1.set_title(ttlstr,fontsize = 18)
|
|
435
|
+
ax2 = axes[1]
|
|
436
|
+
ax2.loglog(f,axx[rc],color = 'grey',alpha = 0.7)
|
|
437
|
+
ax2.axhline(noise_pars[rc,0], xmin=0.5, xmax=1, lw=2, ls=':',
|
|
438
|
+
color='k')
|
|
439
|
+
wl_fknee = axx[rc,np.nanargmin(np.abs(f-noise_pars[rc,2]))]
|
|
440
|
+
ax2.plot(noise_pars[rc,2], wl_fknee, '*', markersize=8, color='green')
|
|
441
|
+
if plot1overfregion:
|
|
442
|
+
#plt.axhline(np.sqrt(2)*noise_pars[rc,0],color = 'C0')
|
|
443
|
+
#plt.axvline(noise_pars[rc,2])
|
|
444
|
+
plt.plot(noisedict['bincenters'],
|
|
445
|
+
noisedict['lowfn'][rc],'.',lw = 3,color = 'r')
|
|
446
|
+
l1 = noise_pars[rc,0]*np.ones(len(f[f<=0.1]))
|
|
447
|
+
with np.errstate(divide='ignore'):
|
|
448
|
+
l2 = 65*np.sqrt(0.1/f[f<=0.1])
|
|
449
|
+
ax2.plot(f[f<=0.1],l2,'--',color = 'C1')
|
|
450
|
+
ax2.fill_between(f[f<=0.1],l2,l1,color = 'wheat',alpha = 0.3)
|
|
451
|
+
text = f'White Noise: {np.round(float(noise_pars[rc,0]),1)} pA/rtHz\n'
|
|
452
|
+
text += 'f$_{knee}$: '+f'{np.round(noise_pars[rc,2],4)} Hz'
|
|
453
|
+
ax2.text(0.03, 0.1, text, bbox=props, transform=ax2.transAxes)
|
|
454
|
+
ax2.set_xlabel('Frequency [Hz]', fontsize=14)
|
|
455
|
+
ax2.set_ylabel('ASD [pA/rtHz]', fontsize=14)
|
|
456
|
+
|
|
457
|
+
if save_plot:
|
|
458
|
+
ctime = int(am.timestamps[0])
|
|
459
|
+
plt.savefig(os.path.join(save_dir,
|
|
460
|
+
f'{ctime}_b{band}c{chan}_noise.png'))
|
|
461
|
+
if show_plot:
|
|
462
|
+
plt.show()
|
|
463
|
+
else:
|
|
464
|
+
plt.close()
|
|
465
|
+
|
|
466
|
+
finally:
|
|
467
|
+
if isinteractive:
|
|
468
|
+
plt.ion()
|
|
469
|
+
return fig, axes
|
|
470
|
+
|
|
471
|
+
@sdl.set_action()
|
|
472
|
+
def take_noise(S, cfg, acq_time=30, plot_band_summary=True, nbins=40,
|
|
473
|
+
show_plot=True, save_plot=True, plotted_rchans=None,
|
|
474
|
+
wl_f_range=(10,30), fit=False,
|
|
475
|
+
nperdecade=10, plot1overfregion=False, save_dir=None,
|
|
476
|
+
g3_tag=None,
|
|
477
|
+
**asd_args):
|
|
478
|
+
"""
|
|
479
|
+
Streams data for specified amount of time and then calculated the ASD and
|
|
480
|
+
calculates the white noise levels and fknees for all channels. Optionally
|
|
481
|
+
the band medians of the fitted parameters can be plotted and/or individual
|
|
482
|
+
channel plots of the TOD and ASD with white noise and fknee called out.
|
|
483
|
+
|
|
484
|
+
Args
|
|
485
|
+
----
|
|
486
|
+
|
|
487
|
+
S : `pysmurf.client.base.smurf_control.SmurfControl`
|
|
488
|
+
pysmurf control object
|
|
489
|
+
cfg : `sodetlib.det_config.DetConfig`
|
|
490
|
+
detconfig object
|
|
491
|
+
acq_time : float
|
|
492
|
+
acquisition time for the noise timestream.
|
|
493
|
+
plot_band_summary : bool
|
|
494
|
+
if true will plot band summary of white noise and fknees.
|
|
495
|
+
show_plot : bool
|
|
496
|
+
if true will display plots.
|
|
497
|
+
plotted_rchans : int list
|
|
498
|
+
list of readout channels (i.e. index of am.signal) to make channel
|
|
499
|
+
plots for.
|
|
500
|
+
wl_f_range : float tuple
|
|
501
|
+
tuple contains (f_low, f_high), if fit=True see `fit_noise_asd`.
|
|
502
|
+
The white noise is calculated as the median of the ASD (pA/rtHz)
|
|
503
|
+
between f_low and f_high.
|
|
504
|
+
fit : bool
|
|
505
|
+
if true will fit the ASD using `fit_noise_asd` function
|
|
506
|
+
nperdecade : int
|
|
507
|
+
used to calculate fknee, see `get_noise_params` doc string.
|
|
508
|
+
plot1overfregion : bool
|
|
509
|
+
if true plots a line and shaded region that represents the SO
|
|
510
|
+
passing low-f requirement (i.e. fknee set by wl = 65pA/rtHz and
|
|
511
|
+
slope must be <= 1/f^{1/2} in the ASD)
|
|
512
|
+
g3_tag: string, optional
|
|
513
|
+
Tag to be attached to g3 stream
|
|
514
|
+
|
|
515
|
+
Returns
|
|
516
|
+
-------
|
|
517
|
+
am: AxisManager
|
|
518
|
+
AxisManager from the timestream acquired to calculate noise
|
|
519
|
+
parameters.
|
|
520
|
+
outdict: dict
|
|
521
|
+
dictionary that contains all calculated noise parameters and
|
|
522
|
+
figure and axes objects for all plots generated. The keys are:
|
|
523
|
+
|
|
524
|
+
'noisedict':
|
|
525
|
+
dictionary returned by `get_noise_params` see that doc string
|
|
526
|
+
'fig_wnl':
|
|
527
|
+
matplotlib figure object for white noise band summary plot
|
|
528
|
+
Only returned if plot_band_summary is True.
|
|
529
|
+
'axes_wnl':
|
|
530
|
+
matplotlib axes object for white noise band summary plot
|
|
531
|
+
Only returned if plot_band_summary is True.
|
|
532
|
+
'fig_fk':
|
|
533
|
+
matplotlib figure object for fknee band summary plot
|
|
534
|
+
Only returned if plot_band_summary is True.
|
|
535
|
+
'axes_fk':
|
|
536
|
+
matplotlib axes object for fknee band summary plot
|
|
537
|
+
Only returned if plot_band_summary is True.
|
|
538
|
+
'channel_plots':
|
|
539
|
+
nested dictionary that has a key for each readout channel in the
|
|
540
|
+
plotted_rchans list and contains a matplotlib figure and axis for
|
|
541
|
+
each readout channel. Only returned if plot_channel_noise is True.
|
|
542
|
+
"""
|
|
543
|
+
if save_dir is None:
|
|
544
|
+
save_dir = S.plot_dir
|
|
545
|
+
|
|
546
|
+
sid = sdl.take_g3_data(S, acq_time, tag=g3_tag, subtype='noise')
|
|
547
|
+
am = sdl.load_session(cfg.stream_id, sid, base_dir=cfg.sys['g3_dir'])
|
|
548
|
+
ctime = int(am.timestamps[0])
|
|
549
|
+
noisedict = get_noise_params(am, wl_f_range=wl_f_range, fit=fit,
|
|
550
|
+
nperdecade=nperdecade, **asd_args)
|
|
551
|
+
|
|
552
|
+
sdl.set_session_data(S, 'noise', {
|
|
553
|
+
'band_medians': noisedict['band_medians']
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
outdict = noisedict.copy()
|
|
557
|
+
outdict['sid'] = sid
|
|
558
|
+
outdict['meta'] = sdl.get_metadata(S, cfg)
|
|
559
|
+
if plot_band_summary:
|
|
560
|
+
fig_wnl, axes_wnl, fig_fk, axes_fk, fig_asd, axes_asd = plot_band_noise(
|
|
561
|
+
am, nbins=nbins, noisedict=noisedict, show_plot=show_plot,
|
|
562
|
+
save_plot=False, save_dir=save_dir, **asd_args)
|
|
563
|
+
if save_plot:
|
|
564
|
+
savename = os.path.join(save_dir, f'{ctime}_white_noise_summary.png')
|
|
565
|
+
fig_wnl.savefig(savename)
|
|
566
|
+
S.pub.register_file(savename, 'take_noise', plot=True)
|
|
567
|
+
savename = os.path.join(save_dir, f'{ctime}_fknee_summary.png')
|
|
568
|
+
fig_fk.savefig(savename)
|
|
569
|
+
S.pub.register_file(savename, 'take_noise', plot=True)
|
|
570
|
+
savename = os.path.join(save_dir, f'{ctime}_asd_summary.png')
|
|
571
|
+
fig_asd.savefig(savename)
|
|
572
|
+
S.pub.register_file(savename, 'take_noise', plot=True)
|
|
573
|
+
|
|
574
|
+
if plotted_rchans is not None:
|
|
575
|
+
outdict['channel_plots'] = {}
|
|
576
|
+
for rc in np.atleast_1d(plotted_rchans):
|
|
577
|
+
outdict['channel_plots'][rc] = {}
|
|
578
|
+
fig, axes = plot_channel_noise(am, rc, save_dir=save_dir,
|
|
579
|
+
noisedict=noisedict,
|
|
580
|
+
show_plot=show_plot,
|
|
581
|
+
save_plot=save_plot,
|
|
582
|
+
plot1overfregion=plot1overfregion,
|
|
583
|
+
**asd_args)
|
|
584
|
+
outdict['channel_plots'][rc]['fig'] = fig
|
|
585
|
+
outdict['channel_plots'][rc]['axes'] = axes
|
|
586
|
+
fname = os.path.join(S.output_dir, f'{ctime}_take_noise.npy')
|
|
587
|
+
outdict['path'] = fname
|
|
588
|
+
sdl.validate_and_save(fname, outdict, S=S, cfg=cfg, make_path=False)
|
|
589
|
+
return am, outdict
|
|
590
|
+
|
|
591
|
+
def plot_noise_all(res, range=(0, 200), text_loc=(0.4, 0.7)):
|
|
592
|
+
"""
|
|
593
|
+
Plots the white noise distribution of all bands together.
|
|
594
|
+
|
|
595
|
+
Args
|
|
596
|
+
-----
|
|
597
|
+
res : dict
|
|
598
|
+
Result from the ``take_noise`` function
|
|
599
|
+
range : tuple
|
|
600
|
+
Range of the histogram
|
|
601
|
+
text_loc : tuple
|
|
602
|
+
Location to place the textbox containing median and file info. These
|
|
603
|
+
coordinates are in the axis tranform frame with (0, 0) being the bottom
|
|
604
|
+
left and (1, 1) being the top right.
|
|
605
|
+
"""
|
|
606
|
+
pars = res['noise_pars']
|
|
607
|
+
wls = pars[:, 0]
|
|
608
|
+
fig, ax = plt.subplots()
|
|
609
|
+
hs = ax.hist(wls, range=range, bins=40)
|
|
610
|
+
wlmed = np.nanmedian(wls)
|
|
611
|
+
ch_pict = int(np.sum(hs[0]))
|
|
612
|
+
ch_tot = len(wls)
|
|
613
|
+
ax.axvline(wlmed, color='red', ls='--')
|
|
614
|
+
txt = '\n'.join([
|
|
615
|
+
f'Median: {wlmed:0.2f} pA/rt(Hz)',
|
|
616
|
+
f'{ch_pict}/{ch_tot} chans pictured',
|
|
617
|
+
f"sid: {res['sid']}",
|
|
618
|
+
f"stream_id: {res['meta']['stream_id']}",
|
|
619
|
+
f"path: {os.path.basename(res['path'])}"
|
|
620
|
+
])
|
|
621
|
+
ax.text(*text_loc, txt, transform=ax.transAxes,
|
|
622
|
+
bbox=dict(facecolor='wheat', alpha=0.7))
|
|
623
|
+
ax.set_xlabel("White Noise (pA/rt(Hz))")
|
|
624
|
+
return fig, ax
|