Просмотр исходного кода

fix: harden CFAR thresholds and async event handoff

master
Jan Svabenik 2 дней назад
Родитель
Сommit
d461c91e22
6 измененных файлов: 93 добавлений и 27 удалений
  1. +4
    -2
      cmd/sdrd/main.go
  2. +14
    -8
      internal/cfar/ca.go
  3. +14
    -8
      internal/cfar/caso.go
  4. +35
    -1
      internal/cfar/cfar_test.go
  5. +14
    -8
      internal/cfar/gosca.go
  6. +12
    -0
      internal/cfar/os.go

+ 4
- 2
cmd/sdrd/main.go Просмотреть файл

@@ -854,8 +854,10 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
_ = enc.Encode(ev) _ = enc.Encode(ev)
} }
eventMu.Unlock() eventMu.Unlock()
if rec != nil {
go rec.OnEvents(finished)
if rec != nil && len(finished) > 0 {
evCopy := make([]detector.Event, len(finished))
copy(evCopy, finished)
go rec.OnEvents(evCopy)
} }
h.broadcast(SpectrumFrame{ h.broadcast(SpectrumFrame{
Timestamp: now.UnixMilli(), Timestamp: now.UnixMilli(),


+ 14
- 8
internal/cfar/ca.go Просмотреть файл

@@ -1,5 +1,7 @@
package cfar package cfar


import "math"

// cellAvg implements CA-CFAR with a sliding sum window. // cellAvg implements CA-CFAR with a sliding sum window.
type cellAvg struct { type cellAvg struct {
guard int guard int
@@ -40,23 +42,27 @@ func (c *cellAvg) Thresholds(spectrum []float64) []float64 {
return spectrum[i] return spectrum[i]
} }


toLinear := func(db float64) float64 {
return math.Pow(10, db/10.0)
}

var leftSum, rightSum float64 var leftSum, rightSum float64
for k := 1; k <= train; k++ { for k := 1; k <= train; k++ {
leftSum += at(0 - guard - k)
rightSum += at(0 + guard + k)
leftSum += toLinear(at(0 - guard - k))
rightSum += toLinear(at(0 + guard + k))
} }


invN := 1.0 / float64(total) invN := 1.0 / float64(total)
out[0] = (leftSum+rightSum)*invN + c.scaleDb
out[0] = 10*math.Log10((leftSum+rightSum)*invN) + c.scaleDb


for i := 1; i < n; i++ { for i := 1; i < n; i++ {
leftSum -= at(i - 1 - guard - train)
leftSum += at(i - guard - 1)
leftSum -= toLinear(at(i - 1 - guard - train))
leftSum += toLinear(at(i - guard - 1))


rightSum -= at(i - 1 + guard + 1)
rightSum += at(i + guard + train)
rightSum -= toLinear(at(i - 1 + guard + 1))
rightSum += toLinear(at(i + guard + train))


out[i] = (leftSum+rightSum)*invN + c.scaleDb
out[i] = 10*math.Log10((leftSum+rightSum)*invN) + c.scaleDb
} }
return out return out
} }


+ 14
- 8
internal/cfar/caso.go Просмотреть файл

@@ -1,5 +1,7 @@
package cfar package cfar


import "math"

type caso struct { type caso struct {
guard int guard int
train int train int
@@ -34,10 +36,14 @@ func (c *caso) Thresholds(spectrum []float64) []float64 {
return spectrum[i] return spectrum[i]
} }


toLinear := func(db float64) float64 {
return math.Pow(10, db/10.0)
}

var leftSum, rightSum float64 var leftSum, rightSum float64
for k := 1; k <= train; k++ { for k := 1; k <= train; k++ {
leftSum += at(0 - guard - k)
rightSum += at(0 + guard + k)
leftSum += toLinear(at(0 - guard - k))
rightSum += toLinear(at(0 + guard + k))
} }
lm := leftSum * inv lm := leftSum * inv
rm := rightSum * inv rm := rightSum * inv
@@ -45,20 +51,20 @@ func (c *caso) Thresholds(spectrum []float64) []float64 {
if rm < noise { if rm < noise {
noise = rm noise = rm
} }
out[0] = noise + c.scaleDb
out[0] = 10*math.Log10(noise) + c.scaleDb


for i := 1; i < n; i++ { for i := 1; i < n; i++ {
leftSum -= at(i - 1 - guard - train)
leftSum += at(i - guard - 1)
rightSum -= at(i - 1 + guard + 1)
rightSum += at(i + guard + train)
leftSum -= toLinear(at(i - 1 - guard - train))
leftSum += toLinear(at(i - guard - 1))
rightSum -= toLinear(at(i - 1 + guard + 1))
rightSum += toLinear(at(i + guard + train))
lm = leftSum * inv lm = leftSum * inv
rm = rightSum * inv rm = rightSum * inv
noise = lm noise = lm
if rm < noise { if rm < noise {
noise = rm noise = rm
} }
out[i] = noise + c.scaleDb
out[i] = 10*math.Log10(noise) + c.scaleDb
} }
return out return out
} }

+ 35
- 1
internal/cfar/cfar_test.go Просмотреть файл

@@ -1,6 +1,9 @@
package cfar package cfar


import "testing"
import (
"math"
"testing"
)


func makeSpectrum(n int, noiseDb float64, signals [][2]int, sigDb float64) []float64 { func makeSpectrum(n int, noiseDb float64, signals [][2]int, sigDb float64) []float64 {
s := make([]float64, n) s := make([]float64, n)
@@ -59,6 +62,37 @@ func TestGOSCAMaskingProtection(t *testing.T) {
} }
} }


func TestCellAveragingUsesLinearPower(t *testing.T) {
spec := []float64{-100, -100, -100, -80, -100, -90, -100, -100, -100}
cfg := Config{GuardCells: 0, TrainCells: 2, ScaleDb: 0, WrapAround: false}

ca := New(Config{Mode: ModeCA, GuardCells: cfg.GuardCells, TrainCells: cfg.TrainCells, ScaleDb: cfg.ScaleDb, WrapAround: cfg.WrapAround})
gosca := New(Config{Mode: ModeGOSCA, GuardCells: cfg.GuardCells, TrainCells: cfg.TrainCells, ScaleDb: cfg.ScaleDb, WrapAround: cfg.WrapAround})
caso := New(Config{Mode: ModeCASO, GuardCells: cfg.GuardCells, TrainCells: cfg.TrainCells, ScaleDb: cfg.ScaleDb, WrapAround: cfg.WrapAround})

mid := 5
caTh := ca.Thresholds(spec)[mid]
goscaTh := gosca.Thresholds(spec)[mid]
casoTh := caso.Thresholds(spec)[mid]

dbApproxCA := (-80.0 + -90.0 + -100.0 + -100.0) / 4.0
dbApproxGOSCA := -85.0
dbApproxCASO := -100.0

if math.Abs(caTh-dbApproxCA) < 1.0 {
t.Fatalf("CA threshold still looks like dB averaging: got %v approx %v", caTh, dbApproxCA)
}
if math.Abs(goscaTh-dbApproxGOSCA) < 1.0 {
t.Fatalf("GOSCA threshold still looks like dB averaging: got %v approx %v", goscaTh, dbApproxGOSCA)
}
if math.Abs(casoTh-dbApproxCASO) < 1.0 {
t.Fatalf("CASO threshold still looks like dB averaging: got %v approx %v", casoTh, dbApproxCASO)
}
if !(goscaTh > caTh && caTh > casoTh) {
t.Fatalf("unexpected ordering: GOSCA=%v CA=%v CASO=%v", goscaTh, caTh, casoTh)
}
}

func BenchmarkCFAR(b *testing.B) { func BenchmarkCFAR(b *testing.B) {
spec := makeSpectrum(2048, -100, [][2]int{{500, 510}, {1000, 1020}}, -20) spec := makeSpectrum(2048, -100, [][2]int{{500, 510}, {1000, 1020}}, -20)
for _, mode := range []Mode{ModeCA, ModeOS, ModeGOSCA, ModeCASO} { for _, mode := range []Mode{ModeCA, ModeOS, ModeGOSCA, ModeCASO} {


+ 14
- 8
internal/cfar/gosca.go Просмотреть файл

@@ -1,5 +1,7 @@
package cfar package cfar


import "math"

// gosca implements Greatest-Of Selection with Cell Averaging. // gosca implements Greatest-Of Selection with Cell Averaging.
type gosca struct { type gosca struct {
guard int guard int
@@ -40,10 +42,14 @@ func (g *gosca) Thresholds(spectrum []float64) []float64 {
return spectrum[i] return spectrum[i]
} }


toLinear := func(db float64) float64 {
return math.Pow(10, db/10.0)
}

var leftSum, rightSum float64 var leftSum, rightSum float64
for k := 1; k <= train; k++ { for k := 1; k <= train; k++ {
leftSum += at(0 - guard - k)
rightSum += at(0 + guard + k)
leftSum += toLinear(at(0 - guard - k))
rightSum += toLinear(at(0 + guard + k))
} }


leftMean := leftSum * inv leftMean := leftSum * inv
@@ -52,13 +58,13 @@ func (g *gosca) Thresholds(spectrum []float64) []float64 {
if rightMean > noise { if rightMean > noise {
noise = rightMean noise = rightMean
} }
out[0] = noise + g.scaleDb
out[0] = 10*math.Log10(noise) + g.scaleDb


for i := 1; i < n; i++ { for i := 1; i < n; i++ {
leftSum -= at(i - 1 - guard - train)
leftSum += at(i - guard - 1)
rightSum -= at(i - 1 + guard + 1)
rightSum += at(i + guard + train)
leftSum -= toLinear(at(i - 1 - guard - train))
leftSum += toLinear(at(i - guard - 1))
rightSum -= toLinear(at(i - 1 + guard + 1))
rightSum += toLinear(at(i + guard + train))


leftMean = leftSum * inv leftMean = leftSum * inv
rightMean = rightSum * inv rightMean = rightSum * inv
@@ -66,7 +72,7 @@ func (g *gosca) Thresholds(spectrum []float64) []float64 {
if rightMean > noise { if rightMean > noise {
noise = rightMean noise = rightMean
} }
out[i] = noise + g.scaleDb
out[i] = 10*math.Log10(noise) + g.scaleDb
} }
return out return out
} }

+ 12
- 0
internal/cfar/os.go Просмотреть файл

@@ -55,6 +55,15 @@ func (o *orderedStat) Thresholds(spectrum []float64) []float64 {
sort.Float64s(win) sort.Float64s(win)
out[0] = win[o.rank] + o.scaleDb out[0] = win[o.rank] + o.scaleDb


rebuildWindow := func(bin int) {
win = win[:0]
for k := 1; k <= train; k++ {
win = append(win, at(bin-guard-k))
win = append(win, at(bin+guard+k))
}
sort.Float64s(win)
}

for i := 1; i < n; i++ { for i := 1; i < n; i++ {
removeFromSorted(&win, at(i-1-guard-train)) removeFromSorted(&win, at(i-1-guard-train))
removeFromSorted(&win, at(i-1+guard+1)) removeFromSorted(&win, at(i-1+guard+1))
@@ -62,6 +71,9 @@ func (o *orderedStat) Thresholds(spectrum []float64) []float64 {
insertSorted(&win, at(i-guard-1)) insertSorted(&win, at(i-guard-1))
insertSorted(&win, at(i+guard+train)) insertSorted(&win, at(i+guard+train))


if len(win) != 2*train {
rebuildWindow(i)
}
out[i] = win[o.rank] + o.scaleDb out[i] = win[o.rank] + o.scaleDb
} }
return out return out


Загрузка…
Отмена
Сохранить