wsi-dicom-annotations 0.2.1

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

#[test]
#[ignore = "requires optional dciodvfy, dcmdump, and dcm2json command-line tools"]
fn exported_ann_and_seg_pass_external_dicom_validation() {
    let dir = tempfile::tempdir().unwrap();
    let source = dir.path().join("source.dcm");
    let ann_path = dir.path().join("annotations.dcm");
    let revised_ann_path = dir.path().join("annotations-revised.dcm");
    let frame_input_path = dir.path().join("frame-input.dcm");
    let frame_ann_path = dir.path().join("frame-annotations.dcm");
    let three_d_input_path = dir.path().join("3d-input.dcm");
    let three_d_ann_path = dir.path().join("3d-annotations.dcm");
    let seg_path = dir.path().join("segmentation.dcm");
    write_source_wsi(&source, 16, 12, 4, 4);
    let mut source_object = dicom_object::open_file(&source).unwrap();
    add_specimen_metadata(&mut source_object);
    source_object.write_to_file(&source).unwrap();
    let context = DicomAnnotationContext::from_source(&source).unwrap();
    let category = code("MORPH", "Morphologically abnormal structure");
    let property = code("VIABLE_TUMOR", "Viable tumor");
    let polygon = vec![
        Point2::new(0.0, 0.0),
        Point2::new(8.0, 0.0),
        Point2::new(8.0, 8.0),
        Point2::new(0.0, 8.0),
    ];
    let annotations = AnnotationDocument::new(
        context.clone(),
        vec![AnnotationGroup::polygons(
            "Viable tumor",
            category.clone(),
            property.clone(),
            [48_000, 40_000, 20_000],
            vec![polygon.clone()],
        )
        .unwrap()],
    )
    .unwrap();
    annotations.write_ann(&ann_path).unwrap();
    annotations.revised().write_ann(&revised_ann_path).unwrap();
    write_native_ann(
        &frame_input_path,
        "2D",
        Some("FRAME"),
        Some(2),
        &[(
            AnnotationGraphicType::Rectangle,
            vec![0.0, 0.0, 3.0, 0.0, 3.0, 2.0, 0.0, 2.0],
            vec![],
            None,
        )],
    );
    AnnotationDocument::read_ann(&frame_input_path, &context)
        .unwrap()
        .revised()
        .write_ann(&frame_ann_path)
        .unwrap();
    write_native_ann(
        &three_d_input_path,
        "3D",
        None,
        None,
        &[(
            AnnotationGraphicType::Point,
            vec![0.001, 0.002],
            vec![],
            Some(vec![0.0]),
        )],
    );
    AnnotationDocument::read_ann(&three_d_input_path, &context)
        .unwrap()
        .revised()
        .write_ann(&three_d_ann_path)
        .unwrap();
    SegmentationDocument::binary(
        context,
        vec![SegmentationSegment::new(
            "Viable tumor",
            category,
            property,
            [48_000, 40_000, 20_000],
            vec![polygon],
            Vec::new(),
        )
        .unwrap()],
    )
    .unwrap()
    .write_seg(&seg_path)
    .unwrap();

    for path in [
        ann_path,
        revised_ann_path,
        frame_ann_path,
        three_d_ann_path,
        seg_path,
    ] {
        for command in ["dciodvfy", "dcmdump", "dcm2json"] {
            let output = std::process::Command::new(command)
                .arg(&path)
                .output()
                .unwrap_or_else(|_| panic!("{command} should be installed for this ignored test"));
            assert!(
                output.status.success(),
                "{command} failed for {}:\n{}{}",
                path.display(),
                String::from_utf8_lossy(&output.stdout),
                String::from_utf8_lossy(&output.stderr)
            );
            if command == "dcm2json" {
                assert_eq!(
                    output
                        .stdout
                        .iter()
                        .copied()
                        .find(|byte| !byte.is_ascii_whitespace()),
                    Some(b'{'),
                    "dcm2json output should begin with a JSON object"
                );
            }
        }
    }
}

#[test]
#[ignore = "requires a Python environment containing pydicom and highdicom"]
fn exported_volume_ann_is_readable_with_pydicom_and_highdicom() {
    let directory = tempfile::tempdir().unwrap();
    let source_path = directory.path().join("source.dcm");
    let annotation_path = directory.path().join("annotations.dcm");
    write_source_wsi(&source_path, 16, 12, 4, 4);
    let mut source_object = dicom_object::open_file(&source_path).unwrap();
    source_object.put(DataElement::new(
        tags::CONTAINER_IDENTIFIER,
        VR::LO,
        "PYTHON-READABLE-SLIDE",
    ));
    source_object.write_to_file(&source_path).unwrap();
    let source = DicomAnnotationContext::from_source(&source_path).unwrap();
    AnnotationDocument::new(
        source,
        vec![AnnotationGroup::points(
            "Cells",
            code("MORPH", "Morphology"),
            code("CELL", "Cell"),
            [0, 0, 0],
            vec![Point2::new(1.0, 1.0)],
        )
        .unwrap()],
    )
    .unwrap()
    .write_ann(&annotation_path)
    .unwrap();

    let python = std::env::var_os("WSI_DICOM_PYTHON").unwrap_or_else(|| "python3".into());
    let script = r#"
import highdicom as hd
import pydicom
import sys

dataset = pydicom.dcmread(sys.argv[1])
assert str(dataset.PixelOriginInterpretation) == "VOLUME"
assert str(dataset.FrameOfReferenceUID) == "1.2.826.0.1.3680043.10.777.104"
assert str(dataset.ContainerIdentifier) == "PYTHON-READABLE-SLIDE"
annotation = hd.ann.MicroscopyBulkSimpleAnnotations.from_dataset(dataset, copy=False)
assert len(annotation.get_annotation_groups()) == 1
"#;
    let output = std::process::Command::new(python)
        .args(["-c", script])
        .arg(&annotation_path)
        .output()
        .expect("configured Python interpreter should start");
    assert!(
        output.status.success(),
        "Python DICOM readers failed:\n{}{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}