imc_rs/
slide.rs

1use core::fmt;
2use std::{
3    collections::HashMap,
4    io::{BufReader, Read, Seek},
5    sync::{Arc, Mutex},
6};
7
8use image::Pixel;
9use image::{imageops::FilterType, ImageBuffer, ImageFormat, Rgba, RgbaImage};
10
11use crate::{
12    channel::ChannelIdentifier,
13    error::MCDError,
14    mcd::{SlideFiducialMarksXML, SlideProfileXML},
15    OnSlide, OpticalImage, Panorama, Print,
16};
17
18use crate::mcd::SlideXML;
19
20/// Represents a slide (contains multiple panoramas) in the *.mcd format
21#[derive(Debug)]
22pub struct Slide<R> {
23    pub(crate) reader: Option<Arc<Mutex<BufReader<R>>>>,
24
25    id: u16,
26    // The newer version of the XSD doesn't have a UID field anymore
27    uid: Option<String>,
28    description: String,
29    filename: String,
30    slide_type: String,
31    width_um: f64,
32    height_um: f64,
33
34    image_start_offset: i64,
35    image_end_offset: i64,
36    image_file: String,
37
38    // New terms in version 2 of the XSD are included as optional
39    energy_db: Option<u32>,
40    frequency: Option<u32>,
41    fmark_slide_length: Option<u64>,
42    fmark_slide_thickness: Option<u64>,
43    name: Option<String>,
44
45    sw_version: String,
46
47    panoramas: HashMap<u16, Panorama<R>>,
48}
49
50impl<R> From<SlideXML> for Slide<R> {
51    fn from(slide: SlideXML) -> Self {
52        Slide {
53            reader: None,
54
55            id: slide.id.unwrap(),
56            uid: slide.uid,
57            description: slide.description.unwrap(),
58            filename: slide.filename.unwrap(),
59            slide_type: slide.slide_type.unwrap(),
60            width_um: slide.width_um.unwrap(),
61            height_um: slide.height_um.unwrap(),
62            image_start_offset: slide.image_start_offset.unwrap(),
63            image_end_offset: slide.image_end_offset.unwrap(),
64            image_file: slide.image_file.unwrap(),
65            sw_version: slide.sw_version.unwrap(),
66
67            energy_db: slide.energy_db,
68            frequency: slide.frequency,
69            fmark_slide_length: slide.fmark_slide_length,
70            fmark_slide_thickness: slide.fmark_slide_thickness,
71            name: slide.name,
72
73            panoramas: HashMap::new(),
74        }
75    }
76}
77
78impl<R: Read + Seek> Slide<R> {
79    /// Create an overview image of the slide scaled to the supplied width.
80    ///
81    /// This will scale the slide image to the supplied width, and overlay any panorama images acquired.
82    /// If an `channel_to_show` is supplied, then the selected channel (`ChannelIdentifier`) will be
83    /// overlayed with the data clipped at the specified maximum value (f32).
84    pub fn create_overview_image(
85        &self,
86        width: u32,
87        channel_to_show: Option<(&ChannelIdentifier, Option<f32>)>,
88    ) -> Result<RgbaImage, MCDError> {
89        let slide_image = self.image().dynamic_image().unwrap();
90
91        // Move into function to help debugging
92        /*match &slide_image {
93            DynamicImage::ImageLuma8(_grey_image) => println!("ImageLuma8"),
94            DynamicImage::ImageLumaA8(_grey_alpha_image) => println!("ImageLumaA8"),
95            DynamicImage::ImageRgb8(_rgb8) => println!("ImageRgb8"),
96            DynamicImage::ImageRgba8(_rgba8) => println!("ImageRgba8"),
97            DynamicImage::ImageBgr8(_bgr8) => println!("ImageBgr8"),
98            DynamicImage::ImageBgra8(_bgra8) => println!("ImageBgra8"),
99            DynamicImage::ImageLuma16(_luma16) => println!("ImageLuma16"),
100            DynamicImage::ImageLumaA16(_lumaa16) => println!("ImageLumaA16"),
101            DynamicImage::ImageRgb16(_rgb16) => println!("ImageRgb16"),
102            DynamicImage::ImageRgba16(_rgba16) => println!("ImageRgba16"),
103        }
104
105        println!("Decoded !");
106
107        slide_image.save("slide.jpeg").unwrap();
108
109        println!("Saved !");*/
110
111        let ratio = self.height_in_um() / self.width_in_um();
112        let output_image_height = (width as f64 * ratio) as u32;
113
114        let mut resized_image = slide_image
115            .resize_exact(width, width / 3, FilterType::Nearest)
116            .to_rgba8();
117        //println!("Resized !");
118
119        //return Ok(slide_image.to_rgba8());
120
121        let scale = self.width_in_um() / width as f64;
122
123        for panorama in self.panoramas() {
124            if panorama.has_image() {
125                let panorama_image = panorama.image().unwrap().as_rgba8().unwrap();
126
127                //let panorama_image = panorama_image.to_rgba8();
128
129                //let bounding_box = panorama.slide_bounding_box();
130                let transform = panorama.to_slide_transform();
131
132                //println!("[Panorama] Bounding box = {:?}", bounding_box);
133                //println!("[Panorama] Transform = {:?}", transform);
134
135                let (width, height) = panorama.dimensions();
136
137                // Transform each coordinate
138                let top_left = transform.transform_to_slide(0.0, 0.0).unwrap();
139                let top_right = transform.transform_to_slide(width as f64, 0.0).unwrap();
140                let bottom_left = transform.transform_to_slide(0.0, height as f64).unwrap();
141                let bottom_right = transform
142                    .transform_to_slide(width as f64, height as f64)
143                    .unwrap();
144
145                let min_x = top_left[0].min(top_right[0].min(bottom_left[0].min(bottom_right[0])));
146                let min_y = top_left[1].min(top_right[1].min(bottom_left[1].min(bottom_right[1])));
147                let max_x = top_left[0].max(top_right[0].max(bottom_left[0].max(bottom_right[0])));
148                let max_y = top_left[1].max(top_right[1].max(bottom_left[1].max(bottom_right[1])));
149
150                let min_x_pixel = (min_x / scale).floor() as u32;
151                let max_x_pixel = (max_x / scale).floor() as u32;
152                let min_y_pixel = (min_y / scale).floor() as u32;
153                let max_y_pixel = (max_y / scale).floor() as u32;
154
155                for y in min_y_pixel..max_y_pixel {
156                    for x in min_x_pixel..max_x_pixel {
157                        let new_point = transform
158                            .transform_from_slide(x as f64 * scale, y as f64 * scale)
159                            .unwrap();
160
161                        let pixel_x = new_point[0].round() as i32;
162                        let pixel_y = panorama_image.height() as i32 - new_point[1].round() as i32;
163
164                        if pixel_x < 0
165                            || pixel_y < 0
166                            || pixel_x >= width as i32
167                            || pixel_y >= height as i32
168                        {
169                            continue;
170                        }
171
172                        let pixel = *panorama_image.get_pixel(pixel_x as u32, pixel_y as u32);
173
174                        resized_image.put_pixel(x, output_image_height - y, pixel);
175                    }
176                }
177
178                /*for y in 0..panorama_image.height() {
179                    for x in 0..panorama_image.width() {
180                        let new_point = transform.transform_to_slide(x as f64, y as f64).unwrap();
181
182                        let pixel = *panorama_image.get_pixel(x, y);
183
184                        resized_image.put_pixel(
185                            (new_point[0] / scale).round() as u32,
186                            ((self.height_um - new_point[1]) / scale).round() as u32,
187                            pixel,
188                        );
189                    }
190                }*/
191            }
192
193            if let Some((identifier, max_value)) = channel_to_show {
194                for acquisition in panorama.acquisitions() {
195                    //println!("[Acquisition] Bounding box = {:?}", bounding_box);
196                    //println!("[Acquisition] Transform = {:?}", transform);
197
198                    //let bounding_box = acquisition.slide_bounding_box();
199                    let transform = acquisition.to_slide_transform();
200                    let data = acquisition.channel_image(identifier, None)?;
201
202                    let max_value = match max_value {
203                        Some(value) => value,
204                        None => data.range.1,
205                    };
206
207                    let mut index = 0;
208
209                    let mut acq_image: ImageBuffer<Rgba<u8>, Vec<u8>> =
210                        ImageBuffer::new(data.width(), data.height());
211
212                    for y in 0..data.height() {
213                        if index >= data.valid_pixels {
214                            break;
215                        }
216
217                        for x in 0..data.width() {
218                            if index >= data.valid_pixels {
219                                break;
220                            }
221
222                            let new_point =
223                                transform.transform_to_slide(x as f64, y as f64).unwrap();
224
225                            let g = ((data.data[index] / max_value) * 255.0) as u8;
226                            let g = g as f64 / 255.0;
227
228                            let cur_pixel =
229                                acq_image.get_pixel_mut(x as u32, y as u32).channels_mut();
230                            cur_pixel[1] = (g * 255.0) as u8;
231                            cur_pixel[3] = 255;
232
233                            //let pixel = Rgba::from_channels(0, g, 0, g);
234
235                            let current_pixel = resized_image
236                                .get_pixel_mut(
237                                    (new_point[0] / scale).round() as u32,
238                                    ((new_point[1]) / scale).round() as u32,
239                                )
240                                .channels_mut();
241
242                            let r = (current_pixel[0] as f64 / 255.0) * (1.0 - g);
243                            let g = g * g + (current_pixel[1] as f64 / 255.0) * (1.0 - g);
244                            let b = (current_pixel[2] as f64 / 255.0) * (1.0 - g);
245
246                            current_pixel[0] = (r * 255.0) as u8;
247                            current_pixel[1] = (g * 255.0) as u8;
248                            current_pixel[2] = (b * 255.0) as u8;
249
250                            index += 1;
251                        }
252                    }
253                }
254
255                //                acq_image
256                //                    .save(format!("{}_Ir(191).png", acquisition.description()))
257                //                    .unwrap();
258
259                //                println!("Finished reading data");
260            }
261        }
262
263        Ok(resized_image)
264    }
265}
266
267impl<R> Slide<R> {
268    /// Returns the slide ID
269    pub fn id(&self) -> u16 {
270        self.id
271    }
272
273    /// Returns the slide UID
274    pub fn uid(&self) -> Option<&str> {
275        match &self.uid {
276            Some(uid) => Some(uid),
277            None => None,
278        }
279    }
280
281    /// Returns the description given to the slide
282    pub fn description(&self) -> &str {
283        &self.description
284    }
285
286    /// Returns the width of the slide in μm
287    pub fn width_in_um(&self) -> f64 {
288        self.width_um
289    }
290
291    /// Returns the height of the slide in μm
292    pub fn height_in_um(&self) -> f64 {
293        self.height_um
294    }
295
296    /// Returns the *.mcd filename
297    pub fn filename(&self) -> &str {
298        &self.filename
299    }
300
301    /// Returns the name of the image file used as a slide image
302    pub fn image_file(&self) -> &str {
303        &self.image_file
304    }
305
306    /// Returns the version of the software used to produce this *.mcd file
307    pub fn software_version(&self) -> &str {
308        &self.sw_version
309    }
310
311    /// Returns the energy in Db
312    pub fn energy_db(&self) -> Option<u32> {
313        self.energy_db
314    }
315
316    /// Returns the frequency
317    pub fn frequency(&self) -> Option<u32> {
318        self.frequency
319    }
320
321    /// Returns the fmark slide length
322    pub fn fmark_slide_length(&self) -> Option<u64> {
323        self.fmark_slide_length
324    }
325
326    /// Returns the fmark slide thickness
327    pub fn fmark_slide_thickness(&self) -> Option<u64> {
328        self.fmark_slide_thickness
329    }
330
331    /// Returns the name given to the slide
332    pub fn name(&self) -> Option<&str> {
333        self.name.as_deref()
334    }
335
336    /// Returns associated image data
337    // pub fn image_data(&self) -> Result<Vec<u8>, std::io::Error> {
338    //     let mutex = self
339    //         .reader
340    //         .as_ref()
341    //         .expect("Should have copied the reader across");
342    //     let reader = mutex.lock().unwrap();
343
344    //     read_image_data(reader, self.image_start_offset, self.image_end_offset)
345    // }
346
347    /// Returns the format describing the binary image data
348    fn image_format(&self) -> ImageFormat {
349        if self.software_version().starts_with('6') {
350            ImageFormat::Jpeg
351        } else {
352            ImageFormat::Png
353        }
354    }
355
356    /// Returns the image associated with the slide
357    pub fn image(&self) -> OpticalImage<R> {
358        OpticalImage {
359            reader: self.reader.as_ref().unwrap().clone(),
360            start_offset: self.image_start_offset,
361            end_offset: self.image_end_offset,
362            image_format: self.image_format(),
363        }
364    }
365
366    // fn dynamic_image(&self) -> DynamicImage {
367    //     let mut reader = ImageReader::new(Cursor::new(self.image_data().unwrap()));
368    //     reader.set_format(self.image_format());
369    //     reader.decode().unwrap()
370    // }
371
372    // /// Returns the image associated with the slide.
373    // pub fn image(&self) -> RgbImage {
374    //     match self.dynamic_image() {
375    //         DynamicImage::ImageRgb8(rgb8) => rgb8,
376    //         _ => panic!("Unexpected DynamicImage type"),
377    //     }
378    // }
379
380    /// Returns a vector of panorama ids sorted by ID number. This allocates a new vector on each call.
381    pub fn panorama_ids(&self) -> Vec<u16> {
382        let mut ids: Vec<u16> = Vec::with_capacity(self.panoramas.len());
383
384        for id in self.panoramas.keys() {
385            ids.push(*id);
386        }
387
388        ids.sort_unstable();
389
390        ids
391    }
392
393    /// Returns panorama with a given ID number, or `None` if no such panorama exists
394    pub fn panorama(&self, id: u16) -> Option<&Panorama<R>> {
395        self.panoramas.get(&id)
396    }
397
398    /// Returns a vector of references to panoramas sorted by ID number. This allocates a new vector on each call.
399    pub fn panoramas(&self) -> Vec<&Panorama<R>> {
400        let mut panoramas = Vec::new();
401
402        let ids = self.panorama_ids();
403        for id in ids {
404            panoramas.push(
405                self.panorama(id)
406                    .expect("Should only be getting panoramas that exist"),
407            );
408        }
409
410        panoramas
411    }
412
413    pub(crate) fn panoramas_mut(&mut self) -> &mut HashMap<u16, Panorama<R>> {
414        &mut self.panoramas
415    }
416}
417
418#[rustfmt::skip]
419impl<R> Print for Slide<R> {
420    fn print<W: fmt::Write + ?Sized>(&self, writer: &mut W, indent: usize) -> fmt::Result {
421        write!(writer, "{:indent$}", "", indent = indent)?;
422        writeln!(writer, "{:-^1$}", "Slide", 36)?;
423        writeln!(
424            writer,
425            "{:indent$}{: <16} | {}",
426            "",
427            "ID",
428            self.id,
429            indent = indent
430        )?;
431
432        if let Some(uid) = &self.uid {
433                writeln!(
434                    writer,
435                    "{:indent$}{: <16} | {}",
436                    "",
437                    "UID",
438                    uid,
439                    indent = indent
440                )?;
441        }
442
443        writeln!(
444            writer,
445            "{:indent$}{: <16} | {}",
446            "",
447            "Description",
448            self.description,
449            indent = indent
450        )?;
451        writeln!(
452            writer,
453            "{:indent$}{: <16} | {}",
454            "",
455            "Filename",
456            self.filename,
457            indent = indent
458        )?;
459        writeln!(
460            writer,
461            "{:indent$}{: <16} | {}",
462            "",
463            "Type",
464            self.slide_type,
465            indent = indent
466        )?;
467        writeln!(
468            writer,
469            "{:indent$}{: <16} | {} μm x {} μm ",
470            "",
471            "Dimensions",
472            self.width_um,
473            self.height_um,
474            indent = indent
475        )?;
476        writeln!(
477            writer,
478            "{:indent$}{: <16} | {}",
479            "",
480            "Image File",
481            self.image_file,
482            indent = indent
483        )?;
484        writeln!(
485            writer,
486            "{:indent$}{: <16} | {}",
487            "",
488            "Software Version",
489            self.sw_version,
490            indent = indent
491        )?;
492
493        write!(writer, "{:indent$}", "", indent = indent)?;
494        writeln!(writer, "{:-^1$}", "", 36)?;
495
496        writeln!(
497            writer,
498            "{:indent$}{} panorama(s) with ids: {:?}",
499            "",
500            self.panoramas.len(),
501            self.panorama_ids(),
502            indent = indent + 1
503        )?;
504        write!(writer, "{:indent$}", "", indent = indent)?;
505        writeln!(writer, "{:-^1$}", "", 36)?;
506
507        Ok(())
508    }
509}
510
511impl<R> fmt::Display for Slide<R> {
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        self.print(f, 0)
514    }
515}
516
517#[derive(Debug)]
518pub struct SlideFiducialMarks {
519    id: u16,
520    slide_id: u16,
521    coordinate_x: u32,
522    coordinate_y: u32,
523}
524
525impl SlideFiducialMarks {
526    pub fn id(&self) -> u16 {
527        self.id
528    }
529    pub fn slide_id(&self) -> u16 {
530        self.slide_id
531    }
532    pub fn coordinate_x(&self) -> u32 {
533        self.coordinate_x
534    }
535    pub fn coordinate_y(&self) -> u32 {
536        self.coordinate_y
537    }
538}
539
540impl From<SlideFiducialMarksXML> for SlideFiducialMarks {
541    fn from(fiducial_marks: SlideFiducialMarksXML) -> Self {
542        SlideFiducialMarks {
543            id: fiducial_marks.id.unwrap(),
544            slide_id: fiducial_marks.slide_id.unwrap(),
545            coordinate_x: fiducial_marks.coordinate_x.unwrap(),
546            coordinate_y: fiducial_marks.coordinate_y.unwrap(),
547        }
548    }
549}
550
551#[derive(Debug)]
552pub struct SlideProfile {
553    id: u16,
554    slide_id: u16,
555    coordinate_x: u32,
556    coordinate_y: u32,
557}
558
559impl SlideProfile {
560    /// Returns the ID of the slide profile
561    pub fn id(&self) -> u16 {
562        self.id
563    }
564
565    pub fn slide_id(&self) -> u16 {
566        self.slide_id
567    }
568    pub fn coordinate_x(&self) -> u32 {
569        self.coordinate_x
570    }
571    pub fn coordinate_y(&self) -> u32 {
572        self.coordinate_y
573    }
574}
575
576impl From<SlideProfileXML> for SlideProfile {
577    fn from(profile: SlideProfileXML) -> Self {
578        SlideProfile {
579            id: profile.id.unwrap(),
580            slide_id: profile.slide_id.unwrap(),
581            coordinate_x: profile.coordinate_x.unwrap(),
582            coordinate_y: profile.coordinate_y.unwrap(),
583        }
584    }
585}