Quellcode durchsuchen

dsp: decouple refinement step

master
Jan Svabenik vor 11 Stunden
Ursprung
Commit
4b9a48fe3f
7 geänderte Dateien mit 109 neuen und 99 gelöschten Zeilen
  1. +4
    -4
      cmd/sdrd/decision_budget.go
  2. +6
    -8
      cmd/sdrd/dsp_loop.go
  3. +10
    -10
      cmd/sdrd/http_handlers.go
  4. +3
    -3
      cmd/sdrd/phase_snapshot_test.go
  5. +4
    -5
      cmd/sdrd/phase_state.go
  6. +11
    -9
      cmd/sdrd/phase_state_test.go
  7. +71
    -60
      cmd/sdrd/pipeline_runtime.go

+ 4
- 4
cmd/sdrd/decision_budget.go Datei anzeigen

@@ -93,8 +93,8 @@ func (dq *decisionQueues) Apply(decisions []pipeline.SignalDecision, maxRecord i
purgeExpired(dq.recordHold, now) purgeExpired(dq.recordHold, now)
purgeExpired(dq.decodeHold, now) purgeExpired(dq.decodeHold, now)


recSelected := selectQueued(dq.record, dq.recordHold, maxRecord, hold, now, policy)
decSelected := selectQueued(dq.decode, dq.decodeHold, maxDecode, hold, now, policy)
recSelected := selectQueued("record", dq.record, dq.recordHold, maxRecord, hold, now, policy)
decSelected := selectQueued("decode", dq.decode, dq.decodeHold, maxDecode, hold, now, policy)


stats := decisionQueueStats{ stats := decisionQueueStats{
RecordQueued: len(dq.record), RecordQueued: len(dq.record),
@@ -127,7 +127,7 @@ func (dq *decisionQueues) Apply(decisions []pipeline.SignalDecision, maxRecord i
return stats return stats
} }


func selectQueued(queue map[int64]*queuedDecision, hold map[int64]time.Time, max int, holdDur time.Duration, now time.Time, policy pipeline.Policy) map[int64]struct{} {
func selectQueued(queueName string, queue map[int64]*queuedDecision, hold map[int64]time.Time, max int, holdDur time.Duration, now time.Time, policy pipeline.Policy) map[int64]struct{} {
selected := map[int64]struct{}{} selected := map[int64]struct{}{}
if len(queue) == 0 { if len(queue) == 0 {
return selected return selected
@@ -147,7 +147,7 @@ func selectQueued(queue map[int64]*queuedDecision, hold map[int64]time.Time, max
if hint == "" { if hint == "" {
hint = qd.Class hint = qd.Class
} }
policyBoost := pipeline.CandidatePriorityBoost(policy, hint)
policyBoost := pipeline.DecisionPriorityBoost(policy, hint, qd.Class, queueName)
scoredList = append(scoredList, scored{id: id, score: qd.SNRDb + boost + policyBoost}) scoredList = append(scoredList, scored{id: id, score: qd.SNRDb + boost + policyBoost})
} }
sort.Slice(scoredList, func(i, j int) bool { sort.Slice(scoredList, func(i, j int) bool {


+ 6
- 8
cmd/sdrd/dsp_loop.go Datei anzeigen

@@ -58,14 +58,13 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
rt.gotSamples = true rt.gotSamples = true
} }
state.surveillance = rt.buildSurveillanceResult(art) state.surveillance = rt.buildSurveillanceResult(art)
state.refinementInput = rt.buildRefinementInput(state.surveillance)
state.refinement = rt.runRefinement(art, state.surveillance, extractMgr, rec)
finished := state.surveillance.Finished finished := state.surveillance.Finished
thresholds := state.surveillance.Thresholds thresholds := state.surveillance.Thresholds
noiseFloor := state.surveillance.NoiseFloor noiseFloor := state.surveillance.NoiseFloor
var displaySignals []detector.Signal var displaySignals []detector.Signal
if len(art.iq) > 0 {
state.refinement = rt.refineSignals(art, state.refinementInput, extractMgr, rec)
displaySignals = state.refinement.Signals
if len(art.detailIQ) > 0 {
displaySignals = state.refinement.Result.Signals
if rec != nil && len(displaySignals) > 0 && len(art.allIQ) > 0 { if rec != nil && len(displaySignals) > 0 && len(art.allIQ) > 0 {
aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult} aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult}
streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, displaySignals, rt.streamPhaseState, rt.streamOverlap, aqCfg) streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, displaySignals, rt.streamPhaseState, rt.streamOverlap, aqCfg)
@@ -89,7 +88,6 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
} }
rt.maintenance(displaySignals, rec) rt.maintenance(displaySignals, rec)
} else { } else {
state.refinement = pipeline.RefinementResult{}
displaySignals = rt.det.StableSignals() displaySignals = rt.det.StableSignals()
} }
state.queueStats = rt.queueStats state.queueStats = rt.queueStats
@@ -119,8 +117,8 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
rec.OnEvents(evCopy) rec.OnEvents(evCopy)
} }
var debugInfo *SpectrumDebug var debugInfo *SpectrumDebug
plan := state.refinementInput.Plan
windowStats := buildWindowStats(state.refinementInput.Windows)
plan := state.refinement.Input.Plan
windowStats := buildWindowStats(state.refinement.Input.Windows)
hasPlan := plan.TotalCandidates > 0 || plan.Budget > 0 || plan.DroppedBySNR > 0 || plan.DroppedByBudget > 0 hasPlan := plan.TotalCandidates > 0 || plan.Budget > 0 || plan.DroppedBySNR > 0 || plan.DroppedByBudget > 0
hasWindows := windowStats != nil && windowStats.Count > 0 hasWindows := windowStats != nil && windowStats.Count > 0
if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan || hasWindows { if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan || hasWindows {
@@ -150,7 +148,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
debugInfo.Windows = windowStats debugInfo.Windows = windowStats
} }
} }
h.broadcast(SpectrumFrame{Timestamp: art.now.UnixMilli(), CenterHz: rt.cfg.CenterHz, SampleHz: rt.cfg.SampleRate, FFTSize: rt.cfg.FFTSize, Spectrum: art.spectrum, Signals: displaySignals, Debug: debugInfo})
h.broadcast(SpectrumFrame{Timestamp: art.now.UnixMilli(), CenterHz: rt.cfg.CenterHz, SampleHz: rt.cfg.SampleRate, FFTSize: rt.cfg.FFTSize, Spectrum: art.surveillanceSpectrum, Signals: displaySignals, Debug: debugInfo})
} }
} }
} }

