wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use super::*;

#[test]
fn indexed_measurements_require_one_based_strictly_ordered_matches() {
    let concept = DicomCode::new("AREA", "99FRAMES", "Area").unwrap();
    let units = DicomCode::new("mm2", "UCUM", "square millimeter").unwrap();
    let measurement = AnnotationMeasurement::for_annotations(
        concept.clone(),
        units.clone(),
        vec![2.0, 4.0],
        vec![1, 3],
    )
    .unwrap();
    assert_eq!(measurement.concept(), &concept);
    assert_eq!(measurement.units(), &units);
    assert_eq!(measurement.values(), &[2.0, 4.0]);
    assert_eq!(measurement.annotation_indices(), Some([1, 3].as_slice()));

    assert!(AnnotationMeasurement::for_annotations(
        concept.clone(),
        units.clone(),
        vec![1.0],
        vec![1, 2]
    )
    .is_err());
    assert!(AnnotationMeasurement::for_annotations(
        concept.clone(),
        units.clone(),
        vec![1.0, 2.0],
        vec![2, 2]
    )
    .is_err());
    assert!(AnnotationMeasurement::for_annotations(concept, units, vec![1.0], vec![0]).is_err());
}

#[test]
fn group_builders_preserve_declared_semantics_and_edit_only_matching_geometry() {
    let category = DicomCode::new("MORPH", "99FRAMES", "Morphology").unwrap();
    let property = DicomCode::new("TUMOR", "99FRAMES", "Tumor").unwrap();
    let modifier = DicomCode::new("INV", "99FRAMES", "Invasive").unwrap();
    let region = DicomCode::new("BREAST", "99FRAMES", "Breast").unwrap();
    let structure = DicomCode::new("LOBULE", "99FRAMES", "Lobule").unwrap();
    let algorithm = AlgorithmIdentification::new(
        DicomCode::new("AI", "99FRAMES", "Artificial intelligence").unwrap(),
        "classifier",
        "1.0",
    )
    .unwrap();
    let mut points = AnnotationGroup::points(
        "Cells",
        category.clone(),
        property.clone(),
        [1, 2, 3],
        vec![Point2::new(1.0, 2.0), Point2::new(3.0, 4.0)],
    )
    .unwrap()
    .with_description("model-selected cells")
    .unwrap()
    .with_generation(GenerationType::Automatic, vec![algorithm])
    .unwrap()
    .with_property_type_modifiers(vec![modifier.clone()])
    .with_anatomic_regions(vec![region.clone()])
    .with_primary_anatomic_structures(vec![structure.clone()])
    .with_referenced_optical_paths(vec!["A".into(), "B".into()])
    .unwrap()
    .with_common_z_coordinates(vec![0.0, 0.5])
    .unwrap();
    points
        .add_measurement(
            AnnotationMeasurement::for_annotations(
                DicomCode::new("AREA", "99FRAMES", "Area").unwrap(),
                DicomCode::new("mm2", "UCUM", "square millimeter").unwrap(),
                vec![2.5],
                vec![2],
            )
            .unwrap(),
        )
        .unwrap();

    assert_eq!(points.description(), "model-selected cells");
    assert_eq!(points.generation_type(), GenerationType::Automatic);
    assert_eq!(points.algorithms().len(), 1);
    assert_eq!(
        points.property_type_modifiers(),
        std::slice::from_ref(&modifier)
    );
    assert_eq!(points.anatomic_regions(), std::slice::from_ref(&region));
    assert_eq!(
        points.primary_anatomic_structures(),
        std::slice::from_ref(&structure)
    );
    assert!(!points.applies_to_all_optical_paths());
    assert_eq!(points.referenced_optical_paths(), &["A", "B"]);
    assert!(!points.applies_to_all_z_planes());
    assert_eq!(points.common_z_coordinates(), &[0.0, 0.5]);
    assert!(points.editable());
    assert!(points.polygon_annotations().is_none());
    points
        .replace_points(vec![Point2::new(2.0, 2.0), Point2::new(3.0, 4.0)])
        .unwrap();

    let mut polygons = AnnotationGroup::polygons(
        "Region",
        category,
        property,
        [4, 5, 6],
        vec![vec![
            Point2::new(0.0, 0.0),
            Point2::new(4.0, 0.0),
            Point2::new(4.0, 4.0),
            Point2::new(0.0, 4.0),
        ]],
    )
    .unwrap();
    assert!(polygons.point_annotations().is_none());
    polygons
        .replace_polygons(vec![vec![
            Point2::new(0.5, 0.5),
            Point2::new(4.0, 0.0),
            Point2::new(4.0, 4.0),
            Point2::new(0.0, 4.0),
        ]])
        .unwrap();
}

#[test]
fn deterministic_uid_is_stable_for_semantics_and_ignores_geometry_and_prior_uid() {
    let category = DicomCode::new("MORPH", "99FRAMES", "Morphology").unwrap();
    let property = DicomCode::new("TUMOR", "99FRAMES", "Tumor").unwrap();
    let first = AnnotationGroup::points(
        "Cells",
        category.clone(),
        property.clone(),
        [1, 2, 3],
        vec![Point2::new(1.0, 2.0)],
    )
    .unwrap()
    .with_description("stable semantics")
    .unwrap()
    .with_deterministic_uid("viewer", "slide-1", "finding-7")
    .unwrap();
    let mut second = AnnotationGroup::points(
        "Cells",
        category,
        property,
        [1, 2, 3],
        vec![Point2::new(9.0, 10.0), Point2::new(11.0, 12.0)],
    )
    .unwrap()
    .with_uid("2.25.42")
    .unwrap()
    .with_description("stable semantics")
    .unwrap();
    second
        .add_measurement(AnnotationMeasurement::new(
            DicomCode::new("COUNT", "99FRAMES", "Count").unwrap(),
            DicomCode::new("1", "UCUM", "no units").unwrap(),
            vec![1.0, 2.0],
        ))
        .unwrap();
    let second = second
        .with_deterministic_uid("viewer", "slide-1", "finding-7")
        .unwrap();

    assert_eq!(first.uid(), second.uid());
    assert_eq!(first.uid(), "2.25.220411763936066836527837183276760079369");
    assert_ne!(
        first.uid(),
        first
            .clone()
            .with_deterministic_uid("viewer", "slide-1", "finding-8")
            .unwrap()
            .uid()
    );
}

