| @@ -236,10 +236,19 @@ func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult) pip | |||
| if len(scheduled) == 0 && len(plan.Selected) > 0 { | |||
| scheduled = append([]pipeline.ScheduledCandidate(nil), plan.Selected...) | |||
| } | |||
| windows := make([]pipeline.RefinementWindow, 0, len(scheduled)) | |||
| for _, sc := range scheduled { | |||
| windows = append(windows, pipeline.RefinementWindow{ | |||
| CenterHz: sc.Candidate.CenterHz, | |||
| SpanHz: sc.Candidate.BandwidthHz, | |||
| Source: "candidate", | |||
| }) | |||
| } | |||
| input := pipeline.RefinementInput{ | |||
| Candidates: append([]pipeline.Candidate(nil), surv.Candidates...), | |||
| Scheduled: scheduled, | |||
| Plan: plan, | |||
| Windows: windows, | |||
| SampleRate: rt.cfg.SampleRate, | |||
| FFTSize: rt.cfg.FFTSize, | |||
| CenterHz: rt.cfg.CenterHz, | |||
| @@ -284,7 +293,7 @@ func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.Refin | |||
| centerHz = rt.cfg.CenterHz | |||
| } | |||
| snips, snipRates := extractSignalIQBatch(extractMgr, art.iq, sampleRate, centerHz, selectedSignals) | |||
| refined := pipeline.RefineCandidates(selectedCandidates, art.spectrum, sampleRate, fftSize, snips, snipRates, classifier.ClassifierMode(rt.cfg.ClassifierMode)) | |||
| refined := pipeline.RefineCandidates(selectedCandidates, input.Windows, art.spectrum, sampleRate, fftSize, snips, snipRates, classifier.ClassifierMode(rt.cfg.ClassifierMode)) | |||
| signals := make([]detector.Signal, 0, len(refined)) | |||
| decisions := make([]pipeline.SignalDecision, 0, len(refined)) | |||
| for i, ref := range refined { | |||
| @@ -24,6 +24,7 @@ type RefinementInput struct { | |||
| Candidates []Candidate `json:"candidates,omitempty"` | |||
| Scheduled []ScheduledCandidate `json:"scheduled,omitempty"` | |||
| Plan RefinementPlan `json:"plan,omitempty"` | |||
| Windows []RefinementWindow `json:"windows,omitempty"` | |||
| SampleRate int `json:"sample_rate"` | |||
| FFTSize int `json:"fft_size"` | |||
| CenterHz float64 `json:"center_hz"` | |||
| @@ -7,7 +7,7 @@ import ( | |||
| // RefineCandidates upgrades coarse detector candidates into refined signals | |||
| // by attaching local IQ-derived classification and PLL metadata. | |||
| func RefineCandidates(candidates []Candidate, spectrum []float64, sampleRate int, fftSize int, snippets [][]complex64, snippetRates []int, mode classifier.ClassifierMode) []Refinement { | |||
| func RefineCandidates(candidates []Candidate, windows []RefinementWindow, spectrum []float64, sampleRate int, fftSize int, snippets [][]complex64, snippetRates []int, mode classifier.ClassifierMode) []Refinement { | |||
| out := make([]Refinement, 0, len(candidates)) | |||
| for i, c := range candidates { | |||
| sig := detector.Signal{ | |||
| @@ -44,8 +44,13 @@ func RefineCandidates(candidates []Candidate, spectrum []float64, sampleRate int | |||
| cls.ModType = classifier.ClassWFMStereo | |||
| } | |||
| } | |||
| var window RefinementWindow | |||
| if i < len(windows) { | |||
| window = windows[i] | |||
| } | |||
| out = append(out, Refinement{ | |||
| Candidate: c, | |||
| Window: window, | |||
| Signal: sig, | |||
| SnippetRate: snipRate, | |||
| Class: cls, | |||
| @@ -8,25 +8,33 @@ import ( | |||
| // Candidate is the coarse output of the surveillance detector. | |||
| // It intentionally stays lightweight and cheap to produce. | |||
| type Candidate struct { | |||
| ID int64 `json:"id"` | |||
| CenterHz float64 `json:"center_hz"` | |||
| BandwidthHz float64 `json:"bandwidth_hz"` | |||
| PeakDb float64 `json:"peak_db"` | |||
| SNRDb float64 `json:"snr_db"` | |||
| FirstBin int `json:"first_bin"` | |||
| LastBin int `json:"last_bin"` | |||
| NoiseDb float64 `json:"noise_db,omitempty"` | |||
| Source string `json:"source,omitempty"` | |||
| Hint string `json:"hint,omitempty"` | |||
| ID int64 `json:"id"` | |||
| CenterHz float64 `json:"center_hz"` | |||
| BandwidthHz float64 `json:"bandwidth_hz"` | |||
| PeakDb float64 `json:"peak_db"` | |||
| SNRDb float64 `json:"snr_db"` | |||
| FirstBin int `json:"first_bin"` | |||
| LastBin int `json:"last_bin"` | |||
| NoiseDb float64 `json:"noise_db,omitempty"` | |||
| Source string `json:"source,omitempty"` | |||
| Hint string `json:"hint,omitempty"` | |||
| } | |||
| // RefinementWindow describes the local analysis span that refinement should use. | |||
| type RefinementWindow struct { | |||
| CenterHz float64 `json:"center_hz"` | |||
| SpanHz float64 `json:"span_hz"` | |||
| Source string `json:"source,omitempty"` | |||
| } | |||
| // Refinement contains higher-cost local analysis derived from a candidate. | |||
| type Refinement struct { | |||
| Candidate Candidate `json:"candidate"` | |||
| Signal detector.Signal `json:"signal"` | |||
| SnippetRate int `json:"snippet_rate"` | |||
| Class *classifier.Classification `json:"class,omitempty"` | |||
| Stage string `json:"stage,omitempty"` | |||
| Candidate Candidate `json:"candidate"` | |||
| Window RefinementWindow `json:"window"` | |||
| Signal detector.Signal `json:"signal"` | |||
| SnippetRate int `json:"snippet_rate"` | |||
| Class *classifier.Classification `json:"class,omitempty"` | |||
| Stage string `json:"stage,omitempty"` | |||
| } | |||
| func CandidatesFromSignals(signals []detector.Signal, source string) []Candidate { | |||