+ 10
- 10
cmd/sdrd/http_handlers.go Datei anzeigen

@@ -154,22 +154,22 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime
mux.HandleFunc("/api/refinement", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/api/refinement", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
snap := phaseSnap.Snapshot() snap := phaseSnap.Snapshot()
windowStats := buildWindowStats(snap.refinementInput.Windows)
windowStats := buildWindowStats(snap.refinement.Input.Windows)
out := map[string]any{ out := map[string]any{
"plan": snap.refinementInput.Plan,
"windows": snap.refinementInput.Windows,
"plan": snap.refinement.Input.Plan,
"windows": snap.refinement.Input.Windows,
"window_stats": windowStats, "window_stats": windowStats,
"queue_stats": snap.queueStats, "queue_stats": snap.queueStats,
"candidates": len(snap.refinementInput.Candidates),
"scheduled": len(snap.refinementInput.Scheduled),
"signals": len(snap.refinement.Signals),
"decisions": len(snap.refinement.Decisions),
"decision_summary": summarizeDecisions(snap.refinement.Decisions),
"decision_items": compactDecisions(snap.refinement.Decisions),
"candidates": len(snap.refinement.Input.Candidates),
"scheduled": len(snap.refinement.Input.Scheduled),
"signals": len(snap.refinement.Result.Signals),
"decisions": len(snap.refinement.Result.Decisions),
"decision_summary": summarizeDecisions(snap.refinement.Result.Decisions),
"decision_items": compactDecisions(snap.refinement.Result.Decisions),
"surveillance_level": snap.surveillance.Level, "surveillance_level": snap.surveillance.Level,
"surveillance_levels": snap.surveillance.Levels, "surveillance_levels": snap.surveillance.Levels,
"display_level": snap.surveillance.DisplayLevel, "display_level": snap.surveillance.DisplayLevel,
"refinement_level": snap.refinementInput.Level,
"refinement_level": snap.refinement.Input.Level,
"presentation_level": snap.presentation, "presentation_level": snap.presentation,
} }
_ = json.NewEncoder(w).Encode(out) _ = json.NewEncoder(w).Encode(out)


+ 3
- 3
cmd/sdrd/phase_snapshot_test.go Datei anzeigen

@@ -6,13 +6,13 @@ func TestPhaseSnapshotSetGet(t *testing.T) {
snap := &phaseSnapshot{} snap := &phaseSnapshot{}
state := phaseState{} state := phaseState{}
state.surveillance.NoiseFloor = -91 state.surveillance.NoiseFloor = -91
state.refinementInput.SampleRate = 2048000
state.refinement.Input.SampleRate = 2048000
snap.Set(state) snap.Set(state)
got := snap.Snapshot() got := snap.Snapshot()
if got.surveillance.NoiseFloor != -91 { if got.surveillance.NoiseFloor != -91 {
t.Fatalf("unexpected noise floor: %v", got.surveillance.NoiseFloor) t.Fatalf("unexpected noise floor: %v", got.surveillance.NoiseFloor)
} }
if got.refinementInput.SampleRate != 2048000 {
t.Fatalf("unexpected sample rate: %v", got.refinementInput.SampleRate)
if got.refinement.Input.SampleRate != 2048000 {
t.Fatalf("unexpected sample rate: %v", got.refinement.Input.SampleRate)
} }
} }

