Bläddra i källkod

Add EMA smoothing and hysteresis to detector

master
Jan Svabenik 4 dagar sedan
förälder
incheckning
81c481bb64
6 ändrade filer med 70 tillägg och 26 borttagningar
  1. +6
    -2
      cmd/sdrd/main.go
  2. +2
    -0
      config.yaml
  3. +3
    -1
      internal/config/config.go
  4. +47
    -19
      internal/detector/detector.go
  5. +1
    -1
      internal/detector/detector_test.go
  6. +11
    -3
      internal/runtime/runtime.go

+ 6
- 2
cmd/sdrd/main.go Visa fil

@@ -314,7 +314,9 @@ func main() {

det := detector.New(cfg.Detector.ThresholdDb, cfg.SampleRate, cfg.FFTSize,
time.Duration(cfg.Detector.MinDurationMs)*time.Millisecond,
time.Duration(cfg.Detector.HoldMs)*time.Millisecond)
time.Duration(cfg.Detector.HoldMs)*time.Millisecond,
cfg.Detector.EmaAlpha,
cfg.Detector.HysteresisDb)

window := fftutil.Hann(cfg.FFTSize)
h := newHub()
@@ -435,7 +437,9 @@ func main() {
if detChanged {
newDet = detector.New(next.Detector.ThresholdDb, next.SampleRate, next.FFTSize,
time.Duration(next.Detector.MinDurationMs)*time.Millisecond,
time.Duration(next.Detector.HoldMs)*time.Millisecond)
time.Duration(next.Detector.HoldMs)*time.Millisecond,
next.Detector.EmaAlpha,
next.Detector.HysteresisDb)
}
if windowChanged {
newWindow = fftutil.Hann(next.FFTSize)


+ 2
- 0
config.yaml Visa fil

@@ -15,6 +15,8 @@ detector:
threshold_db: -20
min_duration_ms: 250
hold_ms: 500
ema_alpha: 0.2
hysteresis_db: 3
recorder:
enabled: true
min_snr_db: 10


+ 3
- 1
internal/config/config.go Visa fil

@@ -17,6 +17,8 @@ type DetectorConfig struct {
ThresholdDb float64 `yaml:"threshold_db" json:"threshold_db"`
MinDurationMs int `yaml:"min_duration_ms" json:"min_duration_ms"`
HoldMs int `yaml:"hold_ms" json:"hold_ms"`
EmaAlpha float64 `yaml:"ema_alpha" json:"ema_alpha"`
HysteresisDb float64 `yaml:"hysteresis_db" json:"hysteresis_db"`
}

type RecorderConfig struct {
@@ -79,7 +81,7 @@ func Default() Config {
AGC: false,
DCBlock: false,
IQBalance: false,
Detector: DetectorConfig{ThresholdDb: -20, MinDurationMs: 250, HoldMs: 500},
Detector: DetectorConfig{ThresholdDb: -20, MinDurationMs: 250, HoldMs: 500, EmaAlpha: 0.2, HysteresisDb: 3},
Recorder: RecorderConfig{
Enabled: false,
MinSNRDb: 10,


+ 47
- 19
internal/detector/detector.go Visa fil

@@ -22,14 +22,17 @@ type Event struct {
}

type Detector struct {
ThresholdDb float64
MinDuration time.Duration
Hold time.Duration
ThresholdDb float64
MinDuration time.Duration
Hold time.Duration
EmaAlpha float64
HysteresisDb float64

binWidth float64
nbins int
sampleRate int

ema []float64
active map[int64]*activeEvent
nextID int64
}
@@ -57,22 +60,31 @@ type Signal struct {
Class *classifier.Classification `json:"class,omitempty"`
}

func New(thresholdDb float64, sampleRate int, fftSize int, minDur, hold time.Duration) *Detector {
func New(thresholdDb float64, sampleRate int, fftSize int, minDur, hold time.Duration, emaAlpha, hysteresis float64) *Detector {
if minDur <= 0 {
minDur = 250 * time.Millisecond
}
if hold <= 0 {
hold = 500 * time.Millisecond
}
if emaAlpha <= 0 || emaAlpha > 1 {
emaAlpha = 0.2
}
if hysteresis <= 0 {
hysteresis = 3
}
return &Detector{
ThresholdDb: thresholdDb,
MinDuration: minDur,
Hold: hold,
binWidth: float64(sampleRate) / float64(fftSize),
nbins: fftSize,
sampleRate: sampleRate,
active: map[int64]*activeEvent{},
nextID: 1,
ThresholdDb: thresholdDb,
MinDuration: minDur,
Hold: hold,
EmaAlpha: emaAlpha,
HysteresisDb: hysteresis,
binWidth: float64(sampleRate) / float64(fftSize),
nbins: fftSize,
sampleRate: sampleRate,
ema: make([]float64, fftSize),
active: map[int64]*activeEvent{},
nextID: 1,
}
}

@@ -102,16 +114,18 @@ func (d *Detector) detectSignals(spectrum []float64, centerHz float64) []Signal
if n == 0 {
return nil
}
threshold := d.ThresholdDb
noise := median(spectrum)
smooth := d.smoothSpectrum(spectrum)
thresholdOn := d.ThresholdDb
thresholdOff := d.ThresholdDb - d.HysteresisDb
noise := median(smooth)
var signals []Signal
in := false
start := 0
peak := -1e9
peakBin := 0
for i := 0; i < n; i++ {
v := spectrum[i]
if v >= threshold {
v := smooth[i]
if v >= thresholdOn {
if !in {
in = true
start = i
@@ -121,13 +135,13 @@ func (d *Detector) detectSignals(spectrum []float64, centerHz float64) []Signal
peak = v
peakBin = i
}
} else if in {
signals = append(signals, d.makeSignal(start, i-1, peak, peakBin, noise, centerHz, spectrum))
} else if in && v < thresholdOff {
signals = append(signals, d.makeSignal(start, i-1, peak, peakBin, noise, centerHz, smooth))
in = false
}
}
if in {
signals = append(signals, d.makeSignal(start, n-1, peak, peakBin, noise, centerHz, spectrum))
signals = append(signals, d.makeSignal(start, n-1, peak, peakBin, noise, centerHz, smooth))
}
return signals
}
@@ -147,6 +161,20 @@ func (d *Detector) makeSignal(first, last int, peak float64, peakBin int, noise
}
}

func (d *Detector) smoothSpectrum(spectrum []float64) []float64 {
if d.ema == nil || len(d.ema) != len(spectrum) {
d.ema = make([]float64, len(spectrum))
copy(d.ema, spectrum)
return d.ema
}
alpha := d.EmaAlpha
for i := range spectrum {
v := spectrum[i]
d.ema[i] = alpha*v + (1-alpha)*d.ema[i]
}
return d.ema
}

func (d *Detector) matchSignals(now time.Time, signals []Signal) []Event {
used := make(map[int64]bool, len(d.active))
for _, s := range signals {


+ 1
- 1
internal/detector/detector_test.go Visa fil

@@ -6,7 +6,7 @@ import (
)

func TestDetectorCreatesEvent(t *testing.T) {
d := New(-10, 1000, 10, 1*time.Millisecond, 10*time.Millisecond)
d := New(-10, 1000, 10, 1*time.Millisecond, 10*time.Millisecond, 0.2, 3)
center := 0.0
spectrum := []float64{-30, -30, -30, -5, -5, -30, -30, -30, -30, -30}
now := time.Now()


+ 11
- 3
internal/runtime/runtime.go Visa fil

@@ -19,9 +19,11 @@ type ConfigUpdate struct {
}

type DetectorUpdate struct {
ThresholdDb *float64 `json:"threshold_db"`
MinDuration *int `json:"min_duration_ms"`
HoldMs *int `json:"hold_ms"`
ThresholdDb *float64 `json:"threshold_db"`
MinDuration *int `json:"min_duration_ms"`
HoldMs *int `json:"hold_ms"`
EmaAlpha *float64 `json:"ema_alpha"`
HysteresisDb *float64 `json:"hysteresis_db"`
}

type SettingsUpdate struct {
@@ -121,6 +123,12 @@ func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) {
}
next.Detector.HoldMs = *update.Detector.HoldMs
}
if update.Detector.EmaAlpha != nil {
next.Detector.EmaAlpha = *update.Detector.EmaAlpha
}
if update.Detector.HysteresisDb != nil {
next.Detector.HysteresisDb = *update.Detector.HysteresisDb
}
}
if update.Recorder != nil {
if update.Recorder.Enabled != nil {


Laddar…
Avbryt
Spara