#[test]
fn checked_geometry_replacement_is_atomic_and_revalidates_topology_and_coordinates() {
    let category = DicomCode::new("MORPH", "99FRAMES", "Morphology").unwrap();
    let property = DicomCode::new("TUMOR", "99FRAMES", "Tumor").unwrap();
    let square = vec![
        Point2::new(0.0, 0.0),
        Point2::new(4.0, 0.0),
        Point2::new(4.0, 4.0),
        Point2::new(0.0, 4.0),
    ];
    let mut polygons = AnnotationGroup::polygons(
        "Region",
        category.clone(),
        property.clone(),
        [1, 2, 3],
        vec![square.clone()],
    )
    .unwrap();

    for invalid in [
        vec![
            Point2::new(0.0, 0.0),
            Point2::new(4.0, 4.0),
            Point2::new(0.0, 4.0),
            Point2::new(4.0, 0.0),
        ],
        vec![
            Point2::new(0.0, 0.0),
            Point2::new(1.0, 0.0),
            Point2::new(2.0, 0.0),
        ],
        vec![
            Point2::new(f64::NAN, 0.0),
            Point2::new(4.0, 0.0),
            Point2::new(0.0, 4.0),
        ],
        vec![
            Point2::new(f64::INFINITY, 0.0),
            Point2::new(4.0, 0.0),
            Point2::new(0.0, 4.0),
        ],
    ] {
        assert!(polygons.replace_polygons(vec![invalid]).is_err());
        assert_eq!(
            polygons.polygon_annotations(),
            Some([square.clone()].as_slice())
        );
    }

    let mut points = AnnotationGroup::points(
        "Cells",
        category,
        property,
        [1, 2, 3],
        vec![Point2::new(1.0, 1.0), Point2::new(2.0, 2.0)],
    )
    .unwrap();
    points
        .add_measurement(AnnotationMeasurement::new(
            DicomCode::new("AREA", "99FRAMES", "Area").unwrap(),
            DicomCode::new("mm2", "UCUM", "square millimeter").unwrap(),
            vec![1.0, 2.0],
        ))
        .unwrap();
    let original = points.clone();
    assert!(points.replace_points(vec![Point2::new(1.0, 1.0)]).is_err());
    assert!(points
        .replace_points(vec![
            Point2::new(1.0, 1.0),
            Point2::new(2.0, 2.0),
            Point2::new(3.0, 3.0),
        ])
        .is_err());
    assert_eq!(points, original);
}

#[test]
fn checked_geometry_replacement_rejects_removed_indexed_measurement_target() {
    let mut points = AnnotationGroup::points(
        "Cells",
        DicomCode::new("MORPH", "99FRAMES", "Morphology").unwrap(),
        DicomCode::new("TUMOR", "99FRAMES", "Tumor").unwrap(),
        [1, 2, 3],
        vec![Point2::new(1.0, 1.0), Point2::new(2.0, 2.0)],
    )
    .unwrap();
    points
        .add_measurement(
            AnnotationMeasurement::for_annotations(
                DicomCode::new("AREA", "99FRAMES", "Area").unwrap(),
                DicomCode::new("mm2", "UCUM", "square millimeter").unwrap(),
                vec![2.0],
                vec![2],
            )
            .unwrap(),
        )
        .unwrap();
    let original = points.clone();

    assert!(points.replace_points(vec![Point2::new(1.0, 1.0)]).is_err());
    assert_eq!(points, original);
}

#[test]
fn group_builders_reject_conflicting_applicability_and_measurement_shapes() {
    let category = DicomCode::new("MORPH", "99FRAMES", "Morphology").unwrap();
    let property = DicomCode::new("TUMOR", "99FRAMES", "Tumor").unwrap();
    let base = || {
        AnnotationGroup::points(
            "Cells",
            category.clone(),
            property.clone(),
            [1, 2, 3],
            vec![Point2::new(1.0, 2.0)],
        )
        .unwrap()
    };

    assert!(base().with_description("contains\0nul").is_err());
    assert!(base().with_referenced_optical_paths(Vec::new()).is_err());
    assert!(base()
        .with_referenced_optical_paths(vec!["A".into(), "A".into()])
        .is_err());
    assert!(base().with_common_z_coordinates(Vec::new()).is_err());
    assert!(base().with_common_z_coordinates(vec![f64::NAN]).is_err());

    let mut group = base();
    let concept = DicomCode::new("AREA", "99FRAMES", "Area").unwrap();
    let units = DicomCode::new("mm2", "UCUM", "square millimeter").unwrap();
    assert!(group
        .add_measurement(AnnotationMeasurement::new(
            concept.clone(),
            units.clone(),
            Vec::new(),
        ))
        .is_err());
    assert!(group
        .add_measurement(AnnotationMeasurement::new(
            concept,
            units,
            vec![f64::INFINITY]
        ))
        .is_err());
    assert!(group.measurements().is_empty());
}