package classifier import "math" func RuleClassify(feat Features) Classification { bw := feat.BW3dB flat := feat.SpectralFlat sym := feat.Symmetry p2a := feat.PeakToAvg scores := map[SignalClass]float64{} add := func(c SignalClass, w float64) { if w <= 0 { return } scores[c] += w } switch { case bw >= 80e3: add(ClassWFM, 2.2) case bw >= 25e3 && bw < 80e3: add(ClassWFM, 1.4) add(ClassNFM, 0.8) case bw >= 6e3 && bw < 25e3: add(ClassNFM, 2.0) case bw >= 3e3 && bw < 6e3: add(ClassSSBUSB, 0.6) add(ClassSSBLSB, 0.6) if p2a > 2.5 && flat < 0.5 { add(ClassAM, 1.1) } case bw >= 500 && bw < 3e3: add(ClassSSBUSB, 0.8) add(ClassSSBLSB, 0.8) if p2a > 3 && flat < 0.4 { add(ClassAM, 1.0) } case bw >= 150 && bw < 500: add(ClassFSK, 0.5) add(ClassPSK, 0.5) case bw < 150: add(ClassCW, 1.6) } if sym > 0.2 { add(ClassSSBUSB, 1.2) } else if sym < -0.2 { add(ClassSSBLSB, 1.2) } if feat.EnvVariance < 0.6 && feat.InstFreqStd < 0.7 && bw >= 2000 && bw < 3000 { add(ClassFT8, 1.4) } if feat.EnvVariance < 0.4 && feat.InstFreqStd < 0.5 && bw >= 150 && bw < 500 { add(ClassWSPR, 1.3) } if feat.InstFreqStd > 0.9 { add(ClassFSK, 1.2) } else if feat.InstFreqStd < 0.25 { add(ClassPSK, 1.0) } if p2a > 2.5 && flat < 0.5 { add(ClassAM, 0.8) } if flat > 0.85 && bw > 2e3 { add(ClassNoise, 1.0) } if feat.InstFreqStd < 0.5 && feat.EnvVariance < 0.3 && bw >= 6e3 && bw < 25e3 { add(ClassDMR, 0.7) } best, bestScore, second, secondScore := top2(scores) if best == "" { best = ClassUnknown } if second == "" { second = ClassUnknown } conf := 0.3 if best != ClassUnknown { sum := bestScore + secondScore + 1e-6 conf = 0.3 + 0.7*(bestScore/sum) } if best == ClassNFM || best == ClassWFM { conf = conf * (0.8 + 0.2*clamp01(1-flat)) } if best == ClassAM { conf = conf * (0.7 + 0.3*clamp01(p2a/6.0)) } if math.IsNaN(conf) || conf <= 0 { conf = 0.3 } if (best == ClassSSBUSB || best == ClassSSBLSB) && second == ClassUnknown { if best == ClassSSBUSB { second = ClassSSBLSB } else { second = ClassSSBUSB } } return Classification{ ModType: best, Confidence: conf, BW3dB: bw, Features: feat, SecondBest: second, } } func top2(scores map[SignalClass]float64) (SignalClass, float64, SignalClass, float64) { var best, second SignalClass bestScore := 0.0 secondScore := 0.0 for k, v := range scores { if v > bestScore { second = best secondScore = bestScore best = k bestScore = v continue } if v > secondScore { second = k secondScore = v } } return best, bestScore, second, secondScore }