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.
@@ -0,0 +1,624 @@
1
+ import sodetlib as sdl
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.patches import Rectangle
5
+
6
+
7
+ def compute_tracking_quality(S, f, df, sync):
8
+ """
9
+ Computes the tracking quality parameter from tracking_setup results.
10
+ Tracking quality is a measure of how periodic the tracking response is with
11
+ respect to the flux-ramp. It is defined as the r-squared value between the
12
+ measured frequency response (f+df), and the (f+df) response averaged across
13
+ all flux-ramp periods. If the signal is regular with respect to flux-ramp,
14
+ the true freq response will be similar to the average freq response,
15
+ resulting in a high r-squared value. If the signal is irregular, the true
16
+ and average freq response will differ, resulting in a lower r-squared
17
+ value. This can be used in conjunction with the f and df peak-to-peak
18
+ values to determine which channels are tracking correctly.
19
+
20
+ Args
21
+ ------
22
+ S : SmurfControl
23
+ Pysmurf instance
24
+ f : np.ndarray
25
+ Array of the tracked frequency for each channelk (kHz), as returned by
26
+ tracking_setup
27
+ df : np.ndarray
28
+ Array of the tracked frequency error for each channel (kHz), as
29
+ returned by tracking_setup
30
+ sync : np.ndarray
31
+ Array containing tracking sync flags, as returned by tracking_setup
32
+ """
33
+ sync_idxs = S.make_sync_flag(sync)
34
+ seg_size = np.min(np.diff(sync_idxs))
35
+ nstacks = len(sync_idxs) - 1
36
+ nchans = len(f[0])
37
+ sig = f + df
38
+
39
+ fstack = np.zeros((seg_size, nchans))
40
+ for i in range(nstacks):
41
+ si = sync_idxs[i]
42
+ fstack[:seg_size] += (sig)[si:si+seg_size] / nstacks
43
+
44
+ # calculates quality of estimate wrt real data
45
+ y_real = (sig)[sync_idxs[0]:sync_idxs[0] + nstacks * seg_size, :]
46
+ y_est = np.vstack([fstack for _ in range(nstacks)])
47
+
48
+ # Force these to be the same len in case all segments are not the same size
49
+ y_est = y_est[:len(y_real)]
50
+
51
+ with np.errstate(invalid='ignore'):
52
+ sstot = np.sum((y_real - np.nanmean(y_real, axis=0))**2, axis=0)
53
+ ssres = np.sum((y_real - y_est)**2, axis=0)
54
+ r2 = 1 - ssres/sstot
55
+
56
+ return r2
57
+
58
+
59
+ class TrackingResults:
60
+ """
61
+ Class for storing, saving, and interpreting results from tracking_setup.
62
+ This class can store tracking results for multiple bands at a time.
63
+ When created, all results array are initialized to be empty. To add
64
+ results from individual bands one at a time, use the ``add_band_data``
65
+ function.
66
+
67
+ Args
68
+ ----
69
+ S : SmurfControl
70
+ Pysmurf Instance
71
+ cfg : DetConfig
72
+ DetConfig
73
+
74
+ Attributes
75
+ --------------
76
+ meta : dict
77
+ Metadata dictionary
78
+ f_ptp_range : np.ndarray
79
+ Array of len(2) containing the min and max allowed f_ptp. This will
80
+ be pulled from the dev cfg.
81
+ df_ptp_range : np.ndarray
82
+ Array of len(2) containing the min and max allowed df_ptp. This will
83
+ be pulled from the dev cfg.
84
+ r2_min : float
85
+ Min value of r2 for a channel to be considered good
86
+ bands : np.ndarray
87
+ Array of len (nchans) containing the band of each channel
88
+ channels : np.ndarray
89
+ Array of shape (nchans) containing the smurf-channel of each channel
90
+ f : np.ndarray
91
+ Array of shape (nchans, nsamps) containing the tracked freq response
92
+ (kHz) throughout the tracking setup call
93
+ df : np.ndarray
94
+ Array of shape (nchans, nsamps) containing the untracked freq response
95
+ (kHz) throughout the tracking setup call
96
+ sync_idx : np.ndarray
97
+ Array of shape (nchans, num_fr_periods) containing the indices where
98
+ the flux ramp resetted
99
+ r2 : np.ndarray
100
+ Array of shape (nchans) containing the r-squared value computed by
101
+ tracking-quality for each channel
102
+ f_ptp : np.ndarray
103
+ Array of shape (nchans) containing the f_ptp of each channel
104
+ df_ptp : np.ndarray
105
+ Array of shape (nchans) containing the df_ptp of each channel
106
+ is_good : np.ndarray
107
+ Array of shape (nchans) containing True if the channel passes cuts
108
+ and False otherwise
109
+ """
110
+ def __init__(self, *args, **kwargs):
111
+ if len(args) > 0:
112
+ self.initialize(*args, **kwargs)
113
+
114
+ def initialize(self, S, cfg):
115
+ self._S = S
116
+ self._cfg = cfg
117
+ self.meta = sdl.get_metadata(S, cfg)
118
+ self.f_ptp_range = np.array(cfg.dev.exp['f_ptp_range'])
119
+ self.df_ptp_range = np.array(cfg.dev.exp['df_ptp_range'])
120
+ self.r2_min = cfg.dev.exp['r2_min']
121
+
122
+ self.bands = np.array([], dtype=int)
123
+ self.channels = np.array([], dtype=int)
124
+ self.subband_centers = np.array([], dtype=float)
125
+ self.nchans = 0
126
+ self.ngood = 0
127
+
128
+ self.f = None
129
+ self.df = None
130
+ self.tracking_kwargs = [None for _ in range(8)]
131
+ self.sync_idxs = [None for _ in range(8)]
132
+ self.r2 = np.array([], dtype=float)
133
+ self.f_ptp = np.array([], dtype=float)
134
+ self.df_ptp = np.array([], dtype=float)
135
+ self.is_good = np.array([], dtype=bool)
136
+
137
+ def add_band_data(self, band, f, df, sync, tracking_kwargs=None):
138
+ """
139
+ Computes tracking-related data based on the tracking response,
140
+ and updates the arrays described in the attributes with channels
141
+ from a new band
142
+
143
+ Args
144
+ -----
145
+ band : int
146
+ Band of the data you're adding
147
+ f : np.ndarray
148
+ Tracked freq response as returned by tracking_setup
149
+ df : np.ndarray
150
+ Untracked freq response as returned by tracking_setup
151
+ sync : np.ndarray
152
+ sync arrayas returned by tracking_setup
153
+ """
154
+ if band in self.bands:
155
+ raise ValueError(f"Data for band {band} has already been added!")
156
+
157
+ dfptp = np.ptp(df, axis=0)
158
+ m = dfptp != 0
159
+
160
+ channels = np.where(m)[0]
161
+ nchans = len(channels)
162
+ bands = np.array([band for _ in channels])
163
+ sb_centers = np.array(
164
+ self._S.get_subband_centers(band, as_offset=False)[1]
165
+ )
166
+ self.channels = np.concatenate((self.channels, channels))
167
+ self.bands = np.concatenate((self.bands, bands))
168
+ self.subband_centers = np.concatenate((
169
+ self.subband_centers, sb_centers[channels]))
170
+
171
+ self.tracking_kwargs[band] = tracking_kwargs
172
+ r2 = compute_tracking_quality(self._S, f, df, sync)
173
+
174
+ if self.f is not None:
175
+ # It's possible for different tracking setup calls to have a
176
+ # slightly different number of samples, so we have to make sure
177
+ # we cut/elongate results so they can fit into the f/df arrays
178
+ nsamps = len(self.f[0])
179
+ _f = np.full((nchans, nsamps), np.nan)
180
+ _df = np.full((nchans, nsamps), np.nan)
181
+ fi = min(nsamps, len(f))
182
+ _f[:, :fi] = f.T[m, :fi] * 1000
183
+ _df[:, :fi] = df.T[m, :fi] * 1000
184
+ if fi < nsamps:
185
+ # Fill with the last data point to not mess up ptp calcs....
186
+ _f[:, fi:] = _f[:, fi-1][:, None]
187
+ _df[:, fi:] = _df[:, fi-1][:, None]
188
+ else:
189
+ _f = f.T[m] * 1000
190
+ _df = df.T[m] * 1000
191
+
192
+ self.r2 = np.concatenate((self.r2, r2[m]))
193
+
194
+ f_ptp = np.ptp(_f, axis=1)
195
+ self.f_ptp = np.concatenate((self.f_ptp, f_ptp))
196
+
197
+ df_ptp = np.ptp(_df, axis=1)
198
+ self.df_ptp = np.concatenate((self.df_ptp, df_ptp))
199
+
200
+ is_good = np.ones_like(r2, dtype=bool)
201
+ self.is_good = np.concatenate((self.is_good, is_good))
202
+
203
+ if self.f is None:
204
+ self.f = _f
205
+ self.df = _df
206
+ else:
207
+ nsamps = len(self.f[0])
208
+ self.f = np.concatenate((self.f, _f[:, :nsamps]), axis=0)
209
+ self.df = np.concatenate((self.df, _df[:, :nsamps]), axis=0)
210
+
211
+ self.sync_idxs[band] = self._S.make_sync_flag(sync)
212
+ self.nchans += len(_f)
213
+ self.find_bad_chans()
214
+
215
+ def find_bad_chans(self, f_ptp_range=None, df_ptp_range=None, r2_min=None):
216
+ """
217
+ Recomputes the ``is_good`` array based on cuts ranges.
218
+ """
219
+ if f_ptp_range is None:
220
+ f_ptp_range = self.f_ptp_range
221
+ if df_ptp_range is None:
222
+ df_ptp_range = self.df_ptp_range
223
+ if r2_min is None:
224
+ r2_min = self.r2_min
225
+
226
+ f0, f1 = f_ptp_range
227
+ df0, df1 = df_ptp_range
228
+
229
+ self.is_good = np.ones_like(self.r2, dtype=bool)
230
+ self.is_good[self.r2 < r2_min] = 0
231
+ self.is_good[~((f0 < self.f_ptp) & (self.f_ptp < f1))] = 0
232
+ self.is_good[~((df0 < self.df_ptp) & (self.df_ptp < df1))] = 0
233
+ self.ngood = np.sum(self.is_good)
234
+
235
+ def save(self, path=None):
236
+ saved_fields = [
237
+ 'meta', 'bands', 'channels',
238
+ 'f_ptp_range', 'df_ptp_range', 'r2_min',
239
+ 'f', 'df', 'sync_idxs', 'r2', 'is_good',
240
+ 'tracking_kwargs', 'f_ptp', 'df_ptp',
241
+ ]
242
+ data = {}
243
+ for field in saved_fields:
244
+ data[field] = getattr(self, field)
245
+ if path is None:
246
+ path = sdl.make_filename(self._S, 'tracking_results.npy')
247
+
248
+ self.filepath = path
249
+ np.save(path, data, allow_pickle=True)
250
+ if self._S is not None:
251
+ self._S.pub.register_file(path, 'tracking_data', format='npy')
252
+
253
+ @classmethod
254
+ def load(cls, path):
255
+ self = cls()
256
+ for k, v in np.load(path, allow_pickle=True).item().items():
257
+ setattr(self, k, v)
258
+ return self
259
+
260
+
261
+ def plot_tracking_channel(tr, idx, show_text=True):
262
+ """
263
+ Plots single tracking channel from results
264
+ """
265
+ f = tr.f[idx]
266
+ df = tr.df[idx]
267
+ fig, ax = plt.subplots(figsize=(12, 4))
268
+ band = int(tr.bands[idx])
269
+ channel = int(tr.channels[idx])
270
+
271
+ ax.set_title(f"Band {band} Channel {channel}")
272
+ ax.plot(df+f, color='grey', alpha=0.8, label='Freq Response')
273
+ ax.plot(f, label='Tracked Freq')
274
+ ax.set_xticks([])
275
+ if hasattr(tr, 'subband_centers'):
276
+ ax.set_ylabel(f"Freq offset from {tr.subband_centers[idx]:0.2f} [kHz]")
277
+ else:
278
+ ax.set_ylabel(f"Freq offset [kHz]")
279
+ ax.legend(loc='upper left')
280
+
281
+ if show_text:
282
+ txt = '\n'.join([
283
+ f'fptp = {tr.f_ptp[idx]:0.2f}',
284
+ f'dfptp = {tr.df_ptp[idx]:0.2f}',
285
+ f'r2 = {tr.r2[idx]:0.2f}',
286
+ f'is_good = {tr.is_good[idx]}',
287
+ ])
288
+ bbox = dict(facecolor='white', alpha=0.8)
289
+ ax.text(0.02, 0.1, txt, transform=ax.transAxes, bbox=bbox)
290
+ for s in tr.sync_idxs[band]:
291
+ ax.axvline(s, color='grey', ls='--')
292
+ return fig, ax
293
+
294
+
295
+ def plot_tracking_summary(tr):
296
+ """
297
+ Plots summary of tracking results
298
+ """
299
+ # fig, axes = plt.subplots(figsize=(12, 8))
300
+ mosaic = """
301
+ BBBB.
302
+ AAAAC
303
+ AAAAC
304
+ AAAAC
305
+ AAAAC
306
+ """
307
+ gs = dict(hspace=0, wspace=0)
308
+ fig, axes = plt.subplot_mosaic(mosaic, figsize=(12, 8), gridspec_kw=gs)
309
+ f0, f1 = tr.f_ptp_range
310
+ df0, df1 = tr.df_ptp_range
311
+
312
+ m = tr.is_good
313
+ ax = axes['A']
314
+ ax.scatter(tr.f_ptp[m], tr.df_ptp[m], marker='.', alpha=0.8)
315
+ ax.scatter(tr.f_ptp[~m], tr.df_ptp[~m], marker='.', color='red', alpha=0.2)
316
+ xlim = (-5, f1 * 1.3)
317
+ ylim = (-5, df1 * 1.3)
318
+ ax.set_xlim(*xlim)
319
+ ax.set_ylim(*ylim)
320
+ rect = Rectangle((f0, df0), f1-f0, df1-df0, fc='green', alpha=0.2,
321
+ linestyle='--', ec='black', lw=3)
322
+ ax.add_patch(rect)
323
+ ax.set_ylabel(r"$df_\mathrm{ptp}$ (kHz)", fontsize=20)
324
+ ax.set_xlabel(r"$f_\mathrm{ptp}$ (kHz)", fontsize=20)
325
+
326
+ text = f"{tr.ngood} / {tr.nchans} good tracking channels"
327
+ bbox = dict(fc='wheat', alpha=0.8)
328
+ ax.text(0.02, 0.9, text, transform=ax.transAxes, bbox=bbox, fontsize=16)
329
+
330
+ ax = axes['B']
331
+ ax.set_xticks([])
332
+ ax.hist(tr.f_ptp, range=xlim, bins=40)
333
+ ax.set_xlim(*xlim)
334
+ ax.axvspan(f0, f1, color='green', alpha=0.2)
335
+
336
+ ax = axes['C']
337
+ ax.set_yticks([])
338
+ ax.hist(tr.df_ptp, range=ylim, bins=40, orientation='horizontal')
339
+ ax.set_ylim(*ylim)
340
+ ax.axhspan(df0, df1, color='green', alpha=0.2)
341
+
342
+ return fig, ax
343
+
344
+
345
+ def disable_bad_chans(S, tr, bands=None, **kwargs):
346
+ """
347
+ Disables cut channels based on a TrackingResults object.
348
+ """
349
+ tr.find_bad_chans(**kwargs)
350
+ if bands is None:
351
+ bands = np.arange(8)
352
+
353
+ bands = np.atleast_1d()
354
+ for b in bands:
355
+ m = tr.bands == b
356
+ asa = S.get_amplitude_scale_array(b)
357
+ fea = S.get_feedback_enable_array(b)
358
+ for good, c in zip(tr.is_good[m], tr.channels[m]):
359
+ if not good:
360
+ asa[c] = 0
361
+ fea[c] = 0
362
+ S.set_amplitude_scale_array(b, asa)
363
+ S.set_feedback_enable_array(b, asa)
364
+
365
+
366
+ @sdl.set_action()
367
+ def setup_tracking_params(S, cfg, bands, update_cfg=True, show_plots=False):
368
+ """
369
+ Sets up tracking parameters by determining correct frac-pp and lms-freq
370
+ for each band.
371
+
372
+ Args
373
+ -----
374
+ S : SmurfControl
375
+ Pysmurf instance
376
+ cfg : DetConfig
377
+ DetConfig instance
378
+ bands : np.ndarray, int
379
+ Band or list of bands to run on
380
+ update_cfg : bool
381
+ If true, will update the device cfg and save the file.
382
+ show_plots : bool
383
+ If true, will show summary plots
384
+ """
385
+
386
+ bands = np.atleast_1d(bands)
387
+ exp = cfg.dev.exp
388
+
389
+ res = TrackingResults(S, cfg)
390
+
391
+ tk = {
392
+ 'reset_rate_khz': exp['flux_ramp_rate_khz'],
393
+ 'make_plot': False, 'show_plot': False, 'channel': [],
394
+ 'nsamp': 2**18, 'return_data': True,
395
+ 'feedback_start_frac': exp['feedback_start_frac'],
396
+ 'feedback_end_frac': exp['feedback_end_frac'],
397
+ }
398
+ for band in bands:
399
+ sdl.pub_ocs_log(S, f"Setting up trackng params: band {band}")
400
+ bcfg = cfg.dev.bands[band]
401
+ tk.update({
402
+ 'lms_freq_hz': None,
403
+ 'meas_lms_freq': True,
404
+ 'fraction_full_scale': exp['init_frac_pp'],
405
+ 'reset_rate_khz': exp['flux_ramp_rate_khz'],
406
+ 'lms_gain': bcfg['lms_gain'],
407
+ 'feedback_gain': bcfg['feedback_gain'],
408
+ })
409
+
410
+ f, df, sync = S.tracking_setup(band, **tk)
411
+ tr = TrackingResults(S, cfg)
412
+ tr.add_band_data(band, f, df, sync)
413
+ asa_init = S.get_amplitude_scale_array(band)
414
+ disable_bad_chans(S, tr, bands=band, r2_min=0.95)
415
+
416
+ # Calculate trracking parameters
417
+ S.tracking_setup(band, **tk)
418
+ lms_meas = S.lms_freq_hz[band]
419
+ lms_freq = exp['nphi0'] * tk['reset_rate_khz'] * 1e3
420
+ frac_pp = tk['fraction_full_scale'] * lms_freq / lms_meas
421
+
422
+ # Re-enables all channels and re-run tracking setup with correct params
423
+ S.set_amplitude_scale_array(band, asa_init)
424
+ tk.update({
425
+ 'meas_lms_freq': False,
426
+ 'fraction_full_scale': frac_pp,
427
+ 'lms_freq_hz': lms_freq,
428
+ })
429
+
430
+ f, df, sync = S.tracking_setup(band, **tk)
431
+ res.add_band_data(band, f, df, sync, tracking_kwargs=tk)
432
+
433
+ # Print tracking params
434
+ d = {
435
+ 'frac_pp': frac_pp,
436
+ 'lms_freq_hz': lms_freq,
437
+ }
438
+ S.log(f"Tracking params found for band {band}: {d}")
439
+
440
+ # Update det config
441
+ if update_cfg:
442
+ cfg.dev.update_band(band, d, update_file=True)
443
+
444
+ res.save()
445
+
446
+ S.log("Running relock tracking setup...")
447
+ res = relock_tracking_setup(S, cfg, bands=bands, show_plots=show_plots)
448
+
449
+ return res
450
+
451
+
452
+ def _get_fixed_tone_channels(S, cfg, bands=None):
453
+ """
454
+ Identify channels assigned to fixed tones by checking state of amplitude and
455
+ feedback arrays.
456
+
457
+ Args
458
+ ----
459
+ S : SmurfControl
460
+ Pysmurf instance
461
+ cfg : DetConfig
462
+ Det config instance
463
+ bands : list of int
464
+ Bands to operate on. Get from config by default.
465
+ """
466
+
467
+ if bands is None:
468
+ bands = cfg.dev.exp['active_bands']
469
+
470
+ # infer from amplitude and feedback state
471
+ ft_ind = {}
472
+ for band in bands:
473
+ amp = S.get_amplitude_scale_array(band)
474
+ feedback = S.get_feedback_enable_array(band)
475
+ ft_ind[band] = np.where((amp != 0) & (feedback == 0))[0]
476
+
477
+ return ft_ind
478
+
479
+
480
+ def _restore_fixed_tone_channels(S, chan_per_band: dict):
481
+ """
482
+ Ensure feedback is disabled on channels that are assigned fixed tones.
483
+
484
+ Args
485
+ ----
486
+ S : SmurfControl
487
+ Pysmurf instance
488
+ chan_per_band : dict
489
+ List of channels to disable feedback on, given as a dictionary indexed by band number.
490
+ """
491
+
492
+ # infer from amplitude and feedback state
493
+ for band, chan in chan_per_band.items():
494
+ feedback = S.get_feedback_enable_array(band)
495
+ feedback[chan] = 0
496
+ S.set_feedback_enable_array(band, feedback)
497
+
498
+
499
+ @sdl.set_action()
500
+ def relock_tracking_setup(S, cfg, bands=None, reset_rate_khz=None, nphi0=None,
501
+ feedback_gain=None, lms_gain=None, show_plots=False,
502
+ frac_pp=None):
503
+ """
504
+ Sets up tracking for smurf. This assumes you already have optimized
505
+ lms_freq and frac-pp for each bands in the device config. This function
506
+ will chose the flux-ramp fraction-full-scale by averaging the optimized
507
+ fractions across the bands you're running on.
508
+
509
+ This function also allows you to set reset_rate_khz and nphi0. The
510
+ fraction-full-scale, and lms frequencies of each band will be automatically
511
+ adjusted based on their pre-existing optimized values.
512
+
513
+ Additional keyword args specified will be passed to S.tracking_setup.
514
+
515
+ Args
516
+ -----
517
+ S : SmurfControl
518
+ Pysmurf instance
519
+ cfg : DetConfig
520
+ Det config instance
521
+ reset_rate_khz : float, optional
522
+ Flux Ramp Reset Rate to set (kHz), defaults to the value in the dev cfg
523
+ nphi0 : int, optional
524
+ Number of phi0's to ramp through. Defaults to the value that was used
525
+ during setup.
526
+ disable_bad_chans : bool
527
+ If true, will disable tones for bad-tracking channels
528
+
529
+ Returns
530
+ --------
531
+ res : dict
532
+ Dictionary of results of all tracking-setup calls, with the bands number
533
+ as key.
534
+ """
535
+ if bands is None:
536
+ bands = cfg.dev.exp['active_bands']
537
+ bands = np.atleast_1d(bands)
538
+
539
+ nbands = len(bands)
540
+ exp = cfg.dev.exp
541
+
542
+ # check if any channels are assigned fixed tones
543
+ fixed_tones = _get_fixed_tone_channels(S, cfg, bands)
544
+
545
+ # Arrays containing the optimized tracking parameters for each band
546
+ frac_pp0 = np.zeros(nbands)
547
+ lms_freq0 = np.zeros(nbands) # Hz
548
+ reset_rate_khz0 = exp['flux_ramp_rate_khz']
549
+ init_nphi0 = exp['nphi0']
550
+
551
+ for i, b in enumerate(bands):
552
+ bcfg = cfg.dev.bands[b]
553
+ frac_pp0[i] = bcfg['frac_pp']
554
+ lms_freq0[i] = bcfg['lms_freq_hz']
555
+
556
+ # Choose frac_pp to be the mean of all running bands.
557
+ # This is the frac-pp at the flux-ramp-rate used for optimization
558
+ fpp0 = np.mean(frac_pp0)
559
+ lms_freq0 *= fpp0 / frac_pp0
560
+
561
+ S.log(f"Using frac-pp={fpp0}")
562
+
563
+ # Adjust fpp, lms_freq, and flux-ramp-rate depending on desired
564
+ # flux-ramp-rate and nphi0
565
+ fpp, lms_freqs = fpp0, lms_freq0
566
+ if nphi0 is not None:
567
+ fpp *= nphi0 / init_nphi0
568
+ lms_freqs *= fpp / fpp0
569
+ if reset_rate_khz is not None:
570
+ lms_freqs *= reset_rate_khz / reset_rate_khz0
571
+ else:
572
+ reset_rate_khz = reset_rate_khz0
573
+
574
+ if frac_pp is not None:
575
+ lms_freqs *= frac_pp / fpp
576
+ fpp = frac_pp
577
+
578
+ res = TrackingResults(S, cfg)
579
+ tk = {
580
+ 'reset_rate_khz': reset_rate_khz, 'fraction_full_scale': fpp,
581
+ 'make_plot': False, 'show_plot': False, 'channel': [],
582
+ 'nsamp': 2**18, 'return_data': True,
583
+ 'feedback_start_frac': exp['feedback_start_frac'],
584
+ 'feedback_end_frac': exp['feedback_end_frac'],
585
+ }
586
+
587
+
588
+ for i, band in enumerate(bands):
589
+ bcfg = cfg.dev.bands[band]
590
+ tk.update({
591
+ 'lms_freq_hz': lms_freqs[i],
592
+ 'lms_gain': bcfg['lms_gain'],
593
+ 'feedback_gain': bcfg['feedback_gain'],
594
+ })
595
+
596
+ if lms_gain is not None:
597
+ tk['lms_gain'] = lms_gain
598
+ if feedback_gain is not None:
599
+ tk['feedback_gain'] = feedback_gain
600
+
601
+ f, df, sync = S.tracking_setup(band, **tk)
602
+ res.add_band_data(band, f, df, sync, tracking_kwargs=tk)
603
+
604
+ res.find_bad_chans()
605
+ res.save()
606
+
607
+ # ensure channels with assigned fixed tones have feedback disabled
608
+ _restore_fixed_tone_channels(S, fixed_tones)
609
+
610
+ is_interactive = plt.isinteractive()
611
+ try:
612
+ if not show_plots:
613
+ plt.ioff()
614
+ fig, ax = plot_tracking_summary(res)
615
+ path = sdl.make_filename(S, 'tracking_results.png', plot=True)
616
+ fig.savefig(path)
617
+ S.pub.register_file(path, 'tracking_summary', plot=True, format='png')
618
+ if not show_plots:
619
+ plt.close(fig)
620
+ finally:
621
+ if is_interactive:
622
+ plt.ion()
623
+
624
+ return res