imc_rs/
panorama.rs

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/// Represents a panorama (containing one or more acquisitions)
24#[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    /// Returns the panorama ID
68    pub fn id(&self) -> u16 {
69        self.id
70    }
71
72    /// Returns the slide ID to which this panorama belongs
73    pub fn slide_id(&self) -> u16 {
74        self.slide_id
75    }
76
77    /// Returns the given description for the panorama
78    pub fn description(&self) -> &str {
79        &self.description
80    }
81
82    /// Return the dimensions in pixels (width, height) of the panorama image
83    pub fn dimensions(&self) -> (i64, i64) {
84        (self.pixel_width, self.pixel_height)
85    }
86
87    /// Returns a scaling coefficient for pixel sizes
88    pub fn pixel_scale_coef(&self) -> f64 {
89        self.pixel_scale_coef
90    }
91
92    /// Returns the type of the panorama image, if known. This is unknown in the first version of the schema
93    pub fn panorama_type(&self) -> Option<&PanoramaType> {
94        self.panorama_type.as_ref()
95    }
96
97    /// Returns whether the panorama is locked or not (if known). This is unknown in the first version of the schema
98    pub fn is_locked(&self) -> Option<bool> {
99        self.is_locked
100    }
101
102    /// Returns the rotation angle of the panorama (if known). This is unknown in the first version of the schema
103    pub fn rotation_angle(&self) -> Option<f64> {
104        self.rotation_angle
105    }
106
107    /// Returns a sorted (acsending) list of acquisition IDs
108    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    /// Returns an acquisition with the supplied ID, or None if none exists
121    pub fn acquisition(&self, id: u16) -> Option<&Acquisition<R>> {
122        self.acquisitions.get(&id)
123    }
124
125    /// Returns a vector of acquisition references ordered by acquistion ID number
126    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    /// Returns true if an image is associated with this panorama
145    pub fn has_image(&self) -> bool {
146        (self.image_end_offset - self.image_start_offset) > 0
147    }
148
149    /// Returns the optical image
150    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
164/*
165impl<T: Seek + Read> OpticalImage for Panorama<T> {
166    fn has_image(&self) -> bool {
167        (self.image_end_offset - self.image_start_offset) > 0
168    }
169
170    /// Returns the format that the panorama image is stored in
171    fn image_format(&self) -> ImageFormat {
172        self.image_format
173    }
174
175    /// Returns the binary data for the image, exactly as stored in the .mcd file
176    fn image_data(&self) -> Result<Vec<u8>, std::io::Error> {
177        let mutex = self
178            .reader
179            .as_ref()
180            .expect("Should have copied the reader across");
181        let reader = mutex.lock().unwrap();
182
183        read_image_data(reader, self.image_start_offset, self.image_end_offset)
184    }
185
186    /// Returns a decoded RgbaImage of the panorama image
187    fn image(&self) -> RgbaImage {
188        match self.dynamic_image() {
189            DynamicImage::ImageRgba8(rgba8) => rgba8,
190            _ => panic!("Unexpected DynamicImage type"),
191        }
192    }
193} */
194
195impl<R> OnSlide for Panorama<R> {
196    /// Returns the bounding box encompasing the panorama image area on the slide (in μm)
197    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    /// Returns the affine transformation from pixel coordinates within the panorama to to the slide coordinates (μm)
236    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        //moving_points.push(Vector2::new(self.slide_x4_pos_um, self.slide_y4_pos_um));
248
249        // println!(
250        //     "slide {} {} {}",
251        //     self.slide_y1_pos_um, self.slide_y2_pos_um, self.slide_y3_pos_um
252        // );
253
254        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        //fixed_points.push(Vector2::new(0.0, self.pixel_height as f64));
261
262        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}