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#[derive(Debug)]
22pub struct Slide<R> {
23 pub(crate) reader: Option<Arc<Mutex<BufReader<R>>>>,
24
25 id: u16,
26 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 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 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 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 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 transform = panorama.to_slide_transform();
131
132 let (width, height) = panorama.dimensions();
136
137 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 }
192
193 if let Some((identifier, max_value)) = channel_to_show {
194 for acquisition in panorama.acquisitions() {
195 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 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 }
261 }
262
263 Ok(resized_image)
264 }
265}
266
267impl<R> Slide<R> {
268 pub fn id(&self) -> u16 {
270 self.id
271 }
272
273 pub fn uid(&self) -> Option<&str> {
275 match &self.uid {
276 Some(uid) => Some(uid),
277 None => None,
278 }
279 }
280
281 pub fn description(&self) -> &str {
283 &self.description
284 }
285
286 pub fn width_in_um(&self) -> f64 {
288 self.width_um
289 }
290
291 pub fn height_in_um(&self) -> f64 {
293 self.height_um
294 }
295
296 pub fn filename(&self) -> &str {
298 &self.filename
299 }
300
301 pub fn image_file(&self) -> &str {
303 &self.image_file
304 }
305
306 pub fn software_version(&self) -> &str {
308 &self.sw_version
309 }
310
311 pub fn energy_db(&self) -> Option<u32> {
313 self.energy_db
314 }
315
316 pub fn frequency(&self) -> Option<u32> {
318 self.frequency
319 }
320
321 pub fn fmark_slide_length(&self) -> Option<u64> {
323 self.fmark_slide_length
324 }
325
326 pub fn fmark_slide_thickness(&self) -> Option<u64> {
328 self.fmark_slide_thickness
329 }
330
331 pub fn name(&self) -> Option<&str> {
333 self.name.as_deref()
334 }
335
336 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 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 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 pub fn panorama(&self, id: u16) -> Option<&Panorama<R>> {
395 self.panoramas.get(&id)
396 }
397
398 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 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}