wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use super::*;
use crate::{DiagnosticDisposition, DiagnosticSeverity};

fn code(value: &str, meaning: &str) -> DicomCode {
    DicomCode::new(value, "99FRAMES", meaning).unwrap()
}

#[test]
fn semantic_vectorization_preserves_ann_fields_and_reports_identity_loss() {
    let (mut segmentation, _) = super::performance_tests::benchmark_documents();
    segmentation.segments.truncate(1);
    segmentation
        .imported_binary_frames
        .as_mut()
        .unwrap()
        .retain(|frame| frame.segment_number == 1);
    let modifier = code("INV", "Invasive");
    let region = code("BREAST", "Breast");
    let structure = code("LOBULE", "Lobule");
    let algorithm =
        AlgorithmIdentification::new(code("AI", "Artificial intelligence"), "classifier", "2.0")
            .unwrap();
    segmentation.segments[0] = segmentation.segments[0]
        .clone()
        .with_description("Full semantic segment")
        .unwrap()
        .with_generation(GenerationType::Automatic, vec![algorithm.clone()])
        .unwrap()
        .with_property_type_modifiers(vec![modifier.clone()])
        .with_anatomic_regions(vec![region.clone()])
        .with_primary_anatomic_structures(vec![structure.clone()])
        .with_tracking("tracking-id", "2.25.4242")
        .unwrap();

    let rejected = segmentation
        .vectorized_annotations(SegToAnnConversionPolicy::RejectLoss)
        .unwrap_err();
    assert!(rejected
        .to_string()
        .contains("SEGMENT_NUMBER_NOT_REPRESENTABLE"));
    assert!(rejected
        .to_string()
        .contains("SEG_TRACKING_ID_NOT_REPRESENTABLE"));

    let vectorized = segmentation
        .vectorized_annotations(SegToAnnConversionPolicy::AllowLoss)
        .unwrap();
    assert_eq!(vectorized.groups().len(), 1);
    let group = &vectorized.groups()[0];
    let segment = &segmentation.segments[0];
    assert_eq!(group.uid(), "2.25.4242");
    assert_eq!(group.label(), segment.label());
    assert_eq!(group.description(), "Full semantic segment");
    assert_eq!(group.generation_type(), GenerationType::Automatic);
    assert_eq!(group.algorithms(), &[algorithm]);
    assert_eq!(group.category(), segment.category());
    assert_eq!(group.property_type(), segment.property_type());
    assert_eq!(group.property_type_modifiers(), &[modifier]);
    assert_eq!(group.anatomic_regions(), &[region]);
    assert_eq!(group.primary_anatomic_structures(), &[structure]);
    assert_eq!(
        group.recommended_display_cielab(),
        segment.recommended_display_cielab()
    );
    let codes = vectorized
        .diagnostics()
        .iter()
        .map(InteroperabilityDiagnostic::code)
        .collect::<Vec<_>>();
    assert!(codes.contains(&"SEGMENT_NUMBER_NOT_REPRESENTABLE"));
    assert!(codes.contains(&"SEG_TRACKING_ID_NOT_REPRESENTABLE"));
    assert!(codes.contains(&"SEG_RASTER_VECTORIZED"));
}

#[test]
fn semantic_vectorization_rejects_an_empty_segment_instead_of_dropping_it() {
    let (mut segmentation, _) = super::performance_tests::benchmark_documents();
    segmentation.segments.truncate(2);
    segmentation.segments[0].source_segment_number = None;
    segmentation.segments[1].source_segment_number = None;
    segmentation
        .imported_binary_frames
        .as_mut()
        .unwrap()
        .retain(|frame| frame.segment_number == 1);
    for frame in segmentation.imported_binary_frames.as_mut().unwrap() {
        frame.segment_number = 1;
    }

    let error = segmentation
        .vectorized_annotations(SegToAnnConversionPolicy::RejectLoss)
        .unwrap_err();
    assert!(error.to_string().contains("EMPTY_SEGMENT_NOT_VECTORIZED"));
}

#[test]
fn semantic_vectorization_carries_import_loss_diagnostics() {
    let (mut segmentation, _) = super::performance_tests::benchmark_documents();
    segmentation.segments.truncate(1);
    segmentation
        .imported_binary_frames
        .as_mut()
        .unwrap()
        .retain(|frame| frame.segment_number == 1);
    segmentation.segments[0].source_segment_number = None;
    segmentation
        .diagnostics
        .push(InteroperabilityDiagnostic::new(
            "SEG_APPLICABILITY_NOT_REPRESENTABLE",
            DiagnosticSeverity::Warning,
            "$.SegmentSequence[0].Applicability",
            DiagnosticDisposition::WouldDrop,
            "synthetic imported applicability restriction cannot be represented in ANN",
        ));

    let error = segmentation
        .vectorized_annotations(SegToAnnConversionPolicy::RejectLoss)
        .unwrap_err();
    assert!(error
        .to_string()
        .contains("SEG_APPLICABILITY_NOT_REPRESENTABLE"));

    let vectorized = segmentation
        .vectorized_annotations(SegToAnnConversionPolicy::AllowLoss)
        .unwrap();
    assert!(vectorized
        .diagnostics()
        .iter()
        .any(|diagnostic| diagnostic.code() == "SEG_APPLICABILITY_NOT_REPRESENTABLE"));
}