+ 4
- 5
cmd/sdrd/phase_state.go Datei anzeigen

@@ -3,9 +3,8 @@ package main
import "sdr-wideband-suite/internal/pipeline" import "sdr-wideband-suite/internal/pipeline"


type phaseState struct { type phaseState struct {
surveillance pipeline.SurveillanceResult
refinementInput pipeline.RefinementInput
refinement pipeline.RefinementResult
queueStats decisionQueueStats
presentation pipeline.AnalysisLevel
surveillance pipeline.SurveillanceResult
refinement pipeline.RefinementStep
queueStats decisionQueueStats
presentation pipeline.AnalysisLevel
} }

+ 11
- 9
cmd/sdrd/phase_state_test.go Datei anzeigen

@@ -8,19 +8,21 @@ import (


func TestPhaseStateCarriesPhaseResults(t *testing.T) { func TestPhaseStateCarriesPhaseResults(t *testing.T) {
ps := &phaseState{ ps := &phaseState{
surveillance: pipeline.SurveillanceResult{Level: pipeline.AnalysisLevel{Name: "surveillance"}, NoiseFloor: -90, Scheduled: []pipeline.ScheduledCandidate{{Candidate: pipeline.Candidate{ID: 1}, Priority: 5}}},
refinementInput: pipeline.RefinementInput{Scheduled: []pipeline.ScheduledCandidate{{Candidate: pipeline.Candidate{ID: 1}, Priority: 5}}, SampleRate: 2048000, FFTSize: 2048, CenterHz: 7.1e6},
refinement: pipeline.RefinementResult{Level: pipeline.AnalysisLevel{Name: "refinement"}, Decisions: []pipeline.SignalDecision{{ShouldRecord: true}}, Candidates: []pipeline.Candidate{{ID: 1}}},
queueStats: decisionQueueStats{RecordQueued: 1},
presentation: pipeline.AnalysisLevel{Name: "presentation"},
surveillance: pipeline.SurveillanceResult{Level: pipeline.AnalysisLevel{Name: "surveillance"}, NoiseFloor: -90, Scheduled: []pipeline.ScheduledCandidate{{Candidate: pipeline.Candidate{ID: 1}, Priority: 5}}},
refinement: pipeline.RefinementStep{
Input: pipeline.RefinementInput{Scheduled: []pipeline.ScheduledCandidate{{Candidate: pipeline.Candidate{ID: 1}, Priority: 5}}, SampleRate: 2048000, FFTSize: 2048, CenterHz: 7.1e6},
Result: pipeline.RefinementResult{Level: pipeline.AnalysisLevel{Name: "refinement"}, Decisions: []pipeline.SignalDecision{{ShouldRecord: true}}, Candidates: []pipeline.Candidate{{ID: 1}}},
},
queueStats: decisionQueueStats{RecordQueued: 1},
presentation: pipeline.AnalysisLevel{Name: "presentation"},
} }
if ps.surveillance.NoiseFloor != -90 || len(ps.surveillance.Scheduled) != 1 { if ps.surveillance.NoiseFloor != -90 || len(ps.surveillance.Scheduled) != 1 {
t.Fatalf("unexpected surveillance state: %+v", ps.surveillance) t.Fatalf("unexpected surveillance state: %+v", ps.surveillance)
} }
if len(ps.refinementInput.Scheduled) != 1 || ps.refinementInput.SampleRate != 2048000 {
t.Fatalf("unexpected refinement input: %+v", ps.refinementInput)
if len(ps.refinement.Input.Scheduled) != 1 || ps.refinement.Input.SampleRate != 2048000 {
t.Fatalf("unexpected refinement input: %+v", ps.refinement.Input)
} }
if len(ps.refinement.Decisions) != 1 || !ps.refinement.Decisions[0].ShouldRecord || len(ps.refinement.Candidates) != 1 {
t.Fatalf("unexpected refinement state: %+v", ps.refinement)
if len(ps.refinement.Result.Decisions) != 1 || !ps.refinement.Result.Decisions[0].ShouldRecord || len(ps.refinement.Result.Candidates) != 1 {
t.Fatalf("unexpected refinement state: %+v", ps.refinement.Result)
} }
} }

+ 71
- 60
cmd/sdrd/pipeline_runtime.go Datei anzeigen

@@ -45,14 +45,16 @@ type dspRuntime struct {
} }


