Quellcode durchsuchen

Add operational surveillance levels with decimated spectra

master
Jan Svabenik vor 13 Stunden
Ursprung
Commit
d40b534d57
4 geänderte Dateien mit 244 neuen und 109 gelöschten Zeilen
  1. +1
    -11
      cmd/sdrd/dsp_loop.go
  2. +212
    -79
      cmd/sdrd/pipeline_runtime.go
  3. +13
    -9
      cmd/sdrd/pipeline_runtime_test.go
  4. +18
    -10
      internal/pipeline/phases.go

+ 1
- 11
cmd/sdrd/dsp_loop.go Datei anzeigen

@@ -13,7 +13,6 @@ import (
"sdr-wideband-suite/internal/config" "sdr-wideband-suite/internal/config"
"sdr-wideband-suite/internal/detector" "sdr-wideband-suite/internal/detector"
"sdr-wideband-suite/internal/dsp" "sdr-wideband-suite/internal/dsp"
"sdr-wideband-suite/internal/pipeline"
"sdr-wideband-suite/internal/recorder" "sdr-wideband-suite/internal/recorder"
) )


@@ -91,16 +90,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
displaySignals = rt.det.StableSignals() displaySignals = rt.det.StableSignals()
} }
state.arbitration = rt.arbitration state.arbitration = rt.arbitration
state.presentation = pipeline.AnalysisLevel{
Name: "presentation",
Role: "presentation",
Truth: "presentation",
SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.Surveillance.DisplayBins,
CenterHz: rt.cfg.CenterHz,
SpanHz: float64(rt.cfg.SampleRate),
Source: "display",
}
state.presentation = state.surveillance.DisplayLevel
if phaseSnap != nil { if phaseSnap != nil {
phaseSnap.Set(*state) phaseSnap.Set(*state)
} }


+ 212
- 79
cmd/sdrd/pipeline_runtime.go Datei anzeigen

