Przeglądaj źródła

Improve CFAR UI, CFAR perf, classifier gaps

master
Jan Svabenik 3 dni temu
rodzic
commit
f90c866193
3 zmienionych plików z 113 dodań i 12 usunięć
  1. +12
    -2
      internal/classifier/rules.go
  2. +62
    -10
      internal/detector/detector.go
  3. +39
    -0
      web/app.js

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

@@ -13,10 +13,14 @@ func RuleClassify(feat Features) Classification {
conf := 0.3

switch {
case bw > 100e3:
case bw >= 80e3:
best = ClassWFM
conf = 0.9
case bw >= 6e3 && bw <= 16e3:
case bw >= 25e3 && bw < 80e3:
best = ClassWFM
second = ClassNFM
conf = 0.65
case bw >= 6e3 && bw < 25e3:
best = ClassNFM
conf = 0.8
if flat > 0.7 {
@@ -63,6 +67,12 @@ func RuleClassify(feat Features) Classification {
if feat.EnvVariance < 0.4 && feat.InstFreqStd < 0.5 {
best = ClassWSPR
conf = 0.55
} else if feat.InstFreqStd > 0.9 {
best = ClassFSK
conf = 0.45
} else if feat.InstFreqStd < 0.25 {
best = ClassPSK
conf = 0.45
}
case bw < 150:
best = ClassCW


+ 62
- 10
internal/detector/detector.go Wyświetl plik

@@ -229,23 +229,75 @@ func (d *Detector) cfarThresholds(spectrum []float64) []float64 {
}
rankIdx := rank - 1
thresholds := make([]float64, n)
buf := make([]float64, totalTrain)
buf := make([]float64, 0, totalTrain)
firstValid := guard + train
lastValid := n - guard - train - 1
for i := 0; i < n; i++ {
leftStart := i - guard - train
rightEnd := i + guard + train
if leftStart < 0 || rightEnd >= n {
if i < firstValid || i > lastValid {
thresholds[i] = math.NaN()
continue
}
copy(buf[:train], spectrum[leftStart:leftStart+train])
copy(buf[train:], spectrum[i+guard+1:i+guard+1+train])
sort.Float64s(buf)
noise := buf[rankIdx]
thresholds[i] = noise + d.CFARScaleDb
}
if firstValid > lastValid {
return thresholds
}

// Build initial sorted window for first valid bin.
leftStart := firstValid - guard - train
buf = append(buf, spectrum[leftStart:leftStart+train]...)
buf = append(buf, spectrum[firstValid+guard+1:firstValid+guard+1+train]...)
sort.Float64s(buf)
thresholds[firstValid] = buf[rankIdx] + d.CFARScaleDb

// Slide window: remove outgoing bins and insert incoming bins (O(train) per step).
for i := firstValid + 1; i <= lastValid; i++ {
outLeft := spectrum[i-guard-train-1]
outRight := spectrum[i+guard]
inLeft := spectrum[i-guard-1]
inRight := spectrum[i+guard+train]
buf = removeValue(buf, outLeft)
buf = removeValue(buf, outRight)
buf = insertValue(buf, inLeft)
buf = insertValue(buf, inRight)
thresholds[i] = buf[rankIdx] + d.CFARScaleDb
}
return thresholds
}

func removeValue(sorted []float64, v float64) []float64 {
if len(sorted) == 0 {
return sorted
}
idx := sort.SearchFloat64s(sorted, v)
if idx < len(sorted) && sorted[idx] == v {
return append(sorted[:idx], sorted[idx+1:]...)
}
for i := idx - 1; i >= 0; i-- {
if sorted[i] == v {
return append(sorted[:i], sorted[i+1:]...)
}
if sorted[i] < v {
break
}
}
for i := idx + 1; i < len(sorted); i++ {
if sorted[i] == v {
return append(sorted[:i], sorted[i+1:]...)
}
if sorted[i] > v {
break
}
}
return sorted
}

func insertValue(sorted []float64, v float64) []float64 {
idx := sort.SearchFloat64s(sorted, v)
sorted = append(sorted, 0)
copy(sorted[idx+1:], sorted[idx:])
sorted[idx] = v
return sorted
}

func (d *Detector) smoothSpectrum(spectrum []float64) []float64 {
if d.ema == nil || len(d.ema) != len(spectrum) {
d.ema = make([]float64, len(spectrum))


+ 39
- 0
web/app.js Wyświetl plik

@@ -542,6 +542,44 @@ function drawSpectrumGrid(ctx, w, h, startHz, endHz) {
}
}

function drawCfarEdgeOverlay(ctx, w, h, startHz, endHz) {
if (!latest || !currentConfig?.detector?.cfar_enabled) return;
const guard = currentConfig.detector.cfar_guard_cells ?? 0;
const train = currentConfig.detector.cfar_train_cells ?? 0;
const bins = guard + train;
if (bins <= 0) return;
const fftSize = latest.fft_size || latest.spectrum_db?.length;
if (!fftSize || fftSize <= 0) return;
const binHz = latest.sample_rate / fftSize;
const edgeHz = bins * binHz;
const bandStart = latest.center_hz - latest.sample_rate / 2;
const bandEnd = latest.center_hz + latest.sample_rate / 2;
const leftEdgeEnd = bandStart + edgeHz;
const rightEdgeStart = bandEnd - edgeHz;

ctx.fillStyle = 'rgba(255, 204, 102, 0.08)';
ctx.strokeStyle = 'rgba(255, 204, 102, 0.18)';
ctx.lineWidth = 1;

const leftStart = Math.max(startHz, bandStart);
const leftEnd = Math.min(endHz, leftEdgeEnd);
if (leftEnd > leftStart) {
const x1 = ((leftStart - startHz) / (endHz - startHz)) * w;
const x2 = ((leftEnd - startHz) / (endHz - startHz)) * w;
ctx.fillRect(x1, 0, Math.max(2, x2 - x1), h);
ctx.strokeRect(x1, 0, Math.max(2, x2 - x1), h);
}

const rightStart = Math.max(startHz, rightEdgeStart);
const rightEnd = Math.min(endHz, bandEnd);
if (rightEnd > rightStart) {
const x1 = ((rightStart - startHz) / (endHz - startHz)) * w;
const x2 = ((rightEnd - startHz) / (endHz - startHz)) * w;
ctx.fillRect(x1, 0, Math.max(2, x2 - x1), h);
ctx.strokeRect(x1, 0, Math.max(2, x2 - x1), h);
}
}

function renderSpectrum() {
if (!latest) return;
const ctx = spectrumCanvas.getContext('2d');
@@ -558,6 +596,7 @@ function renderSpectrum() {
spanInput.value = (span / 1e6).toFixed(3);

drawSpectrumGrid(ctx, w, h, startHz, endHz);
drawCfarEdgeOverlay(ctx, w, h, startHz, endHz);

const minDb = -120;
const maxDb = 0;


Ładowanie…
Anuluj
Zapisz