type spectrumArtifacts struct { type spectrumArtifacts struct {
allIQ []complex64
iq []complex64
spectrum []float64
finished []detector.Event
detected []detector.Signal
thresholds []float64
noiseFloor float64
now time.Time
allIQ []complex64
surveillanceIQ []complex64
detailIQ []complex64
surveillanceSpectrum []float64
detailSpectrum []float64
finished []detector.Event
detected []detector.Signal
thresholds []float64
noiseFloor float64
now time.Time
} }


func newDSPRuntime(cfg config.Config, det *detector.Detector, window []float64, gpuState *gpuStatus) *dspRuntime { func newDSPRuntime(cfg config.Config, det *detector.Detector, window []float64, gpuState *gpuStatus) *dspRuntime {
@@ -160,27 +162,27 @@ func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manag
if rec != nil { if rec != nil {
rec.Ingest(time.Now(), allIQ) rec.Ingest(time.Now(), allIQ)
} }
iq := allIQ
survIQ := allIQ
if len(allIQ) > rt.cfg.FFTSize { if len(allIQ) > rt.cfg.FFTSize {
iq = allIQ[len(allIQ)-rt.cfg.FFTSize:]
survIQ = allIQ[len(allIQ)-rt.cfg.FFTSize:]
} }
if rt.dcEnabled { if rt.dcEnabled {
dcBlocker.Apply(iq)
dcBlocker.Apply(survIQ)
} }
if rt.iqEnabled { if rt.iqEnabled {
dsp.IQBalance(iq)
dsp.IQBalance(survIQ)
} }
var spectrum []float64 var spectrum []float64
if rt.useGPU && rt.gpuEngine != nil { if rt.useGPU && rt.gpuEngine != nil {
gpuBuf := make([]complex64, len(iq))
if len(rt.window) == len(iq) {
for i := 0; i < len(iq); i++ {
v := iq[i]
gpuBuf := make([]complex64, len(survIQ))
if len(rt.window) == len(survIQ) {
for i := 0; i < len(survIQ); i++ {
v := survIQ[i]
w := float32(rt.window[i]) w := float32(rt.window[i])
gpuBuf[i] = complex(real(v)*w, imag(v)*w) gpuBuf[i] = complex(real(v)*w, imag(v)*w)
} }
} else { } else {
copy(gpuBuf, iq)
copy(gpuBuf, survIQ)
} }
out, err := rt.gpuEngine.Exec(gpuBuf) out, err := rt.gpuEngine.Exec(gpuBuf)
if err != nil { if err != nil {
@@ -193,7 +195,7 @@ func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manag
spectrum = fftutil.SpectrumFromFFT(out) spectrum = fftutil.SpectrumFromFFT(out)
} }
} else { } else {
spectrum = fftutil.SpectrumWithPlan(iq, rt.window, rt.plan)
spectrum = fftutil.SpectrumWithPlan(survIQ, rt.window, rt.plan)
} }
for i := range spectrum { for i := range spectrum {
if math.IsNaN(spectrum[i]) || math.IsInf(spectrum[i], 0) { if math.IsNaN(spectrum[i]) || math.IsInf(spectrum[i], 0) {
@@ -203,14 +205,16 @@ func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manag
now := time.Now() now := time.Now()
finished, detected := rt.det.Process(now, spectrum, rt.cfg.CenterHz) finished, detected := rt.det.Process(now, spectrum, rt.cfg.CenterHz)
return &spectrumArtifacts{ return &spectrumArtifacts{
allIQ: allIQ,
iq: iq,
spectrum: spectrum,
finished: finished,
detected: detected,
thresholds: rt.det.LastThresholds(),
noiseFloor: rt.det.LastNoiseFloor(),
now: now,
allIQ: allIQ,
surveillanceIQ: survIQ,
detailIQ: survIQ,
surveillanceSpectrum: spectrum,
detailSpectrum: spectrum,
finished: finished,
detected: detected,
thresholds: rt.det.LastThresholds(),
noiseFloor: rt.det.LastNoiseFloor(),
now: now,
}, nil }, nil
} }


@@ -226,7 +230,7 @@ func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.S
SampleRate: rt.cfg.SampleRate, SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.Surveillance.AnalysisFFTSize, FFTSize: rt.cfg.Surveillance.AnalysisFFTSize,
CenterHz: rt.cfg.CenterHz, CenterHz: rt.cfg.CenterHz,
SpanHz: float64(rt.cfg.SampleRate),
SpanHz: spanForPolicy(policy, float64(rt.cfg.SampleRate)),
Source: "baseband", Source: "baseband",
} }
lowRate := rt.cfg.SampleRate / 2 lowRate := rt.cfg.SampleRate / 2
@@ -242,7 +246,7 @@ func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.S
SampleRate: lowRate, SampleRate: lowRate,
FFTSize: lowFFT, FFTSize: lowFFT,
CenterHz: rt.cfg.CenterHz, CenterHz: rt.cfg.CenterHz,
SpanHz: float64(lowRate),
SpanHz: spanForPolicy(policy, float64(lowRate)),
Source: "downsampled", Source: "downsampled",
} }
displayLevel := pipeline.AnalysisLevel{ displayLevel := pipeline.AnalysisLevel{
@@ -250,13 +254,10 @@ func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.S
SampleRate: rt.cfg.SampleRate, SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.Surveillance.DisplayBins, FFTSize: rt.cfg.Surveillance.DisplayBins,
CenterHz: rt.cfg.CenterHz, CenterHz: rt.cfg.CenterHz,
SpanHz: float64(rt.cfg.SampleRate),
SpanHz: spanForPolicy(policy, float64(rt.cfg.SampleRate)),
Source: "display", Source: "display",
} }
levels := []pipeline.AnalysisLevel{level}
if lowLevel.SampleRate != level.SampleRate || lowLevel.FFTSize != level.FFTSize {
levels = append(levels, lowLevel)
}
levels := surveillanceLevels(policy, level, lowLevel)
return pipeline.SurveillanceResult{ return pipeline.SurveillanceResult{
Level: level, Level: level,
Levels: levels, Levels: levels,
@@ -279,36 +280,14 @@ func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult) pip
} }
windows := make([]pipeline.RefinementWindow, 0, len(scheduled)) windows := make([]pipeline.RefinementWindow, 0, len(scheduled))
for _, sc := range scheduled { for _, sc := range scheduled {
span := sc.Candidate.BandwidthHz
windowSource := "candidate"
if policy.RefinementAutoSpan && (span <= 0 || span < 2000 || span > 400000) {
autoSpan, autoSource := pipeline.AutoSpanForHint(sc.Candidate.Hint)
if autoSpan > 0 {
span = autoSpan
windowSource = autoSource
}
}
if policy.RefinementMinSpanHz > 0 && span < policy.RefinementMinSpanHz {
span = policy.RefinementMinSpanHz
}
if policy.RefinementMaxSpanHz > 0 && span > policy.RefinementMaxSpanHz {
span = policy.RefinementMaxSpanHz
}
if span <= 0 {
span = 12000
}
windows = append(windows, pipeline.RefinementWindow{
CenterHz: sc.Candidate.CenterHz,
SpanHz: span,
Source: windowSource,
})
windows = append(windows, pipeline.RefinementWindowForCandidate(policy, sc.Candidate))
} }
level := pipeline.AnalysisLevel{ level := pipeline.AnalysisLevel{
Name: "refinement", Name: "refinement",
SampleRate: rt.cfg.SampleRate, SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.FFTSize, FFTSize: rt.cfg.FFTSize,
CenterHz: rt.cfg.CenterHz, CenterHz: rt.cfg.CenterHz,
SpanHz: float64(rt.cfg.SampleRate),
SpanHz: spanForPolicy(policy, float64(rt.cfg.SampleRate)),
Source: "refinement-window", Source: "refinement-window",
} }
input := pipeline.RefinementInput{ input := pipeline.RefinementInput{
@@ -328,8 +307,14 @@ func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult) pip
return input return input
} }


