|
- package classifier
-
- import "testing"
-
- func TestRuleClassifyWFM(t *testing.T) {
- sampleRate := 1_000_000
- fftSize := 1024
- spectrum := make([]float64, fftSize)
- for i := range spectrum {
- spectrum[i] = -100
- }
- start := 100
- end := 350
- for i := start; i <= end; i++ {
- spectrum[i] = -10
- }
- cls := Classify(SignalInput{FirstBin: start, LastBin: end, CenterHz: 100e6, SNRDb: 30}, spectrum, sampleRate, fftSize, nil, ModeCombined)
- if cls == nil || cls.ModType != ClassWFM {
- t.Fatalf("expected WFM, got %+v", cls)
- }
- }
-
- func TestSoftmaxConfidence(t *testing.T) {
- scores1 := map[SignalClass]float64{ClassNFM: 2.0, ClassAM: 0.3, ClassNoise: 0.1}
- c1 := softmaxConfidence(scores1, ClassNFM)
- if c1 < 0.7 {
- t.Fatalf("clear winner should have high confidence: %f", c1)
- }
-
- scores2 := map[SignalClass]float64{ClassSSBUSB: 1.0, ClassSSBLSB: 0.9, ClassAM: 0.8}
- c2 := softmaxConfidence(scores2, ClassSSBUSB)
- if c2 > 0.5 {
- t.Fatalf("ambiguous should have low confidence: %f", c2)
- }
-
- c3 := softmaxConfidence(map[SignalClass]float64{}, ClassNFM)
- if c3 != 0.1 {
- t.Fatalf("empty should return 0.1: %f", c3)
- }
- }
-
- func TestClassifierProfiles(t *testing.T) {
- tests := []struct {
- name string
- feat Features
- centerHz float64
- snrDb float64
- wantBest SignalClass
- }{
- {
- name: "FM Broadcast 100 MHz",
- feat: Features{BW3dB: 120000, SpectralFlat: 0.3, PeakToAvg: 1.5, Symmetry: 0.05,
- RolloffLeft: 20, RolloffRight: 22, EnvVariance: 0.01, InstFreqStd: 0.8},
- centerHz: 100.0e6, snrDb: 40,
- wantBest: ClassWFM,
- },
- {
- name: "FT8 auf 7.074 MHz",
- feat: Features{BW3dB: 2500, SpectralFlat: 0.6, PeakToAvg: 1.8, Symmetry: 0.1,
- EnvVariance: 0.03, InstFreqStd: 0.4},
- centerHz: 7.074e6, snrDb: 15,
- wantBest: ClassFT8,
- },
- {
- name: "USB Voice 14.230 MHz",
- feat: Features{BW3dB: 2800, SpectralFlat: 0.35, PeakToAvg: 3.5, Symmetry: 0.4,
- RolloffLeft: 5, RolloffRight: 18, EnvVariance: 0.25, InstFreqStd: 0.6},
- centerHz: 14.230e6, snrDb: 25,
- wantBest: ClassSSBUSB,
- },
- {
- name: "DMR auf 438 MHz",
- feat: Features{BW3dB: 12500, SpectralFlat: 0.7, PeakToAvg: 1.2, Symmetry: 0.02,
- RolloffLeft: 25, RolloffRight: 24, EnvVariance: 0.01, InstFreqStd: 0.35},
- centerHz: 438.5e6, snrDb: 20,
- wantBest: ClassDMR,
- },
- {
- name: "Airband AM 121.5 MHz",
- feat: Features{BW3dB: 7000, SpectralFlat: 0.25, PeakToAvg: 4.0, Symmetry: 0.05,
- RolloffLeft: 15, RolloffRight: 16, EnvVariance: 0.2, InstFreqStd: 0.7},
- centerHz: 121.5e6, snrDb: 30,
- wantBest: ClassAM,
- },
- {
- name: "CW auf 7.020 MHz",
- feat: Features{BW3dB: 80, SpectralFlat: 0.15, PeakToAvg: 8.0, Symmetry: 0.0,
- EnvVariance: 0.9, InstFreqStd: 0.05, CrestFactor: 3.5},
- centerHz: 7.020e6, snrDb: 20,
- wantBest: ClassCW,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cls := RuleClassify(tt.feat, tt.centerHz, tt.snrDb)
- if cls.ModType != tt.wantBest {
- t.Errorf("got %s (conf=%.2f), want %s. Scores: %v", cls.ModType, cls.Confidence, tt.wantBest, cls.Scores)
- }
- })
- }
- }
-
- func TestLowSNRConfidence(t *testing.T) {
- feat := Features{BW3dB: 3000, SpectralFlat: 0.5, PeakToAvg: 1.5}
- cls := RuleClassify(feat, 14.2e6, 5)
- if cls.Confidence > 0.5 {
- t.Errorf("low SNR should have low confidence: got %.2f", cls.Confidence)
- }
- }
|