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.

93 lines
3.2KB

  1. package pipeline
  2. import (
  3. "sdr-wideband-suite/internal/classifier"
  4. "sdr-wideband-suite/internal/detector"
  5. )
  6. // Candidate is the coarse output of the surveillance detector.
  7. // It intentionally stays lightweight and cheap to produce.
  8. type Candidate struct {
  9. ID int64 `json:"id"`
  10. CenterHz float64 `json:"center_hz"`
  11. BandwidthHz float64 `json:"bandwidth_hz"`
  12. PeakDb float64 `json:"peak_db"`
  13. SNRDb float64 `json:"snr_db"`
  14. FirstBin int `json:"first_bin"`
  15. LastBin int `json:"last_bin"`
  16. NoiseDb float64 `json:"noise_db,omitempty"`
  17. Source string `json:"source,omitempty"`
  18. Hint string `json:"hint,omitempty"`
  19. Evidence []LevelEvidence `json:"evidence,omitempty"`
  20. EvidenceState *CandidateEvidenceState `json:"evidence_state,omitempty"`
  21. }
  22. // LevelEvidence captures which analysis level produced a candidate.
  23. // This is intentionally lightweight for later multi-level fusion.
  24. type LevelEvidence struct {
  25. Level AnalysisLevel `json:"level"`
  26. Provenance string `json:"provenance,omitempty"`
  27. }
  28. // MonitorWindow describes a monitoring window to gate candidates.
  29. type MonitorWindow struct {
  30. Label string `json:"label,omitempty"`
  31. StartHz float64 `json:"start_hz,omitempty"`
  32. EndHz float64 `json:"end_hz,omitempty"`
  33. CenterHz float64 `json:"center_hz,omitempty"`
  34. SpanHz float64 `json:"span_hz,omitempty"`
  35. Source string `json:"source,omitempty"`
  36. }
  37. // RefinementWindow describes the local analysis span that refinement should use.
  38. type RefinementWindow struct {
  39. CenterHz float64 `json:"center_hz"`
  40. SpanHz float64 `json:"span_hz"`
  41. Source string `json:"source,omitempty"`
  42. }
  43. // Refinement contains higher-cost local analysis derived from a candidate.
  44. type Refinement struct {
  45. Candidate Candidate `json:"candidate"`
  46. Window RefinementWindow `json:"window"`
  47. Signal detector.Signal `json:"signal"`
  48. SnippetRate int `json:"snippet_rate"`
  49. Class *classifier.Classification `json:"class,omitempty"`
  50. Stage string `json:"stage,omitempty"`
  51. }
  52. func CandidatesFromSignals(signals []detector.Signal, source string) []Candidate {
  53. out := make([]Candidate, 0, len(signals))
  54. for _, s := range signals {
  55. hint := ""
  56. if s.Class != nil {
  57. hint = string(s.Class.ModType)
  58. }
  59. out = append(out, Candidate{
  60. ID: s.ID,
  61. CenterHz: s.CenterHz,
  62. BandwidthHz: s.BWHz,
  63. PeakDb: s.PeakDb,
  64. SNRDb: s.SNRDb,
  65. FirstBin: s.FirstBin,
  66. LastBin: s.LastBin,
  67. NoiseDb: s.NoiseDb,
  68. Source: source,
  69. Hint: hint,
  70. })
  71. }
  72. return out
  73. }
  74. func CandidatesFromSignalsWithLevel(signals []detector.Signal, source string, level AnalysisLevel) []Candidate {
  75. out := CandidatesFromSignals(signals, source)
  76. if level.Name == "" && level.FFTSize == 0 && level.SampleRate == 0 {
  77. return out
  78. }
  79. evidence := LevelEvidence{Level: level, Provenance: source}
  80. for i := range out {
  81. AddCandidateEvidence(&out[i], evidence)
  82. }
  83. return out
  84. }