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/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
@@ -0,0 +1,5 @@
1
+ from sodetlib.operations.bias_steps import take_bias_steps, take_bgmap
2
+ from sodetlib.operations.iv import take_iv
3
+ from sodetlib.operations.bias_dets import (
4
+ bias_to_rfrac_range, bias_to_rfrac
5
+ )