func (rt *dspRuntime) runRefinement(art *spectrumArtifacts, surv pipeline.SurveillanceResult, extractMgr *extractionManager, rec *recorder.Manager) pipeline.RefinementStep {
input := rt.buildRefinementInput(surv)
result := rt.refineSignals(art, input, extractMgr, rec)
return pipeline.RefinementStep{Input: input, Result: result}
}

func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.RefinementInput, extractMgr *extractionManager, rec *recorder.Manager) pipeline.RefinementResult { func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.RefinementInput, extractMgr *extractionManager, rec *recorder.Manager) pipeline.RefinementResult {
if art == nil || len(art.iq) == 0 || len(input.Scheduled) == 0 {
if art == nil || len(art.detailIQ) == 0 || len(input.Scheduled) == 0 {
return pipeline.RefinementResult{} return pipeline.RefinementResult{}
} }
policy := pipeline.PolicyFromConfig(rt.cfg) policy := pipeline.PolicyFromConfig(rt.cfg)
@@ -360,8 +345,8 @@ func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.Refin
if centerHz == 0 { if centerHz == 0 {
centerHz = rt.cfg.CenterHz centerHz = rt.cfg.CenterHz
} }
snips, snipRates := extractSignalIQBatch(extractMgr, art.iq, sampleRate, centerHz, selectedSignals)
refined := pipeline.RefineCandidates(selectedCandidates, input.Windows, art.spectrum, sampleRate, fftSize, snips, snipRates, classifier.ClassifierMode(rt.cfg.ClassifierMode))
snips, snipRates := extractSignalIQBatch(extractMgr, art.detailIQ, sampleRate, centerHz, selectedSignals)
refined := pipeline.RefineCandidates(selectedCandidates, input.Windows, art.detailSpectrum, 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 {
@@ -498,3 +483,29 @@ func (rt *dspRuntime) maintenance(displaySignals []detector.Signal, rec *recorde
_ = aqCfg _ = aqCfg
} }
} }

func spanForPolicy(policy pipeline.Policy, fallback float64) float64 {
if policy.MonitorSpanHz > 0 {
return policy.MonitorSpanHz
}
if policy.MonitorStartHz != 0 && policy.MonitorEndHz != 0 && policy.MonitorEndHz > policy.MonitorStartHz {
return policy.MonitorEndHz - policy.MonitorStartHz
}
return fallback
}

func surveillanceLevels(policy pipeline.Policy, primary pipeline.AnalysisLevel, secondary pipeline.AnalysisLevel) []pipeline.AnalysisLevel {
levels := []pipeline.AnalysisLevel{primary}
strategy := strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy))
switch strategy {
case "", "single-resolution":
if secondary.SampleRate != primary.SampleRate || secondary.FFTSize != primary.FFTSize {
levels = append(levels, secondary)
}
default:
if secondary.SampleRate != primary.SampleRate || secondary.FFTSize != primary.FFTSize {
levels = append(levels, secondary)
}
}
return levels
}

Laden…
Abbrechen
Speichern