Skip to content

Commit

Permalink
Merge pull request #756 from anarkiwi/sigmf
Browse files Browse the repository at this point in the history
move to find peaks class.
  • Loading branch information
anarkiwi committed Jun 30, 2023
2 parents dd60d50 + 39494e3 commit 507e922
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 111 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ While there are other options, these options primarily influence gamutRF's scann

| Option | Description |
| -- | -- |
| --width | Minimum width of a peak to be detected in 0.01MHz increments (passed to scipy find_peaks()) |
| --prominence | Minimum prominence of a peak to be detected (passed to scipy find_peaks()) |
| --threshold | Minimum threshold in dB of a peak to be detected (passed to scipy find_peaks()) |
| --bin_width | Bin width in MHz |
| --history | Number of scanner cycles over which to prioritize recording of least often observed peaks |
| --record_bw_msps | Number of samples per second in units of 1024^2 (generally larger than bin size to record signal margins) |
Expand Down Expand Up @@ -139,7 +136,7 @@ gamutRF supports two separate APIs - for receiving scanner updates, and making s

## Scanner testing

Currently, the scanner ```gain``` and sigfinder ```threshold``` must be set manually for the general RF environment (e.g. noisy/many signals versus quiet/few signals).
Currently, the scanner ```gain``` must be set manually for the general RF environment (e.g. noisy/many signals versus quiet/few signals).
To establish the correct values and to confirm the scanner is working, initiate a scan over the 2.2-2.6GHz range. As the 2.4GHz spectrum is very busy with legacy WiFi
and BlueTooth, the probability of seeing signals is high. If in an environment without BlueTooth or WiFi, an alternative is the FM broadcast band (88MHz to 108MHz).

