Skip to main content

wsi_dicom_annotations/annotations/parametric_map/
plan.rs

1use crate::annotations::dicom_dataset::{dicom_now, new_dicom_uid};
2use crate::{Error, Result};
3
4use super::document::OutputFrameCursor;
5use super::frame::FrameKey;
6use super::writer::{ConcatenationSpec, InstanceSpec};
7
8#[derive(Debug, Clone)]
9pub struct ParametricMapPlan {
10    pub(super) semantic_digest: String,
11    pub(super) series_instance_uid: String,
12    pub(super) dimension_organization_uid: String,
13    pub(super) concatenation_uid: Option<String>,
14    pub(super) concatenation_source_sop_instance_uid: Option<String>,
15    pub(super) content_date: String,
16    pub(super) content_time: String,
17    pub(super) parts: Vec<ParametricMapPartPlan>,
18}
19
20#[derive(Debug, Clone)]
21pub struct ParametricMapPartPlan {
22    pub(super) sop_instance_uid: String,
23    pub(super) frame_offset: u32,
24    pub(super) frame_count: u32,
25    pub(super) pixel_value_length: u32,
26    pub(super) encoded_file_length: u64,
27    pub(super) pixel_sha256: String,
28}
29
30#[derive(Debug, Clone)]
31pub struct ParametricMapInstance {
32    sop_instance_uid: String,
33    series_instance_uid: String,
34    frame_offset: u32,
35    frame_count: u32,
36    pixel_value_length: u32,
37    encoded_file_length: u64,
38    pixel_sha256: String,
39}
40
41impl ParametricMapPlan {
42    #[must_use]
43    pub fn parts(&self) -> &[ParametricMapPartPlan] {
44        &self.parts
45    }
46
47    #[must_use]
48    pub fn series_instance_uid(&self) -> &str {
49        &self.series_instance_uid
50    }
51
52    #[must_use]
53    pub fn concatenation_uid(&self) -> Option<&str> {
54        self.concatenation_uid.as_deref()
55    }
56
57    #[must_use]
58    pub fn concatenation_source_sop_instance_uid(&self) -> Option<&str> {
59        self.concatenation_source_sop_instance_uid.as_deref()
60    }
61
62    pub(super) fn instance_spec<'a>(
63        &'a self,
64        part: &'a ParametricMapPartPlan,
65        frames: &'a [FrameKey],
66        number: u16,
67        total: u16,
68    ) -> Result<InstanceSpec<'a>> {
69        let concatenation = match (
70            self.concatenation_uid.as_deref(),
71            self.concatenation_source_sop_instance_uid.as_deref(),
72        ) {
73            (Some(uid), Some(source_sop_instance_uid)) => Some(ConcatenationSpec {
74                uid,
75                source_sop_instance_uid,
76                number,
77                total,
78            }),
79            (None, None) => None,
80            _ => {
81                return Err(Error::InvalidInput(
82                    "PM plan has incomplete concatenation identity".into(),
83                ))
84            }
85        };
86        Ok(InstanceSpec {
87            sop_instance_uid: &part.sop_instance_uid,
88            series_instance_uid: &self.series_instance_uid,
89            dimension_organization_uid: &self.dimension_organization_uid,
90            frames,
91            frame_offset: part.frame_offset,
92            concatenation,
93            content_date: &self.content_date,
94            content_time: &self.content_time,
95        })
96    }
97}
98
99impl ParametricMapPartPlan {
100    #[must_use]
101    pub fn sop_instance_uid(&self) -> &str {
102        &self.sop_instance_uid
103    }
104
105    #[must_use]
106    pub const fn frame_offset(&self) -> u32 {
107        self.frame_offset
108    }
109
110    #[must_use]
111    pub const fn frame_count(&self) -> u32 {
112        self.frame_count
113    }
114
115    #[must_use]
116    pub const fn pixel_value_length(&self) -> u32 {
117        self.pixel_value_length
118    }
119
120    #[must_use]
121    pub const fn encoded_file_length(&self) -> u64 {
122        self.encoded_file_length
123    }
124
125    #[must_use]
126    pub fn pixel_sha256(&self) -> &str {
127        &self.pixel_sha256
128    }
129}
130
131impl ParametricMapInstance {
132    pub(super) fn from_part(part: &ParametricMapPartPlan, series_instance_uid: &str) -> Self {
133        Self {
134            sop_instance_uid: part.sop_instance_uid.clone(),
135            series_instance_uid: series_instance_uid.to_string(),
136            frame_offset: part.frame_offset,
137            frame_count: part.frame_count,
138            pixel_value_length: part.pixel_value_length,
139            encoded_file_length: part.encoded_file_length,
140            pixel_sha256: part.pixel_sha256.clone(),
141        }
142    }
143
144    #[must_use]
145    pub fn sop_instance_uid(&self) -> &str {
146        &self.sop_instance_uid
147    }
148
149    #[must_use]
150    pub fn series_instance_uid(&self) -> &str {
151        &self.series_instance_uid
152    }
153
154    #[must_use]
155    pub const fn frame_offset(&self) -> u32 {
156        self.frame_offset
157    }
158
159    #[must_use]
160    pub const fn frame_count(&self) -> u32 {
161        self.frame_count
162    }
163
164    #[must_use]
165    pub const fn pixel_value_length(&self) -> u32 {
166        self.pixel_value_length
167    }
168
169    #[must_use]
170    pub const fn encoded_file_length(&self) -> u64 {
171        self.encoded_file_length
172    }
173
174    #[must_use]
175    pub fn pixel_sha256(&self) -> &str {
176        &self.pixel_sha256
177    }
178}
179
180pub(super) struct PlanIdentifiers {
181    pub(super) series_instance_uid: String,
182    pub(super) dimension_organization_uid: String,
183    pub(super) concatenation_uid: String,
184    pub(super) concatenation_source_sop_instance_uid: String,
185    pub(super) content_date: String,
186    pub(super) content_time: String,
187}
188
189impl PlanIdentifiers {
190    pub(super) fn new() -> Self {
191        let (content_date, content_time) = dicom_now();
192        Self {
193            series_instance_uid: new_dicom_uid(),
194            dimension_organization_uid: new_dicom_uid(),
195            concatenation_uid: new_dicom_uid(),
196            concatenation_source_sop_instance_uid: new_dicom_uid(),
197            content_date,
198            content_time,
199        }
200    }
201
202    pub(super) fn instance_spec<'a>(
203        &'a self,
204        sop_instance_uid: &'a str,
205        frames: &'a [FrameKey],
206        frame_offset: u32,
207        number: u16,
208        total: u16,
209        concatenated: bool,
210    ) -> InstanceSpec<'a> {
211        InstanceSpec {
212            sop_instance_uid,
213            series_instance_uid: &self.series_instance_uid,
214            dimension_organization_uid: &self.dimension_organization_uid,
215            frames,
216            frame_offset,
217            concatenation: concatenated.then_some(ConcatenationSpec {
218                uid: &self.concatenation_uid,
219                source_sop_instance_uid: &self.concatenation_source_sop_instance_uid,
220                number,
221                total,
222            }),
223            content_date: &self.content_date,
224            content_time: &self.content_time,
225        }
226    }
227}
228
229#[derive(Debug)]
230pub(super) struct PartDraft {
231    pub(super) sop_instance_uid: String,
232    pub(super) frame_offset: u32,
233    pub(super) frame_count: u32,
234}
235
236pub(super) fn collect_keys(
237    cursor: &mut OutputFrameCursor<'_>,
238    count: u32,
239) -> Result<Vec<FrameKey>> {
240    let capacity = usize::try_from(count)
241        .map_err(|_| Error::InvalidInput("PM frame count exceeds addressable memory".into()))?;
242    let mut keys = Vec::with_capacity(capacity);
243    while keys.len() < capacity {
244        let frame = cursor.next()?.ok_or_else(|| {
245            Error::InvalidInput("PM raster ended before its declared frame count".into())
246        })?;
247        keys.push(frame.key);
248    }
249    Ok(keys)
250}