Skip to main content

projective_grid/
diagnostics.rs

1//! Opt-in evidence explaining how detections were produced.
2//!
3//! Diagnostics are intentionally separate from the ordinary detection result.
4//! Their serializable schema supports debugging, tuning, benchmarks, and blog
5//! visualizations without making intermediate pipeline stages part of the
6//! stable facade contract.
7
8/// Exact trace of the square topological pipeline.
9pub mod trace {
10    pub use crate::topological::trace::*;
11}
12
13pub use crate::result::{RejectedFeature, RejectionReason};
14
15use crate::detect::{detect_grid_all_internal, DetectionRequest};
16use crate::error::{GridError, Result};
17use crate::result::GridDetection;
18
19/// Diagnostics associated with one returned grid component.
20#[derive(Clone, Debug)]
21#[non_exhaustive]
22pub struct ComponentDiagnostics {
23    rejected: Vec<RejectedFeature>,
24}
25
26impl ComponentDiagnostics {
27    /// Features rejected while assembling this component.
28    pub fn rejected(&self) -> &[RejectedFeature] {
29        &self.rejected
30    }
31}
32
33/// Opt-in diagnostics for a multi-component detection.
34#[derive(Clone, Debug)]
35#[non_exhaustive]
36pub struct DetectionDiagnostics {
37    schema_version: u32,
38    components: Vec<ComponentDiagnostics>,
39}
40
41impl DetectionDiagnostics {
42    /// Diagnostics schema version.
43    pub fn schema_version(&self) -> u32 {
44        self.schema_version
45    }
46
47    /// Per-component diagnostics in the same order as returned detections.
48    pub fn components(&self) -> &[ComponentDiagnostics] {
49        &self.components
50    }
51}
52
53/// Detect all components and retain rejection diagnostics.
54pub fn detect_grid_all(
55    request: DetectionRequest<'_>,
56) -> Result<(Vec<GridDetection>, DetectionDiagnostics)> {
57    let solutions = detect_grid_all_internal(request)?;
58    let mut detections = Vec::with_capacity(solutions.len());
59    let mut components = Vec::with_capacity(solutions.len());
60    for solution in solutions {
61        detections.push(solution.detection);
62        components.push(ComponentDiagnostics {
63            rejected: solution.rejected,
64        });
65    }
66    Ok((
67        detections,
68        DetectionDiagnostics {
69            schema_version: 1,
70            components,
71        },
72    ))
73}
74
75/// Detect the primary component and retain its rejection diagnostics.
76pub fn detect_grid(request: DetectionRequest<'_>) -> Result<(GridDetection, ComponentDiagnostics)> {
77    let (mut detections, diagnostics) = detect_grid_all(request)?;
78    if detections.is_empty() {
79        return Err(GridError::InsufficientEvidence);
80    }
81    let detection = detections.remove(0);
82    let component = diagnostics
83        .components
84        .into_iter()
85        .next()
86        .unwrap_or(ComponentDiagnostics {
87            rejected: Vec::new(),
88        });
89    Ok((detection, component))
90}