Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

276 rindas
7.2KB

  1. package main
  2. import (
  3. "sort"
  4. "strings"
  5. "sdr-wideband-suite/internal/pipeline"
  6. )
  7. type SurveillanceLevelSummary struct {
  8. Name string `json:"name"`
  9. Role string `json:"role,omitempty"`
  10. Truth string `json:"truth,omitempty"`
  11. Kind string `json:"kind,omitempty"`
  12. SampleRate int `json:"sample_rate,omitempty"`
  13. FFTSize int `json:"fft_size,omitempty"`
  14. BinHz float64 `json:"bin_hz,omitempty"`
  15. Decimation int `json:"decimation,omitempty"`
  16. SpanHz float64 `json:"span_hz,omitempty"`
  17. CenterHz float64 `json:"center_hz,omitempty"`
  18. Source string `json:"source,omitempty"`
  19. SpectrumBins int `json:"spectrum_bins,omitempty"`
  20. }
  21. type CandidateEvidenceSummary struct {
  22. Level string `json:"level"`
  23. Role string `json:"role,omitempty"`
  24. Kind string `json:"kind,omitempty"`
  25. Provenance string `json:"provenance,omitempty"`
  26. Count int `json:"count"`
  27. }
  28. type CandidateEvidenceStateSummary struct {
  29. Total int `json:"total"`
  30. WithEvidence int `json:"with_evidence"`
  31. Fused int `json:"fused"`
  32. MultiLevelConfirmed int `json:"multi_level_confirmed"`
  33. DerivedOnly int `json:"derived_only"`
  34. SupportOnly int `json:"support_only"`
  35. PrimaryPresent int `json:"primary_present"`
  36. DerivedPresent int `json:"derived_present"`
  37. SupportPresent int `json:"support_present"`
  38. PrimaryOnly int `json:"primary_only"`
  39. }
  40. type CandidateWindowSummary struct {
  41. Index int `json:"index"`
  42. Label string `json:"label,omitempty"`
  43. Source string `json:"source,omitempty"`
  44. StartHz float64 `json:"start_hz,omitempty"`
  45. EndHz float64 `json:"end_hz,omitempty"`
  46. CenterHz float64 `json:"center_hz,omitempty"`
  47. SpanHz float64 `json:"span_hz,omitempty"`
  48. PriorityBias float64 `json:"priority_bias,omitempty"`
  49. Candidates int `json:"candidates"`
  50. }
  51. func buildSurveillanceLevelSummaries(set pipeline.SurveillanceLevelSet, spectra []pipeline.SurveillanceLevelSpectrum) map[string]SurveillanceLevelSummary {
  52. if set.Primary.Name == "" && len(set.Derived) == 0 && len(set.Support) == 0 && set.Presentation.Name == "" && len(set.All) == 0 {
  53. return nil
  54. }
  55. bins := map[string]int{}
  56. for _, spec := range spectra {
  57. if spec.Level.Name == "" || len(spec.Spectrum) == 0 {
  58. continue
  59. }
  60. bins[spec.Level.Name] = len(spec.Spectrum)
  61. }
  62. levels := set.All
  63. if len(levels) == 0 {
  64. if set.Primary.Name != "" {
  65. levels = append(levels, set.Primary)
  66. }
  67. if len(set.Derived) > 0 {
  68. levels = append(levels, set.Derived...)
  69. }
  70. if len(set.Support) > 0 {
  71. levels = append(levels, set.Support...)
  72. }
  73. if set.Presentation.Name != "" {
  74. levels = append(levels, set.Presentation)
  75. }
  76. }
  77. out := make(map[string]SurveillanceLevelSummary, len(levels))
  78. for _, level := range levels {
  79. name := level.Name
  80. if name == "" {
  81. continue
  82. }
  83. binHz := level.BinHz
  84. if binHz == 0 && level.SampleRate > 0 && level.FFTSize > 0 {
  85. binHz = float64(level.SampleRate) / float64(level.FFTSize)
  86. }
  87. kind := evidenceKind(level)
  88. out[name] = SurveillanceLevelSummary{
  89. Name: name,
  90. Role: level.Role,
  91. Truth: level.Truth,
  92. Kind: kind,
  93. SampleRate: level.SampleRate,
  94. FFTSize: level.FFTSize,
  95. BinHz: binHz,
  96. Decimation: level.Decimation,
  97. SpanHz: level.SpanHz,
  98. CenterHz: level.CenterHz,
  99. Source: level.Source,
  100. SpectrumBins: bins[name],
  101. }
  102. }
  103. if len(out) == 0 {
  104. return nil
  105. }
  106. return out
  107. }
  108. func buildCandidateSourceSummary(candidates []pipeline.Candidate) map[string]int {
  109. if len(candidates) == 0 {
  110. return nil
  111. }
  112. out := map[string]int{}
  113. for _, cand := range candidates {
  114. if cand.Source == "" {
  115. continue
  116. }
  117. out[cand.Source]++
  118. }
  119. if len(out) == 0 {
  120. return nil
  121. }
  122. return out
  123. }
  124. func buildCandidateEvidenceSummary(candidates []pipeline.Candidate) []CandidateEvidenceSummary {
  125. if len(candidates) == 0 {
  126. return nil
  127. }
  128. type key struct {
  129. level string
  130. role string
  131. kind string
  132. provenance string
  133. }
  134. counts := map[key]int{}
  135. for _, cand := range candidates {
  136. for _, ev := range cand.Evidence {
  137. name := ev.Level.Name
  138. if name == "" {
  139. name = "unknown"
  140. }
  141. role := strings.TrimSpace(ev.Level.Role)
  142. kind := evidenceKind(ev.Level)
  143. k := key{level: name, role: role, kind: kind, provenance: ev.Provenance}
  144. counts[k]++
  145. }
  146. }
  147. if len(counts) == 0 {
  148. return nil
  149. }
  150. out := make([]CandidateEvidenceSummary, 0, len(counts))
  151. for k, v := range counts {
  152. out = append(out, CandidateEvidenceSummary{Level: k.level, Role: k.role, Kind: k.kind, Provenance: k.provenance, Count: v})
  153. }
  154. sort.Slice(out, func(i, j int) bool {
  155. if out[i].Count == out[j].Count {
  156. if out[i].Level == out[j].Level {
  157. if out[i].Kind == out[j].Kind {
  158. return out[i].Provenance < out[j].Provenance
  159. }
  160. return out[i].Kind < out[j].Kind
  161. }
  162. return out[i].Level < out[j].Level
  163. }
  164. return out[i].Count > out[j].Count
  165. })
  166. return out
  167. }
  168. func buildCandidateEvidenceStateSummary(candidates []pipeline.Candidate) *CandidateEvidenceStateSummary {
  169. if len(candidates) == 0 {
  170. return nil
  171. }
  172. summary := CandidateEvidenceStateSummary{Total: len(candidates)}
  173. for _, cand := range candidates {
  174. state := pipeline.CandidateEvidenceStateFor(cand)
  175. if state.TotalLevelEntries == 0 {
  176. continue
  177. }
  178. summary.WithEvidence++
  179. if state.Fused {
  180. summary.Fused++
  181. }
  182. if state.MultiLevelConfirmed {
  183. summary.MultiLevelConfirmed++
  184. }
  185. if state.DerivedOnly {
  186. summary.DerivedOnly++
  187. }
  188. if state.SupportOnly {
  189. summary.SupportOnly++
  190. }
  191. if state.PrimaryLevelCount > 0 {
  192. summary.PrimaryPresent++
  193. }
  194. if state.DerivedLevelCount > 0 {
  195. summary.DerivedPresent++
  196. }
  197. if state.SupportLevelCount > 0 {
  198. summary.SupportPresent++
  199. }
  200. if state.PrimaryLevelCount > 0 && state.DerivedLevelCount == 0 {
  201. summary.PrimaryOnly++
  202. }
  203. }
  204. if summary.WithEvidence == 0 {
  205. return nil
  206. }
  207. return &summary
  208. }
  209. func buildCandidateWindowSummary(candidates []pipeline.Candidate, windows []pipeline.MonitorWindow) []CandidateWindowSummary {
  210. if len(windows) == 0 {
  211. return nil
  212. }
  213. out := make([]CandidateWindowSummary, 0, len(windows))
  214. index := make(map[int]int, len(windows))
  215. for _, win := range windows {
  216. entry := CandidateWindowSummary{
  217. Index: win.Index,
  218. Label: win.Label,
  219. Source: win.Source,
  220. StartHz: win.StartHz,
  221. EndHz: win.EndHz,
  222. CenterHz: win.CenterHz,
  223. SpanHz: win.SpanHz,
  224. PriorityBias: win.PriorityBias,
  225. }
  226. index[win.Index] = len(out)
  227. out = append(out, entry)
  228. }
  229. totalCandidates := 0
  230. for _, cand := range candidates {
  231. matches := cand.MonitorMatches
  232. if len(matches) == 0 {
  233. matches = pipeline.MonitorWindowMatchesForCandidate(windows, cand)
  234. }
  235. for _, match := range matches {
  236. idx, ok := index[match.Index]
  237. if !ok {
  238. continue
  239. }
  240. out[idx].Candidates++
  241. totalCandidates++
  242. }
  243. }
  244. if totalCandidates == 0 {
  245. return nil
  246. }
  247. sort.Slice(out, func(i, j int) bool {
  248. return out[i].Index < out[j].Index
  249. })
  250. return out
  251. }
  252. func evidenceKind(level pipeline.AnalysisLevel) string {
  253. if pipeline.IsPresentationLevel(level) {
  254. return "presentation"
  255. }
  256. if pipeline.IsSupportLevel(level) {
  257. return "support"
  258. }
  259. if pipeline.IsDetectionLevel(level) {
  260. return "detection"
  261. }
  262. return "unknown"
  263. }