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/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import sodetlib as sdl
|
|
2
|
+
import numpy as np
|
|
3
|
+
import time
|
|
4
|
+
from scipy import signal
|
|
5
|
+
from tqdm.auto import trange
|
|
6
|
+
from pysmurf.client.base.smurf_control import SmurfControl
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
|
|
9
|
+
def check_packet_loss(Ss, cfgs, dur=10, fr_khz=4, nchans=2000, slots=None, downsample_factor=1):
|
|
10
|
+
"""
|
|
11
|
+
Takes a short G3 Stream on multiple slots simultaneously and checks for
|
|
12
|
+
dropped samples. This function is strange since it requires simultaneous
|
|
13
|
+
streaming on multiple slots to properly test, so it doesn't follow the
|
|
14
|
+
standard sodetlib function / data format.
|
|
15
|
+
|
|
16
|
+
Args
|
|
17
|
+
-----
|
|
18
|
+
Ss : dict[SmurfController]1
|
|
19
|
+
Dict of pysmurf instances where the key is the slot-number
|
|
20
|
+
cfgs : dict[DetConfig]
|
|
21
|
+
Dict of DetConfigs where the key is the slot number
|
|
22
|
+
dur : float
|
|
23
|
+
Duration of data stream (sec)
|
|
24
|
+
fr_khz : float
|
|
25
|
+
Frequency of FR rate (khz)
|
|
26
|
+
nchans : int
|
|
27
|
+
Number of channels to stream
|
|
28
|
+
slots : list
|
|
29
|
+
Which slots to stream data on. If None, will stream on all slots in the
|
|
30
|
+
Ss object.
|
|
31
|
+
downsample_factor : int
|
|
32
|
+
Downsample factor to use
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
--------
|
|
36
|
+
ams : dict[AxisManagers]
|
|
37
|
+
Dict of axis managers indexed by slot-number
|
|
38
|
+
res : dict
|
|
39
|
+
Dict where the key is the slot number, and the values are dicts
|
|
40
|
+
containing frame counters, number of dropped frames, etc.
|
|
41
|
+
"""
|
|
42
|
+
if slots is None:
|
|
43
|
+
slots = Ss.keys()
|
|
44
|
+
|
|
45
|
+
for s in slots:
|
|
46
|
+
S = Ss[s]
|
|
47
|
+
S.flux_ramp_setup(fr_khz, 0.4, band=0)
|
|
48
|
+
sdl.stream_g3_on(
|
|
49
|
+
S, channel_mask=np.arange(nchans), downsample_factor=downsample_factor,
|
|
50
|
+
subtype='check_packet_loss'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
time.sleep(dur)
|
|
54
|
+
|
|
55
|
+
sids = {}
|
|
56
|
+
for s in slots:
|
|
57
|
+
sids[s] = sdl.stream_g3_off(Ss[s])
|
|
58
|
+
|
|
59
|
+
ams = {}
|
|
60
|
+
for s, sid in sids.items():
|
|
61
|
+
ams[s] = sdl.load_session(cfgs[s].stream_id, sid, no_signal=True)
|
|
62
|
+
|
|
63
|
+
res = {}
|
|
64
|
+
for s, am in ams.items():
|
|
65
|
+
dropped_samps = np.sum(np.diff(am.primary['FrameCounter']) - 1)
|
|
66
|
+
total_samps = len(am.primary['FrameCounter'])
|
|
67
|
+
res[s] = {
|
|
68
|
+
'sid': sids[s],
|
|
69
|
+
'meta': sdl.get_metadata(Ss[s], cfgs[s]),
|
|
70
|
+
'frame_counter': am.primary['FrameCounter'],
|
|
71
|
+
'dropped_samples': dropped_samps,
|
|
72
|
+
'dropped_frac': dropped_samps / total_samps,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return ams, res
|
|
76
|
+
|
|
77
|
+
@sdl.set_action()
|
|
78
|
+
def measure_bias_line_resistances(
|
|
79
|
+
S: SmurfControl, cfg, vstep=0.001, bgs=None, sleep_time=2.0):
|
|
80
|
+
"""
|
|
81
|
+
Function to measure the bias line resistance and high-low-current-ratio for
|
|
82
|
+
each bias group. This needs to be run with the smurf hooked up to the
|
|
83
|
+
cryostat and the detectors superconducting, and a bgmap should exist.
|
|
84
|
+
|
|
85
|
+
Args
|
|
86
|
+
-------
|
|
87
|
+
S : SmurfControl
|
|
88
|
+
Pysmurf instance
|
|
89
|
+
cfg : DetConfig
|
|
90
|
+
Det Config instance
|
|
91
|
+
vstep : float
|
|
92
|
+
Voltage step size (in low-current-mode volts)
|
|
93
|
+
bgs : list
|
|
94
|
+
Bias lines to measure. Will default to active bias lines
|
|
95
|
+
sleep_time : float
|
|
96
|
+
Time to wait at each step.
|
|
97
|
+
"""
|
|
98
|
+
if bgs is None:
|
|
99
|
+
bgs = cfg.dev.exp['active_bgs']
|
|
100
|
+
bgs = np.atleast_1d(bgs)
|
|
101
|
+
bgmap = np.load(cfg.dev.exp['bgmap_file'], allow_pickle=True).item()
|
|
102
|
+
bg_m = np.asarray(sum([bgmap['bgmap'] == bg for bg in bgs]), dtype=bool)
|
|
103
|
+
|
|
104
|
+
vbias = S.get_tes_bias_bipolar_array()
|
|
105
|
+
vb_low = vbias.copy()
|
|
106
|
+
vb_low[bgs] = 0
|
|
107
|
+
vb_high = vbias.copy()
|
|
108
|
+
vb_high[bgs] = vstep
|
|
109
|
+
segs = []
|
|
110
|
+
|
|
111
|
+
S.set_tes_bias_bipolar_array(vb_low)
|
|
112
|
+
sdl.set_current_mode(S, bgs, 0, const_current=False)
|
|
113
|
+
|
|
114
|
+
def take_step(bias_arr, sleep_time, wait_time=0.2):
|
|
115
|
+
S.set_tes_bias_bipolar_array(bias_arr)
|
|
116
|
+
time.sleep(wait_time)
|
|
117
|
+
t0 = time.time()
|
|
118
|
+
time.sleep(sleep_time)
|
|
119
|
+
t1 = time.time()
|
|
120
|
+
return (t0, t1)
|
|
121
|
+
|
|
122
|
+
sdl.stream_g3_on(S, subtype='measure_bias_line_resistance')
|
|
123
|
+
time.sleep(0.5)
|
|
124
|
+
|
|
125
|
+
segs.append(take_step(vb_low, sleep_time, wait_time=0.5))
|
|
126
|
+
segs.append(take_step(vb_high, sleep_time, wait_time=0.5))
|
|
127
|
+
|
|
128
|
+
S.set_tes_bias_bipolar_array(vb_low)
|
|
129
|
+
time.sleep(0.5)
|
|
130
|
+
sdl.set_current_mode(S, bgs, 1, const_current=False)
|
|
131
|
+
|
|
132
|
+
segs.append(take_step(vb_low, sleep_time, wait_time=0.05))
|
|
133
|
+
segs.append(take_step(vb_high, sleep_time, wait_time=0.05))
|
|
134
|
+
|
|
135
|
+
sid = sdl.stream_g3_off(S)
|
|
136
|
+
|
|
137
|
+
am = sdl.load_session(cfg.stream_id, sid)
|
|
138
|
+
ts = am.timestamps
|
|
139
|
+
sigs = []
|
|
140
|
+
for (t0, t1) in segs:
|
|
141
|
+
m = (t0 < ts) & (ts < t1)
|
|
142
|
+
sigs.append(np.mean(am.signal[bg_m][:, m], axis=1) * S.pA_per_phi0 / (2*np.pi))
|
|
143
|
+
|
|
144
|
+
Rbl_low = vstep / (np.abs(sigs[1] - sigs[0]) * 1e-12)
|
|
145
|
+
Rbl_high = vstep / (np.abs(sigs[3] - sigs[2]) * 1e-12)
|
|
146
|
+
high_low_ratio = Rbl_low / Rbl_high
|
|
147
|
+
|
|
148
|
+
cfg.dev.exp['bias_line_resistance'] = np.nanmedian(Rbl_low)
|
|
149
|
+
cfg.dev.exp['high_low_current_ratio'] = np.nanmedian(high_low_ratio)
|
|
150
|
+
cfg.dev.update_file()
|
|
151
|
+
|
|
152
|
+
path = sdl.make_filename(S, 'measure_bias_line_info')
|
|
153
|
+
data = {
|
|
154
|
+
'Rbl_low_all': Rbl_low,
|
|
155
|
+
'Rbl_high_all': Rbl_high,
|
|
156
|
+
'high_low_ratio_all': high_low_ratio,
|
|
157
|
+
'bias_line_resistance': np.nanmedian(Rbl_low),
|
|
158
|
+
'high_current_mode_resistance': np.nanmedian(Rbl_high),
|
|
159
|
+
'high_low_ratio': np.nanmedian(high_low_ratio),
|
|
160
|
+
'sid': sid,
|
|
161
|
+
'vstep': vstep,
|
|
162
|
+
'bgs': bgs,
|
|
163
|
+
'meta': sdl.get_metadata(S, cfg),
|
|
164
|
+
'segs': segs,
|
|
165
|
+
'sigs': sigs,
|
|
166
|
+
'path': path,
|
|
167
|
+
}
|
|
168
|
+
np.save(path, data, allow_pickle=True)
|
|
169
|
+
S.pub.register_file(path, 'bias_line_resistances', format='npy')
|
|
170
|
+
|
|
171
|
+
return am, data
|
|
172
|
+
|
|
173
|
+
def setup_fixed_tones(S, cfg, tones_per_band=256, bands=None, jitter=0.5,
|
|
174
|
+
tone_power=None):
|
|
175
|
+
"""
|
|
176
|
+
Enables many fixed tones across a selection of bands.
|
|
177
|
+
|
|
178
|
+
Args
|
|
179
|
+
----------
|
|
180
|
+
S : SmurfControl
|
|
181
|
+
Pysmurf instance
|
|
182
|
+
cfg : DetConfig
|
|
183
|
+
Det config instance
|
|
184
|
+
tones_per_band : int
|
|
185
|
+
Number of fixed tones to create in each band
|
|
186
|
+
bands : int, list[int]
|
|
187
|
+
Bands to set fixed tones in. Defaults to all 8.
|
|
188
|
+
jitter : float
|
|
189
|
+
Noise [Mhz] to add to the center freq so that fixed tones are not
|
|
190
|
+
equispaced.
|
|
191
|
+
tone_power : int
|
|
192
|
+
Tone power of fixed tones.
|
|
193
|
+
"""
|
|
194
|
+
if bands is None:
|
|
195
|
+
bands = np.arange(8)
|
|
196
|
+
bands = np.atleast_1d(bands)
|
|
197
|
+
|
|
198
|
+
chans_per_band = S.get_number_channels()
|
|
199
|
+
for band in bands:
|
|
200
|
+
S.log(f"Setting fixed tones for band {band}")
|
|
201
|
+
if tone_power is None:
|
|
202
|
+
tone_power = cfg.dev.bands[band]['tone_power']
|
|
203
|
+
|
|
204
|
+
sbs = np.linspace(0, chans_per_band, tones_per_band, dtype=int, endpoint=False)
|
|
205
|
+
asa = np.zeros_like(S.get_amplitude_scale_array(band))
|
|
206
|
+
asa[sbs] = tone_power
|
|
207
|
+
S.set_amplitude_scale_array(band, asa)
|
|
208
|
+
S.set_center_frequency_array(band, np.random.uniform(-jitter/2, jitter/2, chans_per_band))
|
|
209
|
+
S.set_feedback_enable_array(band, np.zeros(chans_per_band, dtype=int))
|
|
210
|
+
|
|
211
|
+
def get_noise_dBcHz(S, band, chan, nsamp=2**20, nperseg=2**16,
|
|
212
|
+
noise_freq=30e3, noise_bw=100):
|
|
213
|
+
"""
|
|
214
|
+
Takes debug data and measures I/Q noise in dBc/Hz.
|
|
215
|
+
|
|
216
|
+
Args
|
|
217
|
+
-----
|
|
218
|
+
S : SmurfControl
|
|
219
|
+
Pysmurf instance
|
|
220
|
+
band : int
|
|
221
|
+
Smurf band
|
|
222
|
+
chan : int
|
|
223
|
+
Smurf chan
|
|
224
|
+
nsamp : int
|
|
225
|
+
Number of samples to take
|
|
226
|
+
nperseg : int
|
|
227
|
+
Nperseg to use when creating the psd
|
|
228
|
+
noise_freq : float
|
|
229
|
+
Freq to measure the readout noise at.
|
|
230
|
+
noise_bw : float
|
|
231
|
+
Frequency bandwidth over which the noise median will be taken
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
---------
|
|
235
|
+
noise_i : float
|
|
236
|
+
Noise of the I data stream in dBc/Hz
|
|
237
|
+
noise_q : float
|
|
238
|
+
Noise of the Q data stream in dBc/Hz
|
|
239
|
+
"""
|
|
240
|
+
fsamp = S.get_channel_frequency_mhz() * 1e6
|
|
241
|
+
|
|
242
|
+
sig_i, sig_q, _ = S.take_debug_data(band, channel=chan, rf_iq=True, nsamp=nsamp)
|
|
243
|
+
datfile = S.get_streamdatawriter_datafile()
|
|
244
|
+
|
|
245
|
+
fs, pxxi = signal.welch(sig_i, fs=fsamp, nperseg=nperseg)
|
|
246
|
+
fs, pxxq = signal.welch(sig_q, fs=fsamp, nperseg=nperseg)
|
|
247
|
+
|
|
248
|
+
magfac = np.mean(sig_q)**2 + np.mean(sig_i)**2
|
|
249
|
+
pxxi_dbc = 10. * np.log10(pxxi/magfac)
|
|
250
|
+
pxxq_dbc = 10. * np.log10(pxxq/magfac)
|
|
251
|
+
|
|
252
|
+
f0, f1 = noise_freq - noise_bw/2, noise_freq + noise_bw/2
|
|
253
|
+
m = (f0 < fs) & (fs < f1)
|
|
254
|
+
noise_i = np.nanmedian(pxxi_dbc[m])
|
|
255
|
+
noise_q = np.nanmedian(pxxq_dbc[m])
|
|
256
|
+
|
|
257
|
+
return noise_i, noise_q, datfile
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def plot_fixed_tone_loopback(res):
|
|
261
|
+
"""Plot results from fixed_tone_loopback"""
|
|
262
|
+
fig, ax = plt.subplots()
|
|
263
|
+
fig.patch.set_facecolor('white')
|
|
264
|
+
|
|
265
|
+
ax.plot(res['freqs'], res['noise_q'], '.', alpha=0.8)
|
|
266
|
+
ax.plot(res['freqs'], res['noise_i'], '.', alpha=0.8)
|
|
267
|
+
txt = '\n'.join([
|
|
268
|
+
f"num tones: {len(res['ft_freqs_all'])}",
|
|
269
|
+
f"tone power: {res['tone_power']}",
|
|
270
|
+
f"UC att: {res['att_uc']}",
|
|
271
|
+
f"DC att: {res['att_dc']}",
|
|
272
|
+
])
|
|
273
|
+
ax.text(0.05, 0.8, txt, transform=ax.transAxes,
|
|
274
|
+
bbox=dict(fc='white', alpha=0.6))
|
|
275
|
+
ax.set_xlabel("Freq [MHz]")
|
|
276
|
+
ax.set_ylabel("Noise [dBc/Hz]")
|
|
277
|
+
crate_id = res['meta']['crate_id']
|
|
278
|
+
slot = res['meta']['slot']
|
|
279
|
+
ax.set_title(f"Crate {crate_id}, Slot {slot}")
|
|
280
|
+
|
|
281
|
+
return fig, ax
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def fixed_tone_loopback(
|
|
285
|
+
S, cfg, bands=None, tones_per_band=256, meas_chans_per_band=5,
|
|
286
|
+
setup_tones=False, tone_power=12, show_pb=True, noise_freq=30e3,
|
|
287
|
+
noise_bw=100, att_uc=None, att_dc=None):
|
|
288
|
+
"""
|
|
289
|
+
Runs QC test to check noise levels across band with many fixed tones enabled.
|
|
290
|
+
|
|
291
|
+
Args
|
|
292
|
+
------
|
|
293
|
+
S : SmurfControl
|
|
294
|
+
Pysmurf instance
|
|
295
|
+
cfg : DetConfig
|
|
296
|
+
Det config instance
|
|
297
|
+
bands : int, list[int]
|
|
298
|
+
Bands to run on. Default is all 8
|
|
299
|
+
tones_per_band : int
|
|
300
|
+
Number of fixed tones to enable per band. This defaults to 256, which
|
|
301
|
+
is 2048 tones total with all 8 bands enabled.
|
|
302
|
+
meas_chans_per_band : int
|
|
303
|
+
Number of channels per band to measure readout noise
|
|
304
|
+
setup_fixed_tones : bool
|
|
305
|
+
If true, will set up fixed tones across specified bands. If false,
|
|
306
|
+
this will skip the setup and assume tones are already set up.
|
|
307
|
+
tone_power : int
|
|
308
|
+
Tone power to use
|
|
309
|
+
show_pb : bool
|
|
310
|
+
If True will show a progress bar
|
|
311
|
+
noise_freq : float
|
|
312
|
+
Target frequency to measure the readout noise
|
|
313
|
+
noise_bw : float
|
|
314
|
+
Frequency bandwidth over which the noise median will be taken
|
|
315
|
+
att_uc : int
|
|
316
|
+
UC atten. If not set, will use current uc atten.
|
|
317
|
+
att_dc : int
|
|
318
|
+
DC atten. If not set, will use current dc atten
|
|
319
|
+
"""
|
|
320
|
+
if bands is None:
|
|
321
|
+
bands = np.arange(8)
|
|
322
|
+
bands = np.atleast_1d(bands)
|
|
323
|
+
|
|
324
|
+
if att_uc is None:
|
|
325
|
+
att_uc = S.get_att_uc(bands[0])
|
|
326
|
+
else:
|
|
327
|
+
for b in bands:
|
|
328
|
+
S.set_att_uc(b, att_uc)
|
|
329
|
+
|
|
330
|
+
if att_dc is None:
|
|
331
|
+
att_dc = S.get_att_dc(bands[0])
|
|
332
|
+
else:
|
|
333
|
+
for b in bands:
|
|
334
|
+
S.set_att_dc(b, att_dc)
|
|
335
|
+
|
|
336
|
+
if setup_tones:
|
|
337
|
+
setup_fixed_tones(S, cfg, tones_per_band=tones_per_band, bands=bands,
|
|
338
|
+
tone_power=tone_power)
|
|
339
|
+
else: # Just update the tone power
|
|
340
|
+
S.log(f"Setting tone power to {tone_power} for bands {bands}...")
|
|
341
|
+
for band in bands:
|
|
342
|
+
asa = S.get_amplitude_scale_array(band)
|
|
343
|
+
if tone_power in np.unique(asa):
|
|
344
|
+
continue
|
|
345
|
+
asa[asa != 0] = tone_power
|
|
346
|
+
S.set_amplitude_scale_array(band, asa)
|
|
347
|
+
|
|
348
|
+
meas_bands = []
|
|
349
|
+
meas_chans = []
|
|
350
|
+
meas_freqs = []
|
|
351
|
+
ft_chans_all = []
|
|
352
|
+
ft_freqs_all = []
|
|
353
|
+
ft_bands_all = []
|
|
354
|
+
S.log("Finding fixed tones and meas_channels")
|
|
355
|
+
for band in bands:
|
|
356
|
+
freqs = S.get_center_frequency_array(band) \
|
|
357
|
+
+ S.get_tone_frequency_offset_mhz(band) \
|
|
358
|
+
+ S.get_band_center_mhz(band)
|
|
359
|
+
|
|
360
|
+
ft_chans = np.where(S.get_amplitude_scale_array(band))[0]
|
|
361
|
+
freqs = freqs[ft_chans]
|
|
362
|
+
sort_idx = np.argsort(freqs)
|
|
363
|
+
meas_idx = np.unique(np.round(np.linspace(
|
|
364
|
+
0, len(ft_chans) - 1, meas_chans_per_band)).astype(int))
|
|
365
|
+
# We want to sort first so meas_chans are evenly distributed across
|
|
366
|
+
# freq space
|
|
367
|
+
meas_chans.append(ft_chans[sort_idx][meas_idx])
|
|
368
|
+
meas_bands.append([band for _ in meas_idx])
|
|
369
|
+
meas_freqs.append(freqs[sort_idx][meas_idx])
|
|
370
|
+
ft_chans_all.append(ft_chans)
|
|
371
|
+
ft_bands_all.append([band for _ in ft_chans])
|
|
372
|
+
ft_freqs_all.append(freqs)
|
|
373
|
+
meas_bands = np.hstack(meas_bands)
|
|
374
|
+
meas_chans = np.hstack(meas_chans)
|
|
375
|
+
meas_freqs = np.hstack(meas_freqs)
|
|
376
|
+
ft_chans_all = np.hstack(ft_chans_all)
|
|
377
|
+
ft_bands_all = np.hstack(ft_freqs_all)
|
|
378
|
+
ft_freqs_all = np.hstack(ft_freqs_all)
|
|
379
|
+
|
|
380
|
+
noise_i = np.full_like(meas_freqs, np.nan)
|
|
381
|
+
noise_q = np.full_like(meas_freqs, np.nan)
|
|
382
|
+
datfiles = []
|
|
383
|
+
for i in trange(len(meas_bands), disable=not(show_pb)):
|
|
384
|
+
b, c = meas_bands[i], meas_chans[i]
|
|
385
|
+
S.log(f"Band {b}, Chan {c}")
|
|
386
|
+
try:
|
|
387
|
+
noise_i[i], noise_q[i], _ = get_noise_dBcHz(
|
|
388
|
+
S, b, c, noise_freq=noise_freq,
|
|
389
|
+
noise_bw=noise_bw)
|
|
390
|
+
except IndexError as e:
|
|
391
|
+
S.log(f"Take Data failed...\n{e}")
|
|
392
|
+
S.log("Skipping channel")
|
|
393
|
+
datfiles.append(S.get_streamdatawriter_datafile())
|
|
394
|
+
|
|
395
|
+
fname = sdl.make_filename(S, 'fixed_tone_loopback.npy')
|
|
396
|
+
res = dict(
|
|
397
|
+
meta=sdl.get_metadata(S, cfg),
|
|
398
|
+
bands=meas_bands, channels=meas_chans, freqs=meas_freqs,
|
|
399
|
+
noise_i=noise_i, noise_q=noise_q,
|
|
400
|
+
ft_bands_all=ft_bands_all, ft_channels_all=ft_chans_all,
|
|
401
|
+
ft_freqs_all=ft_freqs_all, noise_freq=noise_freq,
|
|
402
|
+
att_uc=att_uc, att_dc=att_dc, tone_power=tone_power,
|
|
403
|
+
datfiles=datfiles
|
|
404
|
+
)
|
|
405
|
+
np.save(fname, res, allow_pickle=True)
|
|
406
|
+
S.pub.register_file(fname, 'loopback', format='npy')
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
fname = sdl.make_filename(S, 'fixed_tone_loopback.png', plot=True)
|
|
410
|
+
fig, _ = plot_fixed_tone_loopback(res)
|
|
411
|
+
fig.savefig(fname)
|
|
412
|
+
S.pub.register_file(fname, 'loopback', format='png', plot=True)
|
|
413
|
+
|
|
414
|
+
return res
|
|
415
|
+
|