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.

125 lines
2.7KB

  1. package pipeline
  2. import "math"
  3. func AddCandidateEvidence(candidate *Candidate, evidence LevelEvidence) {
  4. if candidate == nil {
  5. return
  6. }
  7. levelName := evidence.Level.Name
  8. if levelName == "" {
  9. levelName = "unknown"
  10. }
  11. for _, ev := range candidate.Evidence {
  12. evLevel := ev.Level.Name
  13. if evLevel == "" {
  14. evLevel = "unknown"
  15. }
  16. if evLevel == levelName && ev.Provenance == evidence.Provenance {
  17. return
  18. }
  19. }
  20. candidate.Evidence = append(candidate.Evidence, evidence)
  21. }
  22. func MergeCandidateEvidence(dst *Candidate, src Candidate) {
  23. if dst == nil || len(src.Evidence) == 0 {
  24. return
  25. }
  26. for _, ev := range src.Evidence {
  27. AddCandidateEvidence(dst, ev)
  28. }
  29. }
  30. func CandidateEvidenceLevelCount(candidate Candidate) int {
  31. if len(candidate.Evidence) == 0 {
  32. return 0
  33. }
  34. levels := map[string]struct{}{}
  35. for _, ev := range candidate.Evidence {
  36. name := ev.Level.Name
  37. if name == "" {
  38. name = "unknown"
  39. }
  40. levels[name] = struct{}{}
  41. }
  42. return len(levels)
  43. }
  44. func FuseCandidates(primary []Candidate, derived []Candidate) []Candidate {
  45. if len(primary) == 0 && len(derived) == 0 {
  46. return nil
  47. }
  48. out := make([]Candidate, 0, len(primary)+len(derived))
  49. out = append(out, primary...)
  50. if len(derived) == 0 {
  51. return out
  52. }
  53. used := make([]bool, len(derived))
  54. for i := range out {
  55. for j, cand := range derived {
  56. if used[j] {
  57. continue
  58. }
  59. if !candidatesOverlap(out[i], cand) {
  60. continue
  61. }
  62. MergeCandidateEvidence(&out[i], cand)
  63. used[j] = true
  64. }
  65. }
  66. for j, cand := range derived {
  67. if used[j] {
  68. continue
  69. }
  70. out = append(out, cand)
  71. }
  72. return out
  73. }
  74. func candidatesOverlap(a Candidate, b Candidate) bool {
  75. spanA := candidateSpanHz(a)
  76. spanB := candidateSpanHz(b)
  77. if spanA <= 0 {
  78. spanA = 25000
  79. }
  80. if spanB <= 0 {
  81. spanB = 25000
  82. }
  83. guard := 0.0
  84. if binA, binB := candidateBinHz(a), candidateBinHz(b); binA > 0 || binB > 0 {
  85. guard = 0.5 * math.Max(binA, binB)
  86. }
  87. leftA := a.CenterHz - spanA/2 - guard
  88. rightA := a.CenterHz + spanA/2 + guard
  89. leftB := b.CenterHz - spanB/2 - guard
  90. rightB := b.CenterHz + spanB/2 + guard
  91. return leftA <= rightB && leftB <= rightA
  92. }
  93. func candidateSpanHz(candidate Candidate) float64 {
  94. if candidate.BandwidthHz > 0 {
  95. return candidate.BandwidthHz
  96. }
  97. if candidate.LastBin < candidate.FirstBin {
  98. return 0
  99. }
  100. binHz := candidateBinHz(candidate)
  101. if binHz <= 0 {
  102. return 0
  103. }
  104. return float64(candidate.LastBin-candidate.FirstBin+1) * binHz
  105. }
  106. func candidateBinHz(candidate Candidate) float64 {
  107. for _, ev := range candidate.Evidence {
  108. if ev.Level.BinHz > 0 {
  109. return ev.Level.BinHz
  110. }
  111. if ev.Level.SampleRate > 0 && ev.Level.FFTSize > 0 {
  112. return float64(ev.Level.SampleRate) / float64(ev.Level.FFTSize)
  113. }
  114. }
  115. return 0
  116. }