wsi_dicom_annotations/annotations/parametric_map/
plan.rs1use 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}