wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use dicom_core::value::PrimitiveValue;
use dicom_core::{DataElement, VR};
use dicom_dictionary_std::tags;
use dicom_object::InMemDicomObject;

use crate::annotations::coded_content::code_item;
use crate::annotations::derived_object::sop_reference_item;
use crate::annotations::dicom_dataset::{put_text, sequence};
use crate::annotations::dicom_value::format_ds;
use crate::{DicomCode, Error, Result};

use super::super::document::{ParametricMapDocument, ValueRange};
use super::super::frame::FrameKey;
use super::quantity::real_world_value_item;

pub(super) fn shared_functional_groups(
    document: &ParametricMapDocument,
) -> Result<InMemDicomObject> {
    let mut item = InMemDicomObject::new_empty();
    let mut measures = InMemDicomObject::new_empty();
    put_text(
        &mut measures,
        tags::PIXEL_SPACING,
        VR::DS,
        &format!(
            "{}\\{}",
            format_ds(document.geometry.pixel_spacing[0]),
            format_ds(document.geometry.pixel_spacing[1])
        ),
    );
    put_text(
        &mut measures,
        tags::SLICE_THICKNESS,
        VR::DS,
        &format_ds(document.geometry.slice_thickness),
    );
    item.put(sequence(tags::PIXEL_MEASURES_SEQUENCE, vec![measures]));
    item.put(sequence(
        tags::PIXEL_VALUE_TRANSFORMATION_SEQUENCE,
        vec![identity_pixel_transform()],
    ));
    item.put(sequence(
        tags::PARAMETRIC_MAP_FRAME_TYPE_SEQUENCE,
        vec![parametric_map_frame_type()],
    ));
    item.put(sequence(
        tags::DERIVATION_IMAGE_SEQUENCE,
        vec![derivation_image(document)?],
    ));
    if document.selected_channels.len() == 1 {
        let channel = document.selected_channels[0];
        item.put(sequence(
            tags::FRAME_VOILUT_SEQUENCE,
            vec![voi_item(
                document.channel_ranges[0],
                &document.profile.channels[channel].name,
            )],
        ));
        item.put(sequence(
            tags::REAL_WORLD_VALUE_MAPPING_SEQUENCE,
            vec![real_world_value_item(document, channel, 1)?],
        ));
    }
    Ok(item)
}

pub(super) fn per_frame_functional_groups(
    document: &ParametricMapDocument,
    frame: FrameKey,
) -> Result<InMemDicomObject> {
    let mut item = InMemDicomObject::new_empty();
    let mut content = InMemDicomObject::new_empty();
    if !document.dense {
        let mut indices = Vec::with_capacity(if document.selected_channels.len() > 1 {
            3
        } else {
            2
        });
        if document.selected_channels.len() > 1 {
            indices.push(frame.channel_ordinal);
        }
        indices.extend([frame.tile_column + 1, frame.tile_row + 1]);
        content.put(DataElement::new(
            tags::DIMENSION_INDEX_VALUES,
            VR::UL,
            PrimitiveValue::U32(indices.into()),
        ));
        item.put(sequence(
            tags::PLANE_POSITION_SLIDE_SEQUENCE,
            vec![plane_position(document, frame)?],
        ));
    }
    item.put(sequence(tags::FRAME_CONTENT_SEQUENCE, vec![content]));
    if document.selected_channels.len() > 1 {
        let ordinal = usize::try_from(frame.channel_ordinal)
            .map_err(|_| Error::InvalidInput("PM channel ordinal does not fit usize".into()))?;
        let range = document.channel_ranges[ordinal - 1];
        let channel = &document.profile.channels[frame.channel];
        item.put(sequence(
            tags::FRAME_VOILUT_SEQUENCE,
            vec![voi_item(range, &channel.name)],
        ));
        item.put(sequence(
            tags::REAL_WORLD_VALUE_MAPPING_SEQUENCE,
            vec![real_world_value_item(
                document,
                frame.channel,
                frame.channel_ordinal,
            )?],
        ));
    }
    Ok(item)
}

