Przeglądaj źródła

refactor: simplify detector config and confidence scoring

master
Jan Svabenik 3 dni temu
rodzic
commit
ea7247da28
5 zmienionych plików z 53 dodań i 31 usunięć
  1. +2
    -26
      cmd/sdrd/main.go
  2. +14
    -2
      internal/classifier/rules.go
  3. +16
    -1
      internal/detector/detector.go
  4. +17
    -1
      internal/detector/detector_test.go
  5. +4
    -1
      web/app.js

+ 2
- 26
cmd/sdrd/main.go Wyświetl plik

@@ -314,19 +314,7 @@ func main() {
defer eventFile.Close()
eventMu := &sync.RWMutex{}

det := detector.New(cfg.Detector.ThresholdDb, cfg.SampleRate, cfg.FFTSize,
time.Duration(cfg.Detector.MinDurationMs)*time.Millisecond,
time.Duration(cfg.Detector.HoldMs)*time.Millisecond,
cfg.Detector.EmaAlpha,
cfg.Detector.HysteresisDb,
cfg.Detector.MinStableFrames,
time.Duration(cfg.Detector.GapToleranceMs)*time.Millisecond,
cfg.Detector.CFARMode,
cfg.Detector.CFARGuardCells,
cfg.Detector.CFARTrainCells,
cfg.Detector.CFARRank,
cfg.Detector.CFARScaleDb,
cfg.Detector.CFARWrapAround)
det := detector.New(cfg.Detector, cfg.SampleRate, cfg.FFTSize)

window := fftutil.Hann(cfg.FFTSize)
h := newHub()
@@ -458,19 +446,7 @@ func main() {
var newDet *detector.Detector
var newWindow []float64
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,
next.Detector.EmaAlpha,
next.Detector.HysteresisDb,
next.Detector.MinStableFrames,
time.Duration(next.Detector.GapToleranceMs)*time.Millisecond,
next.Detector.CFARMode,
next.Detector.CFARGuardCells,
next.Detector.CFARTrainCells,
next.Detector.CFARRank,
next.Detector.CFARScaleDb,
next.Detector.CFARWrapAround)
newDet = detector.New(next.Detector, next.SampleRate, next.FFTSize)
}
if windowChanged {
newWindow = fftutil.Hann(next.FFTSize)


+ 14
- 2
internal/classifier/rules.go Wyświetl plik

@@ -114,15 +114,27 @@ func top2(scores map[SignalClass]float64) (SignalClass, float64, SignalClass, fl
var best, second SignalClass
bestScore := 0.0
secondScore := 0.0
better := func(k SignalClass, v float64, cur SignalClass, curV float64) bool {
if v > curV {
return true
}
if v < curV {
return false
}
if cur == "" {
return true
}
return string(k) < string(cur)
}
for k, v := range scores {
if v > bestScore {
if better(k, v, best, bestScore) {
second = best
secondScore = bestScore
best = k
bestScore = v
continue
}
if v > secondScore {
if k != best && better(k, v, second, secondScore) {
second = k
secondScore = v
}


+ 16
- 1
internal/detector/detector.go Wyświetl plik

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

"sdr-visual-suite/internal/cfar"
"sdr-visual-suite/internal/classifier"
"sdr-visual-suite/internal/config"
)

type Event struct {
@@ -68,7 +69,21 @@ type Signal struct {
Class *classifier.Classification `json:"class,omitempty"`
}

func New(thresholdDb float64, sampleRate int, fftSize int, minDur, hold time.Duration, emaAlpha, hysteresis float64, minStable int, gapTolerance time.Duration, cfarMode string, cfarGuard, cfarTrain, cfarRank int, cfarScaleDb float64, cfarWrap bool) *Detector {
func New(detCfg config.DetectorConfig, sampleRate int, fftSize int) *Detector {
minDur := time.Duration(detCfg.MinDurationMs) * time.Millisecond
hold := time.Duration(detCfg.HoldMs) * time.Millisecond
gapTolerance := time.Duration(detCfg.GapToleranceMs) * time.Millisecond
emaAlpha := detCfg.EmaAlpha
hysteresis := detCfg.HysteresisDb
minStable := detCfg.MinStableFrames
cfarMode := detCfg.CFARMode
cfarGuard := detCfg.CFARGuardCells
cfarTrain := detCfg.CFARTrainCells
cfarRank := detCfg.CFARRank
cfarScaleDb := detCfg.CFARScaleDb
cfarWrap := detCfg.CFARWrapAround
thresholdDb := detCfg.ThresholdDb

if minDur <= 0 {
minDur = 250 * time.Millisecond
}


+ 17
- 1
internal/detector/detector_test.go Wyświetl plik

@@ -3,10 +3,26 @@ package detector
import (
"testing"
"time"

"sdr-visual-suite/internal/config"
)

func TestDetectorCreatesEvent(t *testing.T) {
d := New(-10, 1000, 10, 1*time.Millisecond, 10*time.Millisecond, 0.2, 3, 1, 10*time.Millisecond, "OFF", 2, 16, 24, 6, true)
d := New(config.DetectorConfig{
ThresholdDb: -10,
MinDurationMs: 1,
HoldMs: 10,
EmaAlpha: 0.2,
HysteresisDb: 3,
MinStableFrames: 1,
GapToleranceMs: 10,
CFARMode: "OFF",
CFARGuardCells: 2,
CFARTrainCells: 16,
CFARRank: 24,
CFARScaleDb: 6,
CFARWrapAround: true,
}, 1000, 10)
center := 0.0
spectrum := []float64{-30, -30, -30, -5, -5, -30, -30, -30, -30, -30}
now := time.Now()


+ 4
- 1
web/app.js Wyświetl plik

@@ -507,7 +507,10 @@ function updateHeroMetrics() {
metricSource.textContent = stats.last_sample_ago_ms >= 0 ? `${stats.last_sample_ago_ms} ms` : 'n/a';

const gpuText = gpuInfo.active ? 'GPU active' : (gpuInfo.available ? 'GPU ready' : 'GPU n/a');
metaLine.textContent = `${fmtMHz(latest.center_hz, 3)} · ${fmtHz(span)} span · ${gpuText}`;
const thresholdInfo = Array.isArray(latest.thresholds) && latest.thresholds.length
? `CFAR on · noise ${(Number.isFinite(latest.noise_floor) ? latest.noise_floor.toFixed(1) : 'n/a')} dB`
: `CFAR off · noise ${(Number.isFinite(latest.noise_floor) ? latest.noise_floor.toFixed(1) : 'n/a')} dB`;
metaLine.textContent = `${fmtMHz(latest.center_hz, 3)} · ${fmtHz(span)} span · ${thresholdInfo} · ${gpuText}`;
heroSubtitle.textContent = `${latest.signals?.length || 0} live signals · ${events.length} recent events tracked`;

healthBuffer.textContent = String(stats.buffer_samples ?? '-');


Ładowanie…
Anuluj
Zapisz