1use core::fmt;
2use std::{
3 collections::HashMap,
4 io::{BufReader, Read, Seek},
5 sync::{Arc, Mutex},
6};
7
8use image::ImageFormat;
9use nalgebra::Vector2;
10
11use crate::{
12 mcd::PanoramaXML, transform::AffineTransform, Acquisition, BoundingBox, OnSlide, OpticalImage,
13 Print,
14};
15
16#[derive(Debug)]
17pub enum PanoramaType {
18 Default,
19 Imported,
20 Instrument,
21}
22
23#[derive(Debug)]
25pub struct Panorama<R> {
26 pub(crate) reader: Option<Arc<Mutex<BufReader<R>>>>,
27
28 id: u16,
29 slide_id: u16,
30 description: String,
31 slide_x1_pos_um: f64,
32 slide_y1_pos_um: f64,
33 slide_x2_pos_um: f64,
34 slide_y2_pos_um: f64,
35 slide_x3_pos_um: f64,
36 slide_y3_pos_um: f64,
37 slide_x4_pos_um: f64,
38 slide_y4_pos_um: f64,
39
40 image_start_offset: i64,
41 image_end_offset: i64,
42 pixel_width: i64,
43 pixel_height: i64,
44 image_format: ImageFormat,
45 pixel_scale_coef: f64,
46
47 panorama_type: Option<PanoramaType>,
48 is_locked: Option<bool>,
49 rotation_angle: Option<f64>,
50
51 acquisitions: HashMap<u16, Acquisition<R>>,
52}
53
54impl<R: Read + Seek> Panorama<R> {
55 pub(crate) fn fix_image_dimensions(&mut self) {
56 if self.has_image() && (self.pixel_width == 0 || self.pixel_height == 0) {
57 let image = self.image().unwrap();
58 let dims = image.dimensions().unwrap();
59
60 self.pixel_width = dims.0 as i64;
61 self.pixel_height = dims.1 as i64;
62 }
63 }
64}
65
66impl<R> Panorama<R> {
67 pub fn id(&self) -> u16 {
69 self.id
70 }
71
72 pub fn slide_id(&self) -> u16 {
74 self.slide_id
75 }
76
77 pub fn description(&self) -> &str {
79 &self.description
80 }
81
82 pub fn dimensions(&self) -> (i64, i64) {
84 (self.pixel_width, self.pixel_height)
85 }
86
87 pub fn pixel_scale_coef(&self) -> f64 {
89 self.pixel_scale_coef
90 }
91
92 pub fn panorama_type(&self) -> Option<&PanoramaType> {
94 self.panorama_type.as_ref()
95 }
96
97 pub fn is_locked(&self) -> Option<bool> {
99 self.is_locked
100 }
101
102 pub fn rotation_angle(&self) -> Option<f64> {
104 self.rotation_angle
105 }
106
107 pub fn acquisition_ids(&self) -> Vec<u16> {
109 let mut ids: Vec<u16> = Vec::with_capacity(self.acquisitions.len());
110
111 for id in self.acquisitions.keys() {
112 ids.push(*id);
113 }
114
115 ids.sort_unstable();
116
117 ids
118 }
119
120 pub fn acquisition(&self, id: u16) -> Option<&Acquisition<R>> {
122 self.acquisitions.get(&id)
123 }
124
125 pub fn acquisitions(&self) -> Vec<&Acquisition<R>> {
127 let mut acquisitions = Vec::new();
128
129 let ids = self.acquisition_ids();
130 for id in ids {
131 acquisitions.push(
132 self.acquisition(id)
133 .expect("Should only be getting acquisitions that exist"),
134 );
135 }
136
137 acquisitions
138 }
139
140 pub(crate) fn acquisitions_mut(&mut self) -> &mut HashMap<u16, Acquisition<R>> {
141 &mut self.acquisitions
142 }
143
144 pub fn has_image(&self) -> bool {
146 (self.image_end_offset - self.image_start_offset) > 0
147 }
148
149 pub fn image(&self) -> Option<OpticalImage<R>> {
151 if self.has_image() {
152 Some(OpticalImage {
153 reader: self.reader.as_ref()?.clone(),
154 start_offset: self.image_start_offset,
155 end_offset: self.image_end_offset,
156 image_format: self.image_format,
157 })
158 } else {
159 None
160 }
161 }
162}
163
164impl<R> OnSlide for Panorama<R> {
196 fn slide_bounding_box(&self) -> BoundingBox<f64> {
198 let min_x = f64::min(
199 self.slide_x1_pos_um,
200 f64::min(
201 self.slide_x2_pos_um,
202 f64::min(self.slide_x3_pos_um, self.slide_x4_pos_um),
203 ),
204 );
205 let min_y = f64::min(
206 self.slide_y1_pos_um,
207 f64::min(
208 self.slide_y2_pos_um,
209 f64::min(self.slide_y3_pos_um, self.slide_y4_pos_um),
210 ),
211 );
212 let max_x = f64::max(
213 self.slide_x1_pos_um,
214 f64::max(
215 self.slide_x2_pos_um,
216 f64::max(self.slide_x3_pos_um, self.slide_x4_pos_um),
217 ),
218 );
219 let max_y = f64::max(
220 self.slide_y1_pos_um,
221 f64::max(
222 self.slide_y2_pos_um,
223 f64::max(self.slide_y3_pos_um, self.slide_y4_pos_um),
224 ),
225 );
226
227 BoundingBox {
228 min_x,
229 min_y,
230 width: (max_x - min_x).abs(),
231 height: (max_y - min_y).abs(),
232 }
233 }
234
235 fn to_slide_transform(&self) -> AffineTransform<f64> {
237 if !self.has_image() {
238 return AffineTransform::identity();
239 }
240
241 let mut moving_points = Vec::new();
242 let mut fixed_points = Vec::new();
243
244 moving_points.push(Vector2::new(self.slide_x1_pos_um, self.slide_y1_pos_um));
245 moving_points.push(Vector2::new(self.slide_x2_pos_um, self.slide_y2_pos_um));
246 moving_points.push(Vector2::new(self.slide_x3_pos_um, self.slide_y3_pos_um));
247 fixed_points.push(Vector2::new(0.0, self.pixel_height as f64));
255 fixed_points.push(Vector2::new(
256 self.pixel_width as f64,
257 self.pixel_height as f64,
258 ));
259 fixed_points.push(Vector2::new(self.pixel_width as f64, 0.0));
260 AffineTransform::from_points(moving_points, fixed_points)
263 }
264}
265
266#[rustfmt::skip]
267impl<R> Print for Panorama<R> {
268 fn print<W: fmt::Write + ?Sized>(&self, writer: &mut W, indent: usize) -> fmt::Result {
269 write!(writer, "{:indent$}", "", indent = indent)?;
270 writeln!(writer, "{:-^1$}", "Panorama", 42)?;
271
272 writeln!(writer, "{:indent$}{: <20} | {}", "", "ID", self.id, indent = indent)?;
273 writeln!(writer, "{:indent$}{: <20} | {}", "", "Slide ID", self.slide_id, indent = indent)?;
274 writeln!(
275 writer,
276 "{:indent$}{: <20} | {}",
277 "",
278 "Description",
279 self.description,
280 indent = indent
281 )?;
282 writeln!(
283 writer,
284 "{:indent$}{: <20} | ({:.4} μm, {:.4} μm)",
285 "",
286 "Slide coordinates",
287 self.slide_x1_pos_um,
288 self.slide_y1_pos_um,
289 indent = indent
290 )?;
291 writeln!(
292 writer,
293 "{:indent$}{: <20} | ({:.4} μm, {:.4} μm)",
294 "",
295 "",
296 self.slide_x2_pos_um,
297 self.slide_y2_pos_um,
298 indent = indent
299 )?;
300 writeln!(
301 writer,
302 "{:indent$}{: <20} | ({:.4} μm, {:.4} μm)",
303 "",
304 "",
305 self.slide_x3_pos_um,
306 self.slide_y3_pos_um,
307 indent = indent
308 )?;
309 writeln!(
310 writer,
311 "{:indent$}{: <20} | ({:.4} μm, {:.4} μm)",
312 "",
313 "",
314 self.slide_x4_pos_um,
315 self.slide_y4_pos_um,
316 indent = indent
317 )?;
318 writeln!(
319 writer,
320 "{:indent$}{: <20} | {} x {}",
321 "",
322 "Dimensions (pixels)",
323 self.pixel_width,
324 self.pixel_height,
325 indent = indent
326 )?;
327 writeln!(
328 writer,
329 "{:indent$}{: <20} | {}",
330 "",
331 "Pixel scale coef",
332 self.pixel_scale_coef,
333 indent = indent
334 )?;
335
336 write!(writer, "{:indent$}", "", indent = indent)?;
337 writeln!(writer, "{:-^1$}", "", 42)?;
338
339 writeln!(
340 writer,
341 "{} acquisition(s) with ids: {:?}",
342 self.acquisitions.len(),
343 self.acquisition_ids()
344 )?;
345 write!(writer, "{:indent$}", "", indent = indent)?;
346 writeln!(writer, "{:-^1$}", "", 42)?;
347
348 Ok(())
349 }
350}
351
352impl<R> fmt::Display for Panorama<R> {
353 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354 self.print(f, 0)
355 }
356}
357
358impl<R> From<PanoramaXML> for Panorama<R> {
359 fn from(panorama: PanoramaXML) -> Self {
360 Panorama {
361 reader: None,
362
363 id: panorama.id.unwrap(),
364 slide_id: panorama.slide_id.unwrap(),
365 description: panorama.description.unwrap(),
366 slide_x1_pos_um: panorama.slide_x1_pos_um.unwrap(),
367 slide_y1_pos_um: panorama.slide_y1_pos_um.unwrap(),
368 slide_x2_pos_um: panorama.slide_x2_pos_um.unwrap(),
369 slide_y2_pos_um: panorama.slide_y2_pos_um.unwrap(),
370 slide_x3_pos_um: panorama.slide_x3_pos_um.unwrap(),
371 slide_y3_pos_um: panorama.slide_y3_pos_um.unwrap(),
372 slide_x4_pos_um: panorama.slide_x4_pos_um.unwrap(),
373 slide_y4_pos_um: panorama.slide_y4_pos_um.unwrap(),
374 image_start_offset: panorama.image_start_offset.unwrap(),
375 image_end_offset: panorama.image_end_offset.unwrap(),
376 pixel_width: panorama.pixel_width.unwrap(),
377 pixel_height: panorama.pixel_height.unwrap(),
378 image_format: panorama.image_format.unwrap(),
379 pixel_scale_coef: panorama.pixel_scale_coef.unwrap(),
380
381 panorama_type: panorama.panorama_type,
382 is_locked: panorama.is_locked,
383 rotation_angle: panorama.rotation_angle,
384
385 acquisitions: HashMap::new(),
386 }
387 }
388}