wsi-annotation-tests 0.1.3

DICOM Annotation Test Suite for WSI ANN, SEG, SR, and Parametric Map
Documentation
use std::fs;
use std::path::Path;

use serde_json::Value;

use crate::compare::ComparisonResult;

use super::MatrixObservation;
use super::case::{Case, CaseDomain};
use super::observation::CaseRecorder;
use super::phases::{InputPhases, RewritePhases, VerificationPhases};

pub(super) fn finalize_roundtrip(
    case: &Case<'_>,
    domain: CaseDomain<'_>,
    recorder: CaseRecorder,
    input: &InputPhases,
    rewrite: &RewritePhases,
    verification: VerificationPhases,
) -> MatrixObservation {
    let roundtrip_equal = rewrite.viewer.is_ok()
        && rewrite.reference.is_ok()
        && verification.identity_message.is_none();
    let message = messages(
        [
            &input.reference,
            &input.comparison,
            &rewrite.viewer,
            &rewrite.reference,
        ],
        verification.identity_message,
    );
    let mut observation = recorder.finish();
    observation.inspect_equal = input.comparison.is_ok();
    observation.roundtrip_equal = Some(roundtrip_equal);
    observation.highdicom_readable = true;
    match domain {
        CaseDomain::Ann { .. } => {
            let errors = [&input.comparison, &rewrite.viewer, &rewrite.reference];
            observation.coordinate_error_max_px =
                Some(max_error(errors, |result| result.coordinate_error.max));
            observation.coordinate_error_median_px =
                Some(max_error(errors, |result| result.coordinate_error.median));
            observation.coordinate_error_rms_px =
                Some(max_error(errors, |result| result.coordinate_error.rms));
        }
        CaseDomain::Seg { .. } => {
            observation.dice = metric_number(verification.metrics.as_ref(), "dice");
            observation.mask_metrics = verification.metrics;
        }
    }
    observation.input_bytes = file_size(case.path).unwrap_or(observation.input_bytes);
    observation.output_bytes = Some(verification.output_bytes);
    observation.input_sop_instance_uid = verification.input_metadata.sop_instance_uid;
    observation.output_sop_instance_uid = Some(verification.output_metadata.sop_instance_uid);
    observation.message = message;
    observation
}
pub(super) fn messages<const N: usize>(
    results: [&ComparisonResult; N],
    extra: Option<String>,
) -> String {
    let mut messages = results
        .into_iter()
        .flat_map(|result| &result.findings)
        .map(|finding| finding.message.clone())
        .collect::<Vec<_>>();
    messages.extend(extra);
    messages.join("; ")
}

fn max_error<const N: usize>(
    results: [&ComparisonResult; N],
    value: impl Fn(&ComparisonResult) -> f64,
) -> f64 {
    results.into_iter().map(value).fold(0.0, f64::max)
}

pub(super) fn metric_number(metrics: Option<&Value>, key: &str) -> Option<f64> {
    metrics
        .and_then(|value| value.get(key))
        .and_then(Value::as_f64)
}

pub(super) fn file_size(path: &Path) -> Result<u64, String> {
    fs::metadata(path)
        .map(|metadata| metadata.len())
        .map_err(|error| format!("could not stat {}: {error}", path.display()))
}