Expand All @@ -157,8 +154,6 @@ If no or only small peaks appear which are not marked as peaks, increase ```gain

If no peaks appear still, check antenna cabling, or choose a different scan range where signals are expected in your environment.

If peaks appear but are consistently not marked, decrease ```theshold``` (e.g. -25 to -35). If too many peaks are detected (noise detected as peaks), raise ```threshold.```

## Troubleshooting

#### Containers won't start using Ettus SDRs
Expand Down
12 changes: 5 additions & 7 deletions gamutrf/grscan.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import logging
import numpy as np
import sys
from pathlib import Path
import numpy as np

try:
from gnuradio import filter # pytype: disable=import-error
from gnuradio import filter as grfilter # pytype: disable=import-error
from gnuradio import blocks # pytype: disable=import-error
from gnuradio import fft # pytype: disable=import-error
from gnuradio import gr # pytype: disable=import-error
Expand Down Expand Up @@ -44,7 +43,6 @@ def __init__(
sample_dir="",
inference_plan_file="",
inference_output_dir="",
inference_input_len=2048,
bucket_range=1.0,
tuning_ranges="",
scaling="spectrum",
Expand Down Expand Up @@ -84,7 +82,7 @@ def __init__(
)

fft_blocks, fft_roll = self.get_fft_blocks(
fft_size, sdr, dc_block_len, dc_block_long
fft_size, dc_block_len, dc_block_long
)
self.fft_blocks = fft_blocks + self.get_db_blocks(fft_size, samp_rate, scaling)
self.fft_to_inference_block = self.fft_blocks[-1]
Expand Down Expand Up @@ -261,11 +259,11 @@ def get_db_blocks(self, fft_size, samp_rate, scaling):
def get_window(self, fft_size):
return window.hann(fft_size)

def get_fft_blocks(self, fft_size, sdr, dc_block_len, dc_block_long):
def get_fft_blocks(self, fft_size, dc_block_len, dc_block_long):
fft_blocks = []
fft_roll = False
if dc_block_len:
fft_blocks.append(filter.dc_blocker_cc(dc_block_len, dc_block_long))
fft_blocks.append(grfilter.dc_blocker_cc(dc_block_len, dc_block_long))
if self.wavelearner:
fft_batch_size = 256
fft_roll = True
Expand Down
60 changes: 60 additions & 0 deletions gamutrf/peak_finder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import numpy as np
from scipy.signal import find_peaks


class PeakFinderBase:
name = "base"

def find_peaks(self, db_data, height=None):
raise NotImplementedError


class PeakFinderNarrow:
name = "narrowband"

def find_peaks(self, db_data, height=None):
if height is None:
height = np.nanmean(db_data)
peaks, properties = find_peaks(
db_data,
height=height,
width=(1, 10),
prominence=10,
rel_height=0.7,
wlen=120,
)
return peaks, properties


class PeakFinderWide:
name = "wideband"

def find_peaks(self, db_data, height=None):
if height is None:
height = np.nanmean(db_data) + 1
peaks, properties = find_peaks(
db_data,
height=height,
width=10,
prominence=(0, 20),
rel_height=0.7,
wlen=120,
)
return peaks, properties


peak_finders = {
"wideband": PeakFinderWide,
"narrowband": PeakFinderNarrow,
"wb": PeakFinderWide,
"nb": PeakFinderNarrow,
}


def get_peak_finder(name):
if not name:
return None
try:
return peak_finders[name]()
except KeyError:
raise NotImplementedError
8 changes: 0 additions & 8 deletions gamutrf/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,6 @@ def argument_parser():
default="",
help="directory for inference output",
)
parser.add_argument(
"--inference_input_len",
dest="inference_input_len",
type=int,
default=2048,
help="vector length for wavelearner",
)
parser.add_argument(
"--tuning_ranges",
dest="tuning_ranges",
Expand Down Expand Up @@ -327,7 +320,6 @@ def run_loop(options, prom_vars, wavelearner):
dc_block_long=handler.options.dc_block_long,
inference_plan_file=handler.options.inference_plan_file,
inference_output_dir=handler.options.inference_output_dir,
inference_input_len=handler.options.inference_input_len,
scaling=handler.options.scaling,
rotate_secs=handler.options.rotate_secs,
description=handler.options.description,
Expand Down
39 changes: 13 additions & 26 deletions gamutrf/sigfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from gamutrf.sigwindows import get_center
from gamutrf.sigwindows import graph_fft_peaks
from gamutrf.sigwindows import parse_freq_excluded
from gamutrf.sigwindows import scipy_find_sig_windows
from gamutrf.sigwindows import find_sig_windows
from gamutrf.sigwindows import ROLLING_FACTOR
from gamutrf.utils import rotate_file_n, SCAN_FRES
from gamutrf.zmqreceiver import ZmqReceiver, parse_scanners
Expand Down Expand Up @@ -204,14 +204,13 @@ def process_scan(args, scan_configs, prom_vars, df, lastbins, running_df, last_d
(df.freq >= scan_config["freq_start"] / 1e6)
& (df.freq <= scan_config["freq_end"] / 1e6)
]
signals.extend(
scipy_find_sig_windows(
scan_df,
width=args.width,
prominence=args.prominence,
threshold=args.threshold,
if args.detection_type:
signals.extend(
find_sig_windows(
scan_df,
detection_type=args.detection_type,
)
)
)
min_samp_rate = (
min([scan_config["sample_rate"] for scan_config in scan_configs]) / 1e6
)
Expand Down Expand Up @@ -438,24 +437,6 @@ def argument_parser():
parser.add_argument(
"--bin_mhz", default=20, type=int, help="monitoring bin width in MHz"
)
parser.add_argument(
"--width",
default=10,
type=int,
help=f"minimum signal width to detect a peak (multiple of {SCAN_FRES / 1e6} MHz, e.g. 10 is {10 * SCAN_FRES / 1e6} MHz)",
)
parser.add_argument(
"--threshold",
default=-35,
type=float,
help="minimum signal finding threshold (dB)",
)
parser.add_argument(
"--prominence",
default=2,
type=float,
help="minimum peak prominence (see scipy.signal.find_peaks)",
)
parser.add_argument(
"--history",
default=5,
Expand Down Expand Up @@ -526,6 +507,12 @@ def argument_parser():
default=ROLLING_FACTOR,
help="Divisor for rolling dB average (or 0 to disable)",
)
parser.add_argument(
"--detection_type",
default="narrowband",
type=str,
help="Detection type to plot (wideband, narrowband).",
)
return parser


Expand Down
14 changes: 9 additions & 5 deletions gamutrf/sigwindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import numpy as np
import pandas as pd
from scipy.signal import find_peaks
import matplotlib
import matplotlib.pyplot as plt
from gamutrf.peak_finder import get_peak_finder
from gamutrf.utils import SCAN_FRES, SCAN_FROLL, WIDTH, HEIGHT, DPI, MPL_BACKEND


Expand Down Expand Up @@ -85,10 +85,14 @@ def calc_db(df, rolling_factor=ROLLING_FACTOR):
return df


def scipy_find_sig_windows(df, width, prominence, threshold):
data = df.db.to_numpy()
peaks, _ = find_peaks(data, prominence=prominence, width=(width,), height=threshold)
return [(df.iloc[peak].freq, df.iloc[peak].db) for peak in peaks]
def find_sig_windows(df, detection_type):
if detection_type:
peak_finder = get_peak_finder(detection_type)
if peak_finder:
data = df.db.to_numpy()
peaks, _ = peak_finder.find_peaks(data)
return [(df.iloc[peak].freq, df.iloc[peak].db) for peak in peaks]
return []


def graph_fft_peaks(
Expand Down
49 changes: 9 additions & 40 deletions gamutrf/waterfall.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from matplotlib.collections import LineCollection
from matplotlib.ticker import MultipleLocator, AutoMinorLocator
from scipy.ndimage import gaussian_filter
from scipy.signal import find_peaks

from gamutrf.peak_finder import get_peak_finder
from gamutrf.zmqreceiver import ZmqReceiver, parse_scanners

warnings.filterwarnings(action="ignore", message="Mean of empty slice")
Expand Down Expand Up @@ -456,7 +456,7 @@ def waterfall(
sampling_rate,
base_save_path,
save_time,
detection_type,
peak_finder,
engine,
savefig_path,
rotate_secs,
Expand Down Expand Up @@ -717,33 +717,8 @@ def sig_handler(_sig=None, _frame=None):
mean_psd_ln.set_ydata(np.nanmean(db_data, axis=0))
ax_psd.draw_artist(mesh_psd)

if detection_type:
# NARROWBAND SIGNAL DETECT

if detection_type == "narrowband":
peaks, properties = find_peaks(
db_data[-1],
height=np.nanmean(db_data, axis=0),
width=(1, 10),
prominence=10,
rel_height=0.7,
wlen=120,
)

# WIDEBAND SIGNAL DETECT
elif detection_type == "wideband":
peaks, properties = find_peaks(
db_data[
-1
], # db_data[-1] - np.nanmin(db_data, axis=0),#db_data[-1],
# height=np.nanmean(db_data, axis=0) - np.nanmin(db_data, axis=0),
height=np.nanmean(db_data, axis=0) + 1,
width=10,
prominence=(0, 20),
rel_height=0.7,
wlen=120,
)

if peak_finder:
peaks, properties = peak_finder.find_peaks(db_data[-1])
peaks, properties = filter_peaks(peaks, properties)

if save_path:
Expand All @@ -757,7 +732,7 @@ def sig_handler(_sig=None, _frame=None):
peaks,
properties,
psd_x_edges,
detection_type,
peak_finder.name,
)

peak_lns.set_xdata(psd_x_edges[peaks])
Expand Down Expand Up @@ -917,21 +892,15 @@ def main():

parser = argument_parser()
args = parser.parse_args()
detection_type = args.detection_type
detection_type = args.detection_type.lower()
peak_finder = None

if args.save_path:
Path(args.save_path, "waterfall").mkdir(parents=True, exist_ok=True)

if detection_type:
Path(args.save_path, "detections").mkdir(parents=True, exist_ok=True)

detection_type = detection_type.lower()
if detection_type in ["wb", "wide band", "wideband"]:
detection_type = "wideband"
elif detection_type in ["nb", "narrow band", "narrowband"]:
detection_type = "narrowband"
else:
raise ValueError("detection_type must be 'narrowband' or 'wideband'")
peak_finder = get_peak_finder(detection_type)

with tempfile.TemporaryDirectory() as tempdir:
flask = None
Expand All @@ -958,7 +927,7 @@ def main():
args.sampling_rate,
args.save_path,
args.save_time,
detection_type,
peak_finder,
engine,
savefig_path,
args.rotate_secs,
Expand Down
Loading

0 comments on commit 507e922

Please sign in to comment.