@@ -35,6 +35,9 @@ type dspRuntime struct {
detailWindow []float64 detailWindow []float64
detailPlan *fftutil.CmplxPlan detailPlan *fftutil.CmplxPlan
detailFFT int detailFFT int
survWindows map[int][]float64
survPlans map[int]*fftutil.CmplxPlan
survFIR map[int][]float64
dcEnabled bool dcEnabled bool
iqEnabled bool iqEnabled bool
useGPU bool useGPU bool
@@ -52,6 +55,8 @@ type spectrumArtifacts struct {
surveillanceIQ []complex64 surveillanceIQ []complex64
detailIQ []complex64 detailIQ []complex64
surveillanceSpectrum []float64 surveillanceSpectrum []float64
surveillanceSpectra []pipeline.SurveillanceLevelSpectrum
surveillancePlan surveillancePlan
detailSpectrum []float64 detailSpectrum []float64
finished []detector.Event finished []detector.Event
detected []detector.Signal detected []detector.Signal
@@ -60,6 +65,20 @@ type spectrumArtifacts struct {
now time.Time now time.Time
} }


type surveillanceLevelSpec struct {
Level pipeline.AnalysisLevel
Decim int
AllowGPU bool
}

type surveillancePlan struct {
Primary pipeline.AnalysisLevel
Levels []pipeline.AnalysisLevel
Presentation pipeline.AnalysisLevel
Context pipeline.AnalysisContext
Specs []surveillanceLevelSpec
}

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 {
detailFFT := cfg.Refinement.DetailFFTSize detailFFT := cfg.Refinement.DetailFFTSize
if detailFFT <= 0 { if detailFFT <= 0 {
@@ -73,6 +92,9 @@ func newDSPRuntime(cfg config.Config, det *detector.Detector, window []float64,
detailWindow: fftutil.Hann(detailFFT), detailWindow: fftutil.Hann(detailFFT),
detailPlan: fftutil.NewCmplxPlan(detailFFT), detailPlan: fftutil.NewCmplxPlan(detailFFT),
detailFFT: detailFFT, detailFFT: detailFFT,
survWindows: map[int][]float64{},
survPlans: map[int]*fftutil.CmplxPlan{},
survFIR: map[int][]float64{},
dcEnabled: cfg.DCBlock, dcEnabled: cfg.DCBlock,
iqEnabled: cfg.IQBalance, iqEnabled: cfg.IQBalance,
useGPU: cfg.UseGPUFFT, useGPU: cfg.UseGPUFFT,
@@ -98,6 +120,7 @@ func newDSPRuntime(cfg config.Config, det *detector.Detector, window []float64,


func (rt *dspRuntime) applyUpdate(upd dspUpdate, srcMgr *sourceManager, rec *recorder.Manager, gpuState *gpuStatus) { func (rt *dspRuntime) applyUpdate(upd dspUpdate, srcMgr *sourceManager, rec *recorder.Manager, gpuState *gpuStatus) {
prevFFT := rt.cfg.FFTSize prevFFT := rt.cfg.FFTSize
prevSampleRate := rt.cfg.SampleRate
prevUseGPU := rt.useGPU prevUseGPU := rt.useGPU
prevDetailFFT := rt.detailFFT prevDetailFFT := rt.detailFFT
rt.cfg = upd.cfg rt.cfg = upd.cfg
@@ -137,6 +160,13 @@ func (rt *dspRuntime) applyUpdate(upd dspUpdate, srcMgr *sourceManager, rec *rec
rt.detailWindow = fftutil.Hann(detailFFT) rt.detailWindow = fftutil.Hann(detailFFT)
rt.detailPlan = fftutil.NewCmplxPlan(detailFFT) rt.detailPlan = fftutil.NewCmplxPlan(detailFFT)
} }
if prevSampleRate != rt.cfg.SampleRate {
rt.survFIR = map[int][]float64{}
}
if prevFFT != rt.cfg.FFTSize {
rt.survWindows = map[int][]float64{}
rt.survPlans = map[int]*fftutil.CmplxPlan{}
}
rt.dcEnabled = upd.dcBlock rt.dcEnabled = upd.dcBlock
rt.iqEnabled = upd.iqBalance rt.iqEnabled = upd.iqBalance
if rt.cfg.FFTSize != prevFFT || rt.cfg.UseGPUFFT != prevUseGPU { if rt.cfg.FFTSize != prevFFT || rt.cfg.UseGPUFFT != prevUseGPU {
@@ -199,6 +229,90 @@ func (rt *dspRuntime) spectrumFromIQWithPlan(iq []complex64, window []float64, p
return fftutil.SpectrumWithPlan(iq, window, plan) return fftutil.SpectrumWithPlan(iq, window, plan)
} }


func (rt *dspRuntime) windowForFFT(fftSize int) []float64 {
if fftSize <= 0 {
return nil
}
if fftSize == rt.cfg.FFTSize {
return rt.window
}
if rt.survWindows == nil {
rt.survWindows = map[int][]float64{}
}
if window, ok := rt.survWindows[fftSize]; ok {
return window
}
window := fftutil.Hann(fftSize)
rt.survWindows[fftSize] = window
return window
}

func (rt *dspRuntime) planForFFT(fftSize int) *fftutil.CmplxPlan {
if fftSize <= 0 {
return nil
}
if fftSize == rt.cfg.FFTSize {
return rt.plan
}
if rt.survPlans == nil {
rt.survPlans = map[int]*fftutil.CmplxPlan{}
}
if plan, ok := rt.survPlans[fftSize]; ok {
return plan
}
plan := fftutil.NewCmplxPlan(fftSize)
rt.survPlans[fftSize] = plan
return plan
}

func (rt *dspRuntime) spectrumForLevel(iq []complex64, fftSize int, gpuState *gpuStatus, allowGPU bool) []float64 {
if len(iq) == 0 || fftSize <= 0 {
return nil
}
if len(iq) > fftSize {
iq = iq[len(iq)-fftSize:]
}
window := rt.windowForFFT(fftSize)
plan := rt.planForFFT(fftSize)
return rt.spectrumFromIQWithPlan(iq, window, plan, gpuState, allowGPU)
}

func sanitizeSpectrum(spectrum []float64) {
for i := range spectrum {
if math.IsNaN(spectrum[i]) || math.IsInf(spectrum[i], 0) {
spectrum[i] = -200
}
}
}

func (rt *dspRuntime) decimationTaps(factor int) []float64 {
if factor <= 1 {
return nil
}
if rt.survFIR == nil {
rt.survFIR = map[int][]float64{}
}
if taps, ok := rt.survFIR[factor]; ok {
return taps
}
cutoff := float64(rt.cfg.SampleRate/factor) * 0.5 * 0.8
taps := dsp.LowpassFIR(cutoff, rt.cfg.SampleRate, 101)
rt.survFIR[factor] = taps
return taps
}

func (rt *dspRuntime) decimateSurveillanceIQ(iq []complex64, factor int) []complex64 {
if factor <= 1 {
return iq
}
taps := rt.decimationTaps(factor)
if len(taps) == 0 {
return dsp.Decimate(iq, factor)
}
filtered := dsp.ApplyFIR(iq, taps)
return dsp.Decimate(filtered, factor)
}

func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manager, dcBlocker *dsp.DCBlocker, gpuState *gpuStatus) (*spectrumArtifacts, error) { func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manager, dcBlocker *dsp.DCBlocker, gpuState *gpuStatus) (*spectrumArtifacts, error) {
required := rt.cfg.FFTSize required := rt.cfg.FFTSize
if rt.detailFFT > required { if rt.detailFFT > required {
@@ -238,19 +352,44 @@ func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manag
} }
} }
survSpectrum := rt.spectrumFromIQ(survIQ, gpuState) survSpectrum := rt.spectrumFromIQ(survIQ, gpuState)
for i := range survSpectrum {
if math.IsNaN(survSpectrum[i]) || math.IsInf(survSpectrum[i], 0) {
survSpectrum[i] = -200
}
}
sanitizeSpectrum(survSpectrum)
detailSpectrum := survSpectrum detailSpectrum := survSpectrum
if !sameIQBuffer(detailIQ, survIQ) { if !sameIQBuffer(detailIQ, survIQ) {
detailSpectrum = rt.spectrumFromIQWithPlan(detailIQ, rt.detailWindow, rt.detailPlan, gpuState, false) detailSpectrum = rt.spectrumFromIQWithPlan(detailIQ, rt.detailWindow, rt.detailPlan, gpuState, false)
for i := range detailSpectrum {
if math.IsNaN(detailSpectrum[i]) || math.IsInf(detailSpectrum[i], 0) {
detailSpectrum[i] = -200
sanitizeSpectrum(detailSpectrum)
}
policy := pipeline.PolicyFromConfig(rt.cfg)
plan := rt.buildSurveillancePlan(policy)
surveillanceSpectra := make([]pipeline.SurveillanceLevelSpectrum, 0, len(plan.Specs))
for _, spec := range plan.Specs {
if spec.Level.FFTSize <= 0 {
continue
}
var spectrum []float64
if spec.Decim <= 1 {
if spec.Level.FFTSize == len(survSpectrum) {
spectrum = survSpectrum
} else {
spectrum = rt.spectrumForLevel(survIQ, spec.Level.FFTSize, gpuState, spec.AllowGPU)
sanitizeSpectrum(spectrum)
} }
} else {
required := spec.Level.FFTSize * spec.Decim
if required > len(survIQ) {
continue
}
src := survIQ
if len(src) > required {
src = src[len(src)-required:]
}
decimated := rt.decimateSurveillanceIQ(src, spec.Decim)
spectrum = rt.spectrumForLevel(decimated, spec.Level.FFTSize, gpuState, false)
sanitizeSpectrum(spectrum)
} }
if len(spectrum) == 0 {
continue
}
surveillanceSpectra = append(surveillanceSpectra, pipeline.SurveillanceLevelSpectrum{Level: spec.Level, Spectrum: spectrum})
} }
now := time.Now() now := time.Now()
finished, detected := rt.det.Process(now, survSpectrum, rt.cfg.CenterHz) finished, detected := rt.det.Process(now, survSpectrum, rt.cfg.CenterHz)
@@ -259,6 +398,8 @@ func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manag
surveillanceIQ: survIQ, surveillanceIQ: survIQ,
detailIQ: detailIQ, detailIQ: detailIQ,
surveillanceSpectrum: survSpectrum, surveillanceSpectrum: survSpectrum,
surveillanceSpectra: surveillanceSpectra,
surveillancePlan: plan,
detailSpectrum: detailSpectrum, detailSpectrum: detailSpectrum,
finished: finished, finished: finished,
detected: detected, detected: detected,
@@ -275,50 +416,16 @@ func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.S
policy := pipeline.PolicyFromConfig(rt.cfg) policy := pipeline.PolicyFromConfig(rt.cfg)
candidates := pipeline.CandidatesFromSignals(art.detected, "surveillance-detector") candidates := pipeline.CandidatesFromSignals(art.detected, "surveillance-detector")
scheduled := pipeline.ScheduleCandidates(candidates, policy) scheduled := pipeline.ScheduleCandidates(candidates, policy)
level := pipeline.AnalysisLevel{
Name: "surveillance",
Role: "surveillance",
Truth: "surveillance",
SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.Surveillance.AnalysisFFTSize,
CenterHz: rt.cfg.CenterHz,
SpanHz: spanForPolicy(policy, float64(rt.cfg.SampleRate)),
Source: "baseband",
}
lowRate := rt.cfg.SampleRate / 2
lowFFT := rt.cfg.Surveillance.AnalysisFFTSize / 2
if lowRate < 200000 {
lowRate = rt.cfg.SampleRate
}
if lowFFT < 256 {
lowFFT = rt.cfg.Surveillance.AnalysisFFTSize
}
lowLevel := pipeline.AnalysisLevel{
Name: "surveillance-lowres",
Role: "surveillance-lowres",
Truth: "surveillance",
SampleRate: lowRate,
FFTSize: lowFFT,
CenterHz: rt.cfg.CenterHz,
SpanHz: spanForPolicy(policy, float64(lowRate)),
Source: "downsampled",
plan := art.surveillancePlan
if plan.Primary.Name == "" {
plan = rt.buildSurveillancePlan(policy)
} }
displayLevel := pipeline.AnalysisLevel{
Name: "presentation",
Role: "presentation",
Truth: "presentation",
SampleRate: rt.cfg.SampleRate,
FFTSize: rt.cfg.Surveillance.DisplayBins,
CenterHz: rt.cfg.CenterHz,
SpanHz: spanForPolicy(policy, float64(rt.cfg.SampleRate)),
Source: "display",
}
levels, context := surveillanceLevels(policy, level, lowLevel, displayLevel)
return pipeline.SurveillanceResult{ return pipeline.SurveillanceResult{
Level: level,
Levels: levels,
DisplayLevel: displayLevel,
Context: context,
Level: plan.Primary,
Levels: plan.Levels,
DisplayLevel: plan.Presentation,
Context: plan.Context,
Spectra: art.surveillanceSpectra,
Candidates: candidates, Candidates: candidates,
Scheduled: scheduled, Scheduled: scheduled,
Finished: art.finished, Finished: art.finished,
@@ -361,26 +468,8 @@ func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult, now
if _, maxSpan, ok := windowSpanBounds(windows); ok { if _, maxSpan, ok := windowSpanBounds(windows); ok {
levelSpan = maxSpan levelSpan = maxSpan
} }
level := pipeline.AnalysisLevel{
Name: "refinement",
Role: "refinement",
Truth: "refinement",
SampleRate: rt.cfg.SampleRate,
FFTSize: detailFFT,
CenterHz: rt.cfg.CenterHz,
SpanHz: levelSpan,
Source: "refinement-window",
}
detailLevel := pipeline.AnalysisLevel{
Name: "detail",
Role: "detail",
Truth: "refinement",
SampleRate: rt.cfg.SampleRate,
FFTSize: detailFFT,
CenterHz: rt.cfg.CenterHz,
SpanHz: levelSpan,
Source: "detail-spectrum",
}
level := analysisLevel("refinement", "refinement", "refinement", rt.cfg.SampleRate, detailFFT, rt.cfg.CenterHz, levelSpan, "refinement-window", 1, rt.cfg.SampleRate)
detailLevel := analysisLevel("detail", "detail", "refinement", rt.cfg.SampleRate, detailFFT, rt.cfg.CenterHz, levelSpan, "detail-spectrum", 1, rt.cfg.SampleRate)
if len(workItems) > 0 { if len(workItems) > 0 {
for i := range workItems { for i := range workItems {
item := &workItems[i] item := &workItems[i]
@@ -644,21 +733,65 @@ func windowSpanBounds(windows []pipeline.RefinementWindow) (float64, float64, bo
return minSpan, maxSpan, ok return minSpan, maxSpan, ok
} }


func surveillanceLevels(policy pipeline.Policy, primary pipeline.AnalysisLevel, secondary pipeline.AnalysisLevel, presentation pipeline.AnalysisLevel) ([]pipeline.AnalysisLevel, pipeline.AnalysisContext) {
func analysisLevel(name, role, truth string, sampleRate int, fftSize int, centerHz float64, spanHz float64, source string, decimation int, baseRate int) pipeline.AnalysisLevel {
level := pipeline.AnalysisLevel{
Name: name,
Role: role,
Truth: truth,
SampleRate: sampleRate,
FFTSize: fftSize,
CenterHz: centerHz,
SpanHz: spanHz,
Source: source,
}
if level.SampleRate > 0 && level.FFTSize > 0 {
level.BinHz = float64(level.SampleRate) / float64(level.FFTSize)
}
if decimation > 0 {
level.Decimation = decimation
} else if baseRate > 0 && level.SampleRate > 0 && baseRate%level.SampleRate == 0 {
level.Decimation = baseRate / level.SampleRate
}
return level
}

func (rt *dspRuntime) buildSurveillancePlan(policy pipeline.Policy) surveillancePlan {
baseRate := rt.cfg.SampleRate
baseFFT := rt.cfg.Surveillance.AnalysisFFTSize
if baseFFT <= 0 {
baseFFT = rt.cfg.FFTSize
}
span := spanForPolicy(policy, float64(baseRate))
primary := analysisLevel("surveillance", "surveillance", "surveillance", baseRate, baseFFT, rt.cfg.CenterHz, span, "baseband", 1, baseRate)
levels := []pipeline.AnalysisLevel{primary} levels := []pipeline.AnalysisLevel{primary}
context := pipeline.AnalysisContext{
Surveillance: primary,
Presentation: presentation,
}
specs := []surveillanceLevelSpec{{Level: primary, Decim: 1, AllowGPU: true}}
context := pipeline.AnalysisContext{Surveillance: primary}

strategy := strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)) strategy := strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy))
switch strategy { switch strategy {
case "multi-res", "multi-resolution", "multi", "multi_res": case "multi-res", "multi-resolution", "multi", "multi_res":
if secondary.SampleRate != primary.SampleRate || secondary.FFTSize != primary.FFTSize {
levels = append(levels, secondary)
context.Derived = append(context.Derived, secondary)
decim := 2
derivedRate := baseRate / decim
derivedFFT := baseFFT / decim
if derivedRate >= 200000 && derivedFFT >= 256 {
derivedSpan := spanForPolicy(policy, float64(derivedRate))
derived := analysisLevel("surveillance-lowres", "surveillance-lowres", "surveillance", derivedRate, derivedFFT, rt.cfg.CenterHz, derivedSpan, "decimated", decim, baseRate)
levels = append(levels, derived)
specs = append(specs, surveillanceLevelSpec{Level: derived, Decim: decim, AllowGPU: false})
context.Derived = append(context.Derived, derived)
} }
} }
return levels, context

presentation := analysisLevel("presentation", "presentation", "presentation", baseRate, rt.cfg.Surveillance.DisplayBins, rt.cfg.CenterHz, span, "display", 1, baseRate)
context.Presentation = presentation

return surveillancePlan{
Primary: primary,
Levels: levels,
Presentation: presentation,
Context: context,
Specs: specs,
}
} }


func sameIQBuffer(a []complex64, b []complex64) bool { func sameIQBuffer(a []complex64, b []complex64) bool {


+ 13
- 9
cmd/sdrd/pipeline_runtime_test.go Datei anzeigen

@@ -44,18 +44,22 @@ func TestScheduledCandidateSelectionUsesPolicy(t *testing.T) {
} }


func TestSurveillanceLevelsRespectStrategy(t *testing.T) { func TestSurveillanceLevelsRespectStrategy(t *testing.T) {
cfg := config.Default()
det := detector.New(cfg.Detector, cfg.SampleRate, cfg.FFTSize)
window := fftutil.Hann(cfg.FFTSize)
rt := newDSPRuntime(cfg, det, window, &gpuStatus{})
policy := pipeline.Policy{SurveillanceStrategy: "single-resolution"} policy := pipeline.Policy{SurveillanceStrategy: "single-resolution"}
primary := pipeline.AnalysisLevel{Name: "primary", SampleRate: 2000000, FFTSize: 2048}
secondary := pipeline.AnalysisLevel{Name: "secondary", SampleRate: 1000000, FFTSize: 1024}
presentation := pipeline.AnalysisLevel{Name: "presentation", SampleRate: 2000000, FFTSize: 2048}
levels, _ := surveillanceLevels(policy, primary, secondary, presentation)
if len(levels) != 1 {
t.Fatalf("expected single level for single-resolution, got %d", len(levels))
plan := rt.buildSurveillancePlan(policy)
if len(plan.Levels) != 1 {
t.Fatalf("expected single level for single-resolution, got %d", len(plan.Levels))
} }
policy.SurveillanceStrategy = "multi-res" policy.SurveillanceStrategy = "multi-res"
levels, _ = surveillanceLevels(policy, primary, secondary, presentation)
if len(levels) != 2 {
t.Fatalf("expected secondary level for multi-res, got %d", len(levels))
plan = rt.buildSurveillancePlan(policy)
if len(plan.Levels) != 2 {
t.Fatalf("expected secondary level for multi-res, got %d", len(plan.Levels))
}
if plan.Levels[1].Decimation != 2 {
t.Fatalf("expected decimation factor 2, got %d", plan.Levels[1].Decimation)
} }
} }




+ 18
- 10
internal/pipeline/phases.go Datei anzeigen

@@ -8,11 +8,18 @@ type AnalysisLevel struct {
Truth string `json:"truth,omitempty"` Truth string `json:"truth,omitempty"`
SampleRate int `json:"sample_rate"` SampleRate int `json:"sample_rate"`
FFTSize int `json:"fft_size"` FFTSize int `json:"fft_size"`
BinHz float64 `json:"bin_hz,omitempty"`
Decimation int `json:"decimation,omitempty"`
CenterHz float64 `json:"center_hz"` CenterHz float64 `json:"center_hz"`
SpanHz float64 `json:"span_hz"` SpanHz float64 `json:"span_hz"`
Source string `json:"source,omitempty"` Source string `json:"source,omitempty"`
} }


type SurveillanceLevelSpectrum struct {
Level AnalysisLevel `json:"level"`
Spectrum []float64 `json:"spectrum_db,omitempty"`
}

type AnalysisContext struct { type AnalysisContext struct {
Surveillance AnalysisLevel `json:"surveillance,omitempty"` Surveillance AnalysisLevel `json:"surveillance,omitempty"`
Refinement AnalysisLevel `json:"refinement,omitempty"` Refinement AnalysisLevel `json:"refinement,omitempty"`
@@ -22,16 +29,17 @@ type AnalysisContext struct {
} }


type SurveillanceResult struct { type SurveillanceResult struct {
Level AnalysisLevel `json:"level"`
Levels []AnalysisLevel `json:"levels,omitempty"`
Candidates []Candidate `json:"candidates"`
Scheduled []ScheduledCandidate `json:"scheduled,omitempty"`
Finished []detector.Event `json:"finished"`
Signals []detector.Signal `json:"signals"`
NoiseFloor float64 `json:"noise_floor"`
Thresholds []float64 `json:"thresholds,omitempty"`
DisplayLevel AnalysisLevel `json:"display_level"`
Context AnalysisContext `json:"context,omitempty"`
Level AnalysisLevel `json:"level"`
Levels []AnalysisLevel `json:"levels,omitempty"`
Candidates []Candidate `json:"candidates"`
Scheduled []ScheduledCandidate `json:"scheduled,omitempty"`
Finished []detector.Event `json:"finished"`
Signals []detector.Signal `json:"signals"`
NoiseFloor float64 `json:"noise_floor"`
Thresholds []float64 `json:"thresholds,omitempty"`
DisplayLevel AnalysisLevel `json:"display_level"`
Context AnalysisContext `json:"context,omitempty"`
Spectra []SurveillanceLevelSpectrum `json:"spectra,omitempty"`
} }


type RefinementPlan struct { type RefinementPlan struct {


Laden…
Abbrechen
Speichern