Wideband autonomous SDR analysis engine forked from sdr-visual-suite
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
1.4KB

  1. package classifier
  2. import "math"
  3. func RuleClassify(feat Features) Classification {
  4. bw := feat.BW3dB
  5. flat := feat.SpectralFlat
  6. sym := feat.Symmetry
  7. p2a := feat.PeakToAvg
  8. best := ClassUnknown
  9. second := ClassUnknown
  10. conf := 0.3
  11. switch {
  12. case bw > 100e3:
  13. best = ClassWFM
  14. conf = 0.9
  15. case bw >= 6e3 && bw <= 16e3:
  16. best = ClassNFM
  17. conf = 0.8
  18. if flat > 0.7 {
  19. second = ClassNoise
  20. }
  21. case bw >= 500 && bw < 3e3:
  22. if sym > 0.2 {
  23. best = ClassSSBUSB
  24. conf = 0.7
  25. } else if sym < -0.2 {
  26. best = ClassSSBLSB
  27. conf = 0.7
  28. } else if p2a > 3 && flat < 0.4 {
  29. best = ClassAM
  30. conf = 0.6
  31. }
  32. case bw < 500:
  33. best = ClassCW
  34. conf = 0.7
  35. }
  36. // noise hint
  37. if best == ClassUnknown && flat > 0.85 && bw > 2e3 {
  38. best = ClassNoise
  39. conf = 0.6
  40. }
  41. // edge-case: if symmetry is strong, second best opposite side
  42. if (best == ClassSSBUSB || best == ClassSSBLSB) && second == ClassUnknown {
  43. if best == ClassSSBUSB {
  44. second = ClassSSBLSB
  45. } else {
  46. second = ClassSSBUSB
  47. }
  48. }
  49. // slightly scale confidence by feature strength
  50. if best == ClassNFM || best == ClassWFM {
  51. conf = conf * (0.8 + 0.2*clamp01(1-flat))
  52. }
  53. if best == ClassAM {
  54. conf = conf * (0.7 + 0.3*clamp01(p2a/6.0))
  55. }
  56. if math.IsNaN(conf) || conf <= 0 {
  57. conf = 0.3
  58. }
  59. return Classification{
  60. ModType: best,
  61. Confidence: conf,
  62. BW3dB: bw,
  63. Features: feat,
  64. SecondBest: second,
  65. }
  66. }