fn identity_pixel_transform() -> InMemDicomObject {
    let mut item = InMemDicomObject::new_empty();
    put_text(&mut item, tags::RESCALE_INTERCEPT, VR::DS, "0");
    put_text(&mut item, tags::RESCALE_SLOPE, VR::DS, "1");
    put_text(&mut item, tags::RESCALE_TYPE, VR::LO, "US");
    item
}

fn parametric_map_frame_type() -> InMemDicomObject {
    let mut item = InMemDicomObject::new_empty();
    put_text(
        &mut item,
        tags::FRAME_TYPE,
        VR::CS,
        "DERIVED\\PRIMARY\\VOLUME\\QUANTITY",
    );
    item
}

fn voi_item(range: ValueRange, name: &str) -> InMemDicomObject {
    let width = range.maximum - range.minimum;
    let (center, width) = if width > 0.0 {
        (range.minimum + width / 2.0, width)
    } else {
        (range.minimum, 1.0)
    };
    let mut item = InMemDicomObject::new_empty();
    put_text(&mut item, tags::WINDOW_CENTER, VR::DS, &format_ds(center));
    put_text(&mut item, tags::WINDOW_WIDTH, VR::DS, &format_ds(width));
    put_text(
        &mut item,
        tags::WINDOW_CENTER_WIDTH_EXPLANATION,
        VR::LO,
        name,
    );
    put_text(&mut item, tags::VOILUT_FUNCTION, VR::CS, "LINEAR_EXACT");
    item
}

fn plane_position(document: &ParametricMapDocument, frame: FrameKey) -> Result<InMemDicomObject> {
    let column = frame.column_position(document.descriptor)?;
    let row = frame.row_position(document.descriptor)?;
    let position = document.geometry.frame_position(column, row);
    let mut item = InMemDicomObject::new_empty();
    item.put(DataElement::new(
        tags::COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX,
        VR::SL,
        PrimitiveValue::from(
            i32::try_from(column)
                .map_err(|_| Error::InvalidInput("PM column position exceeds DICOM SL".into()))?,
        ),
    ));
    item.put(DataElement::new(
        tags::ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX,
        VR::SL,
        PrimitiveValue::from(
            i32::try_from(row)
                .map_err(|_| Error::InvalidInput("PM row position exceeds DICOM SL".into()))?,
        ),
    ));
    for (tag, value) in [
        (tags::X_OFFSET_IN_SLIDE_COORDINATE_SYSTEM, position[0]),
        (tags::Y_OFFSET_IN_SLIDE_COORDINATE_SYSTEM, position[1]),
        (tags::Z_OFFSET_IN_SLIDE_COORDINATE_SYSTEM, position[2]),
    ] {
        put_text(&mut item, tag, VR::DS, &format_ds(value));
    }
    Ok(item)
}

fn derivation_image(document: &ParametricMapDocument) -> Result<InMemDicomObject> {
    let mut reference = sop_reference_item(
        document.source.sop_class_uid(),
        document.source.sop_instance_uid(),
    );
    reference.put(sequence(
        tags::PURPOSE_OF_REFERENCE_CODE_SEQUENCE,
        vec![code_item(&DicomCode::new(
            "121322",
            "DCM",
            "Source Image for Image Processing Operation",
        )?)],
    ));
    put_text(
        &mut reference,
        tags::SPATIAL_LOCATIONS_PRESERVED,
        VR::CS,
        "YES",
    );
    let mut derivation = InMemDicomObject::new_empty();
    derivation.put(sequence(
        tags::DERIVATION_CODE_SEQUENCE,
        vec![code_item(&DicomCode::new(
            "MODEL_INFERENCE",
            "99FRAMES",
            "Model inference",
        )?)],
    ));
    derivation.put(sequence(tags::SOURCE_IMAGE_SEQUENCE, vec![reference]));
    Ok(derivation)
}