saccade 0.2.6

Visual-regression CLI for renderers and graphics code, built on NVIDIA FLIP
//! No-reference quality CLI; reports measurements without a content-independent quality gate.
use crate::{agent::CliError, general_cmd};
use saccade_core::general::{assessment, input};
use serde_json::{Value, json};
use std::path::{Path, PathBuf};
#[derive(clap::Args)]
pub(crate) struct Args {
    image: PathBuf,
    #[command(flatten)]
    checks: crate::vision_checks::Checks,
    /// Reference quality measurements; deltas are image minus compare-to.
    #[arg(long)]
    compare_to: Option<PathBuf>,
    #[arg(long, default_value = "assessment-report")]
    out: PathBuf,
    #[arg(long)]
    json: bool,
}
pub(crate) fn run(args: Args) -> Result<u8, CliError> {
    let mut value = measure(&args.image, args.compare_to.as_deref(), &args.out)?;
    value["vision_checks"] = args.checks.measure(&args.image)?;
    general_cmd::emit_document(value, Some(&args.out), args.json)
}
fn one(path: &Path) -> Result<Value, CliError> {
    let bytes = input::bytes(path, input::MAX_BYTES)?;
    let image = input::decode(&bytes)?;
    Ok(
        json!({"encoded_sha256":saccade_core::localized::digest(&bytes),"dimensions":[image.width(),image.height()],"measures":assessment::assess(&image)?}),
    )
}
pub(crate) fn measure(
    image: &Path,
    compare_to: Option<&Path>,
    out: &Path,
) -> Result<Value, CliError> {
    let current = one(image)?;
    let before = compare_to.map(one).transpose()?;
    let mut delta = serde_json::Map::new();
    if let Some(before) = &before
        && let (Some(a), Some(b)) = (
            current["measures"].as_object(),
            before["measures"].as_object(),
        )
    {
        for (key, value) in a {
            delta.insert(
                key.clone(),
                match (value.as_f64(), b.get(key).and_then(Value::as_f64)) {
                    (Some(a), Some(b)) => json!(a - b),
                    _ => Value::Null,
                },
            );
        }
    }
    let mut inputs = vec![image];
    inputs.extend(compare_to);
    general_cmd::prepare_out(out, &inputs)?;
    Ok(
        json!({"schema":assessment::SCHEMA,"operation":"assess","related_commands":crate::capability_cmd::related_commands(),"verdict":"unknown","counts":{"images":if before.is_some(){2}else{1}},"image":current,"compare_to":before,"deltas":delta,"meanings":{"laplacian_variance":"luma-squared; lower can mean blur or flat content","edge_width_px":"contrast/local slope in pixels; larger can mean broader edges","noise_sigma":"MAD high-pass estimate in low-gradient areas, luma units; texture can contribute","block_boundary_excess":"8-pixel boundary difference excess, luma units; content can mimic blocks","occupied_luma_fraction":"occupied 8-bit levels/256; fewer can mean posterisation or flat content","shallow_plateau_step_fraction":"shallow jumps between plateaus; banding candidate, not a verdict","black_clip_fraction":"all-black RGB pixels after white compositing","white_clip_fraction":"all-white RGB pixels after white compositing","low_channel_fraction":"RGB samples at 0","high_channel_fraction":"RGB samples at 255","transparent_fraction":"alpha below 255; white compositing affects all measures"},"learned_score":{"status":"unavailable","reason":"no reviewed permissive model/export and pinned artifact supplied"},"limitations":["thresholds are content-dependent; no good/bad quality or authenticity verdict","noise, blockiness and banding indicators can respond to intended image structure","edge-width sampling can miss small features; absent edges are unavailable","8-bit SDR metrics; use HDR-FLIP for HDR pair comparison","paired deltas are image minus compare-to; resolution differences affect interpretation"]}),
    )
}
#[cfg(feature = "mcp")]
pub(crate) fn schemas() -> Vec<Value> {
    vec![
        json!({"type":"object","properties":{"operation":{"const":"assess","type":"string"},"image":{"type":"string"},"compare_to":{"type":"string"},"out":{"type":"string"}},"required":["operation","image","out"],"additionalProperties":false}),
    ]
}