| @@ -236,10 +236,19 @@ func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult) pip | |||||
| if len(scheduled) == 0 && len(plan.Selected) > 0 { | if len(scheduled) == 0 && len(plan.Selected) > 0 { | ||||
| scheduled = append([]pipeline.ScheduledCandidate(nil), plan.Selected...) | 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{ | input := pipeline.RefinementInput{ | ||||
| Candidates: append([]pipeline.Candidate(nil), surv.Candidates...), | Candidates: append([]pipeline.Candidate(nil), surv.Candidates...), | ||||
| Scheduled: scheduled, | Scheduled: scheduled, | ||||
| Plan: plan, | Plan: plan, | ||||
| Windows: windows, | |||||
| SampleRate: rt.cfg.SampleRate, | SampleRate: rt.cfg.SampleRate, | ||||
| FFTSize: rt.cfg.FFTSize, | FFTSize: rt.cfg.FFTSize, | ||||
| CenterHz: rt.cfg.CenterHz, | CenterHz: rt.cfg.CenterHz, | ||||
| @@ -284,7 +293,7 @@ func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.Refin | |||||
| centerHz = rt.cfg.CenterHz | centerHz = rt.cfg.CenterHz | ||||
| } | } | ||||
| snips, snipRates := extractSignalIQBatch(extractMgr, art.iq, sampleRate, centerHz, selectedSignals) | 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)) | signals := make([]detector.Signal, 0, len(refined)) | ||||
| decisions := make([]pipeline.SignalDecision, 0, len(refined)) | decisions := make([]pipeline.SignalDecision, 0, len(refined)) | ||||
| for i, ref := range refined { | for i, ref := range refined { | ||||
| @@ -24,6 +24,7 @@ type RefinementInput struct { | |||||
| Candidates []Candidate `json:"candidates,omitempty"` | Candidates []Candidate `json:"candidates,omitempty"` | ||||
| Scheduled []ScheduledCandidate `json:"scheduled,omitempty"` | Scheduled []ScheduledCandidate `json:"scheduled,omitempty"` | ||||
| Plan RefinementPlan `json:"plan,omitempty"` | Plan RefinementPlan `json:"plan,omitempty"` | ||||
| Windows []RefinementWindow `json:"windows,omitempty"` | |||||
| SampleRate int `json:"sample_rate"` | SampleRate int `json:"sample_rate"` | ||||
| FFTSize int `json:"fft_size"` | FFTSize int `json:"fft_size"` | ||||
| CenterHz float64 `json:"center_hz"` | CenterHz float64 `json:"center_hz"` | ||||
| @@ -7,7 +7,7 @@ import ( | |||||
| // RefineCandidates upgrades coarse detector candidates into refined signals | // RefineCandidates upgrades coarse detector candidates into refined signals | ||||
| // by attaching local IQ-derived classification and PLL metadata. | // 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)) | out := make([]Refinement, 0, len(candidates)) | ||||
| for i, c := range candidates { | for i, c := range candidates { | ||||
| sig := detector.Signal{ | sig := detector.Signal{ | ||||
| @@ -44,8 +44,13 @@ func RefineCandidates(candidates []Candidate, spectrum []float64, sampleRate int | |||||
| cls.ModType = classifier.ClassWFMStereo | cls.ModType = classifier.ClassWFMStereo | ||||
| } | } | ||||
| } | } | ||||
| var window RefinementWindow | |||||
| if i < len(windows) { | |||||
| window = windows[i] | |||||
| } | |||||
| out = append(out, Refinement{ | out = append(out, Refinement{ | ||||
| Candidate: c, | Candidate: c, | ||||
| Window: window, | |||||
| Signal: sig, | Signal: sig, | ||||
| SnippetRate: snipRate, | SnippetRate: snipRate, | ||||
| Class: cls, | Class: cls, | ||||
| @@ -8,25 +8,33 @@ import ( | |||||
| // Candidate is the coarse output of the surveillance detector. | // Candidate is the coarse output of the surveillance detector. | ||||
| // It intentionally stays lightweight and cheap to produce. | // It intentionally stays lightweight and cheap to produce. | ||||
| type Candidate struct { | 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. | // Refinement contains higher-cost local analysis derived from a candidate. | ||||
| type Refinement struct { | 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 { | func CandidatesFromSignals(signals []detector.Signal, source string) []Candidate { | ||||