chromaframe-sdk 0.1.1

Deterministic, privacy-preserving color measurement and ranking SDK
Documentation
use crate::vision::{RegionKind, RegionSet, VisionError};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct OverlayColorKey {
    pub kind: RegionKind,
    pub color: String,
}

#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct OverlayRequest {
    pub output_path: PathBuf,
    pub include_points: bool,
    pub include_labels: bool,
}

impl fmt::Debug for OverlayRequest {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("OverlayRequest")
            .field("output_path", &"[REDACTED]")
            .field("include_points", &self.include_points)
            .field("include_labels", &self.include_labels)
            .finish()
    }
}

#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct OverlayArtifactSummary {
    pub output_path_redacted: String,
    pub dimensions: (u32, u32),
    pub region_count: usize,
    pub color_key: Vec<OverlayColorKey>,
}

impl fmt::Debug for OverlayArtifactSummary {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("OverlayArtifactSummary")
            .field("output_path", &"[REDACTED]")
            .field("dimensions", &self.dimensions)
            .field("region_count", &self.region_count)
            .field("color_key", &self.color_key)
            .finish()
    }
}

#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct VisionDebugOverlay {
    pub summary: OverlayArtifactSummary,
    pub warnings: Vec<String>,
}

impl fmt::Debug for VisionDebugOverlay {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("VisionDebugOverlay")
            .field("summary", &self.summary)
            .field("warning_count", &self.warnings.len())
            .finish()
    }
}

pub struct SummaryOverlayRenderer;

impl crate::vision::DebugOverlayRenderer for SummaryOverlayRenderer {
    fn render_overlay(
        &self,
        image: &crate::quality::NormalizedImage,
        regions: &RegionSet,
        _request: &OverlayRequest,
    ) -> Result<OverlayArtifactSummary, VisionError> {
        Ok(OverlayArtifactSummary {
            output_path_redacted: "[REDACTED]".to_string(),
            dimensions: (image.width, image.height),
            region_count: regions.regions.len(),
            color_key: default_color_key(),
        })
    }
}

pub fn default_color_key() -> Vec<OverlayColorKey> {
    vec![
        OverlayColorKey {
            kind: RegionKind::Skin,
            color: "red".to_string(),
        },
        OverlayColorKey {
            kind: RegionKind::Brow,
            color: "brown".to_string(),
        },
        OverlayColorKey {
            kind: RegionKind::Iris,
            color: "blue".to_string(),
        },
        OverlayColorKey {
            kind: RegionKind::Sclera,
            color: "cyan".to_string(),
        },
        OverlayColorKey {
            kind: RegionKind::Lip,
            color: "magenta".to_string(),
        },
        OverlayColorKey {
            kind: RegionKind::Hair,
            color: "purple".to_string(),
        },
    ]
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::quality::NormalizedImage;
    use crate::types::MeasurementMode;
    use crate::vision::{DebugOverlayRenderer, ExtractionQualityReport};

    #[test]
    fn default_color_key_includes_only_drawn_product_regions() {
        let kinds: Vec<_> = default_color_key()
            .into_iter()
            .map(|entry| entry.kind)
            .collect();

        assert_eq!(
            kinds,
            vec![
                RegionKind::Skin,
                RegionKind::Brow,
                RegionKind::Iris,
                RegionKind::Sclera,
                RegionKind::Lip,
                RegionKind::Hair,
            ]
        );
    }

    #[test]
    fn default_color_key_does_not_advertise_beard_or_orange() {
        let color_key = default_color_key();

        assert!(
            !color_key
                .iter()
                .any(|entry| entry.kind == RegionKind::Beard)
        );
        assert!(!color_key.iter().any(|entry| entry.color == "orange"));
    }

    #[test]
    fn summary_renderer_uses_default_product_color_key() {
        let image = NormalizedImage {
            width: 4,
            height: 4,
            pixels: Vec::new(),
            measurement_mode: MeasurementMode::SrgbAssumed,
            orientation_applied: true,
            icc_status: "not_embedded".to_string(),
        };
        let regions = RegionSet {
            image_width: 4,
            image_height: 4,
            regions: Vec::new(),
            extraction_quality: ExtractionQualityReport {
                faces_detected: 1,
                selected_face_index: Some(0),
                backend: "summary".to_string(),
                warnings: Vec::new(),
            },
            warnings: Vec::new(),
        };
        let request = OverlayRequest {
            output_path: "overlay.png".into(),
            include_points: false,
            include_labels: false,
        };

        let summary = SummaryOverlayRenderer
            .render_overlay(&image, &regions, &request)
            .expect("summary overlay rendering should be infallible");

        assert_eq!(summary.color_key, default_color_key());
        assert!(
            !summary
                .color_key
                .iter()
                .any(|entry| entry.kind == RegionKind::Beard)
        );
    }
}