wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use crate::annotations::dicom_dataset::{dicom_now, new_dicom_uid};
use crate::{Error, Result};

use super::document::OutputFrameCursor;
use super::frame::FrameKey;
use super::writer::{ConcatenationSpec, InstanceSpec};

#[derive(Debug, Clone)]
pub struct ParametricMapPlan {
    pub(super) semantic_digest: String,
    pub(super) series_instance_uid: String,
    pub(super) dimension_organization_uid: String,
    pub(super) concatenation_uid: Option<String>,
    pub(super) concatenation_source_sop_instance_uid: Option<String>,
    pub(super) content_date: String,
    pub(super) content_time: String,
    pub(super) parts: Vec<ParametricMapPartPlan>,
}

#[derive(Debug, Clone)]
pub struct ParametricMapPartPlan {
    pub(super) sop_instance_uid: String,
    pub(super) frame_offset: u32,
    pub(super) frame_count: u32,
    pub(super) pixel_value_length: u32,
    pub(super) encoded_file_length: u64,
    pub(super) pixel_sha256: String,
}

#[derive(Debug, Clone)]
pub struct ParametricMapInstance {
    sop_instance_uid: String,
    series_instance_uid: String,
    frame_offset: u32,
    frame_count: u32,
    pixel_value_length: u32,
    encoded_file_length: u64,
    pixel_sha256: String,
}

impl ParametricMapPlan {
    #[must_use]
    pub fn parts(&self) -> &[ParametricMapPartPlan] {
        &self.parts
    }

    #[must_use]
    pub fn series_instance_uid(&self) -> &str {
        &self.series_instance_uid
    }

    #[must_use]
    pub fn concatenation_uid(&self) -> Option<&str> {
        self.concatenation_uid.as_deref()
    }

    #[must_use]
    pub fn concatenation_source_sop_instance_uid(&self) -> Option<&str> {
        self.concatenation_source_sop_instance_uid.as_deref()
    }

    pub(super) fn instance_spec<'a>(
        &'a self,
        part: &'a ParametricMapPartPlan,
        frames: &'a [FrameKey],
        number: u16,
        total: u16,
    ) -> Result<InstanceSpec<'a>> {
        let concatenation = match (
            self.concatenation_uid.as_deref(),
            self.concatenation_source_sop_instance_uid.as_deref(),
        ) {
            (Some(uid), Some(source_sop_instance_uid)) => Some(ConcatenationSpec {
                uid,
                source_sop_instance_uid,
                number,
                total,
            }),
            (None, None) => None,
            _ => {
                return Err(Error::InvalidInput(
                    "PM plan has incomplete concatenation identity".into(),
                ))
            }
        };
        Ok(InstanceSpec {
            sop_instance_uid: &part.sop_instance_uid,
            series_instance_uid: &self.series_instance_uid,
            dimension_organization_uid: &self.dimension_organization_uid,
            frames,
            frame_offset: part.frame_offset,
            concatenation,
            content_date: &self.content_date,
            content_time: &self.content_time,
        })
    }
}

impl ParametricMapPartPlan {
    #[must_use]
    pub fn sop_instance_uid(&self) -> &str {
        &self.sop_instance_uid
    }

    #[must_use]
    pub const fn frame_offset(&self) -> u32 {
        self.frame_offset
    }

    #[must_use]
    pub const fn frame_count(&self) -> u32 {
        self.frame_count
    }

    #[must_use]
    pub const fn pixel_value_length(&self) -> u32 {
        self.pixel_value_length
    }

    #[must_use]
    pub const fn encoded_file_length(&self) -> u64 {
        self.encoded_file_length
    }

    #[must_use]
    pub fn pixel_sha256(&self) -> &str {
        &self.pixel_sha256
    }
}

impl ParametricMapInstance {
    pub(super) fn from_part(part: &ParametricMapPartPlan, series_instance_uid: &str) -> Self {
        Self {
            sop_instance_uid: part.sop_instance_uid.clone(),
            series_instance_uid: series_instance_uid.to_string(),
            frame_offset: part.frame_offset,
            frame_count: part.frame_count,
            pixel_value_length: part.pixel_value_length,
            encoded_file_length: part.encoded_file_length,
            pixel_sha256: part.pixel_sha256.clone(),
        }
    }

    #[must_use]
    pub fn sop_instance_uid(&self) -> &str {
        &self.sop_instance_uid
    }

    #[must_use]
    pub fn series_instance_uid(&self) -> &str {
        &self.series_instance_uid
    }

    #[must_use]
    pub const fn frame_offset(&self) -> u32 {
        self.frame_offset
    }

    #[must_use]
    pub const fn frame_count(&self) -> u32 {
        self.frame_count
    }

    #[must_use]
    pub const fn pixel_value_length(&self) -> u32 {
        self.pixel_value_length
    }

    #[must_use]
    pub const fn encoded_file_length(&self) -> u64 {
        self.encoded_file_length
    }

    #[must_use]
    pub fn pixel_sha256(&self) -> &str {
        &self.pixel_sha256
    }
}

pub(super) struct PlanIdentifiers {
    pub(super) series_instance_uid: String,
    pub(super) dimension_organization_uid: String,
    pub(super) concatenation_uid: String,
    pub(super) concatenation_source_sop_instance_uid: String,
    pub(super) content_date: String,
    pub(super) content_time: String,
}

impl PlanIdentifiers {
    pub(super) fn new() -> Self {
        let (content_date, content_time) = dicom_now();
        Self {
            series_instance_uid: new_dicom_uid(),
            dimension_organization_uid: new_dicom_uid(),
            concatenation_uid: new_dicom_uid(),
            concatenation_source_sop_instance_uid: new_dicom_uid(),
            content_date,
            content_time,
        }
    }

    pub(super) fn instance_spec<'a>(
        &'a self,
        sop_instance_uid: &'a str,
        frames: &'a [FrameKey],
        frame_offset: u32,
        number: u16,
        total: u16,
        concatenated: bool,
    ) -> InstanceSpec<'a> {
        InstanceSpec {
            sop_instance_uid,
            series_instance_uid: &self.series_instance_uid,
            dimension_organization_uid: &self.dimension_organization_uid,
            frames,
            frame_offset,
            concatenation: concatenated.then_some(ConcatenationSpec {
                uid: &self.concatenation_uid,
                source_sop_instance_uid: &self.concatenation_source_sop_instance_uid,
                number,
                total,
            }),
            content_date: &self.content_date,
            content_time: &self.content_time,
        }
    }
}

#[derive(Debug)]
pub(super) struct PartDraft {
    pub(super) sop_instance_uid: String,
    pub(super) frame_offset: u32,
    pub(super) frame_count: u32,
}

pub(super) fn collect_keys(
    cursor: &mut OutputFrameCursor<'_>,
    count: u32,
) -> Result<Vec<FrameKey>> {
    let capacity = usize::try_from(count)
        .map_err(|_| Error::InvalidInput("PM frame count exceeds addressable memory".into()))?;
    let mut keys = Vec::with_capacity(capacity);
    while keys.len() < capacity {
        let frame = cursor.next()?.ok_or_else(|| {
            Error::InvalidInput("PM raster ended before its declared frame count".into())
        })?;
        keys.push(frame.key);
    }
    Ok(keys)
}