Wideband autonomous SDR analysis engine forked from sdr-visual-suite
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

282 satır
7.5KB

  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. Priority float64 `json:"priority,omitempty"`
  49. PriorityBias float64 `json:"priority_bias,omitempty"`
  50. AutoRecord bool `json:"auto_record,omitempty"`
  51. AutoDecode bool `json:"auto_decode,omitempty"`
  52. Candidates int `json:"candidates"`
  53. }
  54. func buildSurveillanceLevelSummaries(set pipeline.SurveillanceLevelSet, spectra []pipeline.SurveillanceLevelSpectrum) map[string]SurveillanceLevelSummary {
  55. if set.Primary.Name == "" && len(set.Derived) == 0 && len(set.Support) == 0 && set.Presentation.Name == "" && len(set.All) == 0 {
  56. return nil
  57. }
  58. bins := map[string]int{}
  59. for _, spec := range spectra {
  60. if spec.Level.Name == "" || len(spec.Spectrum) == 0 {
  61. continue
  62. }
  63. bins[spec.Level.Name] = len(spec.Spectrum)
  64. }
  65. levels := set.All
  66. if len(levels) == 0 {
  67. if set.Primary.Name != "" {
  68. levels = append(levels, set.Primary)
  69. }
  70. if len(set.Derived) > 0 {
  71. levels = append(levels, set.Derived...)
  72. }
  73. if len(set.Support) > 0 {
  74. levels = append(levels, set.Support...)
  75. }
  76. if set.Presentation.Name != "" {
  77. levels = append(levels, set.Presentation)
  78. }
  79. }
  80. out := make(map[string]SurveillanceLevelSummary, len(levels))
  81. for _, level := range levels {
  82. name := level.Name
  83. if name == "" {
  84. continue
  85. }
  86. binHz := level.BinHz
  87. if binHz == 0 && level.SampleRate > 0 && level.FFTSize > 0 {
  88. binHz = float64(level.SampleRate) / float64(level.FFTSize)
  89. }
  90. kind := evidenceKind(level)
  91. out[name] = SurveillanceLevelSummary{
  92. Name: name,
  93. Role: level.Role,
  94. Truth: level.Truth,
  95. Kind: kind,
  96. SampleRate: level.SampleRate,
  97. FFTSize: level.FFTSize,
  98. BinHz: binHz,
  99. Decimation: level.Decimation,
  100. SpanHz: level.SpanHz,
  101. CenterHz: level.CenterHz,
  102. Source: level.Source,
  103. SpectrumBins: bins[name],
  104. }
  105. }
  106. if len(out) == 0 {
  107. return nil
  108. }
  109. return out
  110. }
  111. func buildCandidateSourceSummary(candidates []pipeline.Candidate) map[string]int {
  112. if len(candidates) == 0 {
  113. return nil
  114. }
  115. out := map[string]int{}
  116. for _, cand := range candidates {
  117. if cand.Source == "" {
  118. continue
  119. }
  120. out[cand.Source]++
  121. }
  122. if len(out) == 0 {
  123. return nil
  124. }
  125. return out
  126. }
  127. func buildCandidateEvidenceSummary(candidates []pipeline.Candidate) []CandidateEvidenceSummary {
  128. if len(candidates) == 0 {
  129. return nil
  130. }
  131. type key struct {
  132. level string
  133. role string
  134. kind string
  135. provenance string
  136. }
  137. counts := map[key]int{}
  138. for _, cand := range candidates {
  139. for _, ev := range cand.Evidence {
  140. name := ev.Level.Name
  141. if name == "" {
  142. name = "unknown"
  143. }
  144. role := strings.TrimSpace(ev.Level.Role)
  145. kind := evidenceKind(ev.Level)
  146. k := key{level: name, role: role, kind: kind, provenance: ev.Provenance}
  147. counts[k]++
  148. }
  149. }
  150. if len(counts) == 0 {
  151. return nil
  152. }
  153. out := make([]CandidateEvidenceSummary, 0, len(counts))
  154. for k, v := range counts {
  155. out = append(out, CandidateEvidenceSummary{Level: k.level, Role: k.role, Kind: k.kind, Provenance: k.provenance, Count: v})
  156. }
  157. sort.Slice(out, func(i, j int) bool {
  158. if out[i].Count == out[j].Count {
  159. if out[i].Level == out[j].Level {
  160. if out[i].Kind == out[j].Kind {
  161. return out[i].Provenance < out[j].Provenance
  162. }
  163. return out[i].Kind < out[j].Kind
  164. }
  165. return out[i].Level < out[j].Level
  166. }
  167. return out[i].Count > out[j].Count
  168. })
  169. return out
  170. }
  171. func buildCandidateEvidenceStateSummary(candidates []pipeline.Candidate) *CandidateEvidenceStateSummary {
  172. if len(candidates) == 0 {
  173. return nil
  174. }
  175. summary := CandidateEvidenceStateSummary{Total: len(candidates)}
  176. for _, cand := range candidates {
  177. state := pipeline.CandidateEvidenceStateFor(cand)
  178. if state.TotalLevelEntries == 0 {
  179. continue
  180. }
  181. summary.WithEvidence++
  182. if state.Fused {
  183. summary.Fused++
  184. }
  185. if state.MultiLevelConfirmed {
  186. summary.MultiLevelConfirmed++
  187. }
  188. if state.DerivedOnly {
  189. summary.DerivedOnly++
  190. }
  191. if state.SupportOnly {
  192. summary.SupportOnly++
  193. }
  194. if state.PrimaryLevelCount > 0 {
  195. summary.PrimaryPresent++
  196. }
  197. if state.DerivedLevelCount > 0 {
  198. summary.DerivedPresent++
  199. }
  200. if state.SupportLevelCount > 0 {
  201. summary.SupportPresent++
  202. }
  203. if state.PrimaryLevelCount > 0 && state.DerivedLevelCount == 0 {
  204. summary.PrimaryOnly++
  205. }
  206. }
  207. if summary.WithEvidence == 0 {
  208. return nil
  209. }
  210. return &summary
  211. }
  212. func buildCandidateWindowSummary(candidates []pipeline.Candidate, windows []pipeline.MonitorWindow) []CandidateWindowSummary {
  213. if len(windows) == 0 {
  214. return nil
  215. }
  216. out := make([]CandidateWindowSummary, 0, len(windows))
  217. index := make(map[int]int, len(windows))
  218. for _, win := range windows {
  219. entry := CandidateWindowSummary{
  220. Index: win.Index,
  221. Label: win.Label,
  222. Source: win.Source,
  223. StartHz: win.StartHz,
  224. EndHz: win.EndHz,
  225. CenterHz: win.CenterHz,
  226. SpanHz: win.SpanHz,
  227. Priority: win.Priority,
  228. PriorityBias: win.PriorityBias,
  229. AutoRecord: win.AutoRecord,
  230. AutoDecode: win.AutoDecode,
  231. }
  232. index[win.Index] = len(out)
  233. out = append(out, entry)
  234. }
  235. totalCandidates := 0
  236. for _, cand := range candidates {
  237. matches := cand.MonitorMatches
  238. if len(matches) == 0 {
  239. matches = pipeline.MonitorWindowMatchesForCandidate(windows, cand)
  240. }
  241. for _, match := range matches {
  242. idx, ok := index[match.Index]
  243. if !ok {
  244. continue
  245. }
  246. out[idx].Candidates++
  247. totalCandidates++
  248. }
  249. }
  250. if totalCandidates == 0 {
  251. return nil
  252. }
  253. sort.Slice(out, func(i, j int) bool {
  254. return out[i].Index < out[j].Index
  255. })
  256. return out
  257. }
  258. func evidenceKind(level pipeline.AnalysisLevel) string {
  259. if pipeline.IsPresentationLevel(level) {
  260. return "presentation"
  261. }
  262. if pipeline.IsSupportLevel(level) {
  263. return "support"
  264. }
  265. if pipeline.IsDetectionLevel(level) {
  266. return "detection"
  267. }
  268. return "unknown"
  269. }