From 1a2bd761aa7288cbaca2c4034f35a3d4ba4b2142 Mon Sep 17 00:00:00 2001 From: Josh Bailey Date: Fri, 7 Jun 2024 09:18:35 +0000 Subject: [PATCH] Serve alternate waterfalls --- .../gamutrfwaterfall/flask_handler.py | 2 +- .../gamutrfwaterfall/waterfall.py | 50 +++++++++++++------ .../gamutrfwaterfall/waterfall_plot.py | 16 ++++-- gamutrfwaterfall/tests/test_waterfall.py | 34 +++++++------ 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/gamutrfwaterfall/gamutrfwaterfall/flask_handler.py b/gamutrfwaterfall/gamutrfwaterfall/flask_handler.py index fa1a6681..a29312b6 100644 --- a/gamutrfwaterfall/gamutrfwaterfall/flask_handler.py +++ b/gamutrfwaterfall/gamutrfwaterfall/flask_handler.py @@ -157,7 +157,7 @@ def poll_zmq(self): def serve(self, path): if path: - full_path = os.path.realpath(os.path.join("/", path)) + full_path = os.path.realpath(os.path.join(self.tempdir, path)) if os.path.exists(full_path): return send_file(full_path, mimetype="image/png") return "%s: not found" % full_path, 404 diff --git a/gamutrfwaterfall/gamutrfwaterfall/waterfall.py b/gamutrfwaterfall/gamutrfwaterfall/waterfall.py index 36b296da..f5491b62 100644 --- a/gamutrfwaterfall/gamutrfwaterfall/waterfall.py +++ b/gamutrfwaterfall/gamutrfwaterfall/waterfall.py @@ -6,7 +6,7 @@ import warnings from gamutrflib.peak_finder import get_peak_finder -from gamutrflib.zmqbucket import ZmqReceiver, parse_scanners, frame_resample +from gamutrflib.zmqbucket import ZmqReceiver, parse_scanners from gamutrfwaterfall.argparser import argument_parser from gamutrfwaterfall.flask_handler import ( FlaskHandler, @@ -119,11 +119,41 @@ def sig_handler(_sig=None, _frame=None): config.fft_len, config.freq_resolution, ) + tuning_ranges = [] + for scan_config in scan_configs: + for tuning_range in scan_config["tuning_ranges"].split(","): + tuning_ranges.append([int(i) for i in tuning_range.split("-")]) plot_manager.close() plot_manager.add_plot(config, 0) - results = [ - (scan_configs, frame_resample(scan_df, config.freq_resolution * 1e6)) - ] + if len(tuning_ranges) > 1: + for i, tuning_range in enumerate(tuning_ranges, start=1): + plot_savefig_path = os.path.join( + os.path.dirname(savefig_path), + "-".join((str(i), os.path.basename(savefig_path))), + ) + logging.info( + f"tuning range {tuning_range[0]}-{tuning_range[1]} writing to {plot_savefig_path}" + ) + plot_config = make_config( + scan_configs, + tuning_range[0], + tuning_range[1], + engine, + plot_snr, + plot_savefig_path, + top_n, + base_save_path, + width, + height, + waterfall_height, + waterfall_width, + batch, + rotate_secs, + save_time, + ) + plot_manager.add_plot(plot_config, i) + + results = [(scan_configs, scan_df)] if config_vars_path: get_scanner_args(api_endpoint, config_vars) @@ -143,9 +173,7 @@ def sig_handler(_sig=None, _frame=None): gap_time = time.time() - last_gap if results and gap_time > refresh / 2: break - scan_configs, scan_df = zmqr.read_buff( - scan_fres=config.freq_resolution * 1e6 - ) + scan_configs, scan_df = zmqr.read_buff() if scan_df is None: if batch and gap_time < refresh / 2: time.sleep(0.1) @@ -173,14 +201,6 @@ def sig_handler(_sig=None, _frame=None): results = [] need_reconfig = True break - scan_df = scan_df[ - (scan_df.freq >= config.min_freq) & (scan_df.freq <= config.max_freq) - ] - if scan_df.empty: - logging.info( - f"Scan is outside specified frequency range ({config.min_freq} to {config.max_freq})." - ) - continue results.append((scan_configs, scan_df)) if need_reconfig: continue diff --git a/gamutrfwaterfall/gamutrfwaterfall/waterfall_plot.py b/gamutrfwaterfall/gamutrfwaterfall/waterfall_plot.py index 2351742c..07ce11d1 100644 --- a/gamutrfwaterfall/gamutrfwaterfall/waterfall_plot.py +++ b/gamutrfwaterfall/gamutrfwaterfall/waterfall_plot.py @@ -13,6 +13,7 @@ import matplotlib.pyplot as plt from matplotlib.ticker import MultipleLocator, AutoMinorLocator from scipy.ndimage import gaussian_filter +from gamutrflib.zmqbucket import frame_resample class WaterfallConfig: @@ -237,7 +238,6 @@ def reset_mesh_psd(self, data=None): if data is None: data = np.zeros(X[:-1, :-1].shape) - self.state.mesh_psd = self.state.ax_psd.pcolormesh(X, Y, data, shading="flat") def reset_mesh(self, data): @@ -390,8 +390,18 @@ def update_fig(self, results): logging.info("processing backlog of %u results", len(results)) scan_duration = 0 + row_time = None - for scan_configs, scan_df in results: + for scan_configs, orig_scan_df in results: + scan_df = frame_resample( + orig_scan_df.copy(), self.config.freq_resolution * 1e6 + ) + scan_df = scan_df[ + (scan_df.freq >= self.config.min_freq) + & (scan_df.freq <= self.config.max_freq) + ] + if scan_df.empty: + continue tune_step_hz = min( scan_config["tune_step_hz"] for scan_config in scan_configs ) @@ -443,7 +453,7 @@ def update_fig(self, results): self.state.counter += 1 - if self.state.counter % self.config.draw_rate == 0: + if row_time is not None and self.state.counter % self.config.draw_rate == 0: now = time.time() since_last_plot = 0 if self.state.last_plot: diff --git a/gamutrfwaterfall/tests/test_waterfall.py b/gamutrfwaterfall/tests/test_waterfall.py index bb3ee825..3db49aa8 100755 --- a/gamutrfwaterfall/tests/test_waterfall.py +++ b/gamutrfwaterfall/tests/test_waterfall.py @@ -14,28 +14,28 @@ class FakeZmqReceiver: - def __init__(self, run_secs, peak_min, peak_max, peak_val): + def __init__(self, run_secs, peak_min, peak_max, peak_val, freq_min, freq_max): self.start_time = time.time() self.run_secs = run_secs self.serve_results = None self.peak_min = peak_min self.peak_max = peak_max self.peak_val = peak_val + self.freq_min = freq_min + self.freq_max = freq_max def healthy(self): return time.time() - self.start_time < self.run_secs - def read_buff(self, scan_fres): + def read_buff(self, scan_fres=1e3): if not self.serve_results: - freq_min = 1e6 - freq_max = 10e6 - rows = int((freq_max - freq_min) / scan_fres) + rows = int((self.freq_max - self.freq_min) / scan_fres) df = pd.DataFrame( [ { "ts": time.time(), - "freq": freq_min + (i * scan_fres), - "db": self.peak_val / 2, + "freq": self.freq_min + (i * scan_fres), + "db": self.peak_val * 2, "tune_count": i, } for i in range(rows) @@ -44,7 +44,6 @@ def read_buff(self, scan_fres): df.loc[(df.freq >= self.peak_min) & (df.freq <= self.peak_max), "db"] = ( self.peak_val ) - df["freq"] /= 1e6 self.serve_results = [ ( [ @@ -55,6 +54,7 @@ def read_buff(self, scan_fres): "freq_end": df.freq.max(), "tune_step_hz": scan_fres, "tune_step_fft": 256, + "tuning_ranges": f"{int(df.freq.min())}-{int(df.freq.max())}", } ], df, @@ -73,15 +73,17 @@ def test_arg_parser(self): def test_run_waterfall(self): with tempfile.TemporaryDirectory() as tempdir: - peak_min = 1.50 - peak_max = 1.51 - peak_val = 100 + peak_min = 1.50e6 + peak_max = 1.52e6 + peak_val = -10 + freq_min = 1e6 + freq_max = 2e6 savefig = os.path.join(tempdir, "test.png") - zmqr = FakeZmqReceiver(90, peak_min, peak_max, peak_val) + zmqr = FakeZmqReceiver(90, peak_min, peak_max, peak_val, freq_min, freq_max) peak_finder = get_peak_finder("narrowband") serve_waterfall( - 1e6, # args.min_freq, - 2e6, # args.max_freq, + freq_min, # args.min_freq, + freq_max, # args.max_freq, True, # args.plot_snr, 1, # args.n_detect, tempdir, # args.save_path, @@ -117,10 +119,10 @@ def test_run_waterfall(self): # timestamp,start_freq,end_freq,dB,type # 1.0,1.49609375,1.5078125,100.0,narrowband self.assertEqual( - round(peak_min, 1), round(float(row["start_freq"]), 1), row + round(peak_min / 1e6, 1), round(float(row["start_freq"]), 1), row ) self.assertEqual( - round(peak_max, 1), round(float(row["end_freq"]), 1), row + round(peak_max / 1e6, 1), round(float(row["end_freq"]), 1), row ) self.assertEqual(peak_val, float(row["dB"]), row)