瀏覽代碼

Add temporal IQ features to classifier

master
Jan Svabenik 3 天之前
父節點
當前提交
9d96de57b6
共有 6 個檔案被更改,包括 92 行新增3 行删除
  1. +8
    -0
      cmd/sdrd/main.go
  2. +8
    -1
      internal/classifier/classifier.go
  3. +1
    -1
      internal/classifier/classifier_test.go
  4. +69
    -0
      internal/classifier/features_iq.go
  5. +5
    -0
      internal/classifier/types.go
  6. +1
    -1
      internal/detector/detector.go

+ 8
- 0
cmd/sdrd/main.go 查看文件

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

"github.com/gorilla/websocket"

"sdr-visual-suite/internal/classifier"
"sdr-visual-suite/internal/config"
"sdr-visual-suite/internal/detector"
"sdr-visual-suite/internal/dsp"
@@ -704,6 +705,13 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
}
now := time.Now()
finished, signals := det.Process(now, spectrum, cfg.CenterHz)
// enrich classification with temporal IQ features
if len(iq) > 0 {
for i := range signals {
cls := classifier.Classify(classifier.SignalInput{FirstBin: signals[i].FirstBin, LastBin: signals[i].LastBin, SNRDb: signals[i].SNRDb}, spectrum, cfg.SampleRate, cfg.FFTSize, iq)
signals[i].Class = cls
}
}
eventMu.Lock()
for _, ev := range finished {
_ = enc.Encode(ev)


+ 8
- 1
internal/classifier/classifier.go 查看文件

@@ -1,11 +1,18 @@
package classifier

// Classify builds features and applies the rule-based classifier.
func Classify(input SignalInput, spectrum []float64, sampleRate int, fftSize int) *Classification {
func Classify(input SignalInput, spectrum []float64, sampleRate int, fftSize int, iq []complex64) *Classification {
if len(spectrum) == 0 || input.FirstBin < 0 || input.LastBin < 0 {
return nil
}
feat := ExtractFeatures(input, spectrum, sampleRate, fftSize)
if len(iq) > 0 {
envVar, zc, instStd, crest := ExtractTemporalFeatures(iq)
feat.EnvVariance = envVar
feat.ZeroCross = zc
feat.InstFreqStd = instStd
feat.CrestFactor = crest
}
cls := RuleClassify(feat)
return &cls
}

+ 1
- 1
internal/classifier/classifier_test.go 查看文件

@@ -14,7 +14,7 @@ func TestRuleClassifyWFM(t *testing.T) {
for i := start; i <= end; i++ {
spectrum[i] = -10
}
cls := Classify(SignalInput{FirstBin: start, LastBin: end}, spectrum, sampleRate, fftSize)
cls := Classify(SignalInput{FirstBin: start, LastBin: end}, spectrum, sampleRate, fftSize, nil)
if cls == nil || cls.ModType != ClassWFM {
t.Fatalf("expected WFM, got %+v", cls)
}


+ 69
- 0
internal/classifier/features_iq.go 查看文件

@@ -0,0 +1,69 @@
package classifier

import (
"math"
)

// ExtractTemporalFeatures computes simple time-domain features from IQ.
func ExtractTemporalFeatures(iq []complex64) (envVar float64, zeroCross float64, instFreqStd float64, crest float64) {
if len(iq) == 0 {
return 0, 0, 0, 0
}
env := make([]float64, len(iq))
var mean, rms float64
for i, v := range iq {
a := math.Hypot(float64(real(v)), float64(imag(v)))
env[i] = a
mean += a
rms += a * a
}
mean /= float64(len(iq))
rms = math.Sqrt(rms / float64(len(iq)))
// env variance
var sumVar float64
for _, v := range env {
d := v - mean
sumVar += d * d
}
envVar = sumVar / float64(len(iq))
if rms > 0 {
crest = maxFloat(env) / rms
}
// zero-crossing on real part
zc := 0
for i := 1; i < len(iq); i++ {
p := real(iq[i-1])
c := real(iq[i])
if (p >= 0 && c < 0) || (p < 0 && c >= 0) {
zc++
}
}
zeroCross = float64(zc) / float64(len(iq))
// instantaneous frequency std
if len(iq) > 1 {
var sum, sumSq float64
for i := 1; i < len(iq); i++ {
p := iq[i-1]
c := iq[i]
num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c))
den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c))
v := math.Atan2(num, den)
sum += v
sumSq += v * v
}
n := float64(len(iq) - 1)
mean := sum / n
instFreqStd = math.Sqrt(sumSq/n - mean*mean)
}
return
}

func maxFloat(vals []float64) float64 {
m := vals[0]
for _, v := range vals {
if v > m {
m = v
}
}
return m
}

+ 5
- 0
internal/classifier/types.go 查看文件

@@ -24,6 +24,11 @@ type Features struct {
Symmetry float64 `json:"symmetry"`
RolloffLeft float64 `json:"rolloff_left_db_khz"`
RolloffRight float64 `json:"rolloff_right_db_khz"`
// Temporal
EnvVariance float64 `json:"env_variance"`
ZeroCross float64 `json:"zero_cross_rate"`
InstFreqStd float64 `json:"inst_freq_std"`
CrestFactor float64 `json:"crest_factor"`
}

// Classification is the classifier output attached to signals/events.


+ 1
- 1
internal/detector/detector.go 查看文件

@@ -122,7 +122,7 @@ func (d *Detector) makeSignal(first, last int, peak float64, peakBin int, noise
centerFreq := centerHz + (centerBin-float64(d.nbins)/2.0)*d.binWidth
bw := float64(last-first+1) * d.binWidth
snr := peak - noise
cls := classifier.Classify(classifier.SignalInput{FirstBin: first, LastBin: last, SNRDb: snr}, spectrum, d.sampleRate, d.nbins)
cls := classifier.Classify(classifier.SignalInput{FirstBin: first, LastBin: last, SNRDb: snr}, spectrum, d.sampleRate, d.nbins, nil)
return Signal{
FirstBin: first,
LastBin: last,


Loading…
取消
儲存