imc_rs/lib.rs
1#![warn(missing_docs)]
2#![warn(clippy::unwrap_used)]
3
4//! This library provides a means of accessing imaging mass cytometry (Fluidigm) data stored in the (*.mcd) format.
5//!
6//! # Example
7//!
8//! To run this example, make sure to first download the test file [20200612_FLU_1923.mcd](https://zenodo.org/record/4110560/files/data/20200612_FLU_1923/20200612_FLU_1923.mcd?download=1) to the `test/` folder.
9//!
10//! ```
11//! extern crate imc_rs;
12//!
13//! use imc_rs::MCD;
14//! use std::io::BufReader;
15//! use std::fs::File;
16//!
17//! fn main() {
18//! let filename = "../test/20200612_FLU_1923.mcd";
19//!
20//! let mcd = MCD::from_path(filename).unwrap();
21//!
22//! // Optionally we can load/create the .dcm file for fast access to images
23//! let mcd = mcd.with_dcm().unwrap();
24//!
25//! }
26//! ```
27
28/// Convert .mcd file to .dcm file for faster access to data.
29pub mod convert;
30/// Errors associated with parsing IMC data
31pub mod error;
32pub(crate) mod mcd;
33/// Transformations (e.g. affine) used for converting
34pub mod transform;
35
36mod acquisition;
37mod calibration;
38mod channel;
39mod panorama;
40mod slide;
41
42/// Provides methods for reading in cell segmentation data from Halo
43pub mod halo;
44
45pub use self::acquisition::{Acquisition, AcquisitionIdentifier, Acquisitions};
46pub use self::channel::{AcquisitionChannel, ChannelIdentifier};
47pub use self::panorama::Panorama;
48pub use self::slide::Slide;
49
50use error::{MCDError, Result};
51use image::io::Reader as ImageReader;
52use std::convert::TryInto;
53use std::fmt;
54use std::fs::File;
55use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom};
56
57use std::ops::DerefMut;
58use std::path::{Path, PathBuf};
59use std::sync::{Arc, Mutex};
60
61use std::collections::HashMap;
62
63use calibration::{Calibration, CalibrationChannel, CalibrationFinal, CalibrationParams};
64use mcd::{MCDParser, ParserState};
65
66use image::{DynamicImage, ImageFormat, RgbImage, RgbaImage};
67use slide::{SlideFiducialMarks, SlideProfile};
68use transform::AffineTransform;
69
70/// Print to `writer` trait
71pub trait Print {
72 /// Formats and prints to `writer`
73 fn print<W: fmt::Write + ?Sized>(&self, writer: &mut W, indent: usize) -> fmt::Result;
74}
75
76/// Represents property of having an optical image
77pub struct OpticalImage<R> {
78 reader: Arc<Mutex<BufReader<R>>>,
79
80 // TODO: Why are we using i64 here?
81 start_offset: i64,
82 end_offset: i64,
83 image_format: ImageFormat,
84}
85
86impl<R: Read + Seek> OpticalImage<R> {
87 /// Returns whether an optical image is present
88 //fn has_image(&self) -> bool;
89 /// Returns the binary data for the image, exactly as stored in the .mcd file
90 pub fn image_data(&self) -> Result<Vec<u8>> {
91 let mut reader = self.reader.lock().or(Err(MCDError::PoisonMutex))?;
92
93 let start_offset = self.start_offset();
94 let image_size = self
95 .image_size()
96 .try_into()
97 .or(Err(MCDError::InvalidOffset {
98 offset: start_offset,
99 }))?;
100
101 let mut buf_u8 = vec![0; image_size];
102
103 match reader.seek(SeekFrom::Start(start_offset as u64)) {
104 Ok(_seek) => match reader.read_exact(&mut buf_u8) {
105 Ok(()) => Ok(buf_u8),
106 Err(error) => Err(error.into()),
107 },
108 Err(error) => Err(error.into()),
109 }
110 }
111
112 /// Returns the dimensions of the images in pixels as a tuple (width, height)
113 pub fn dimensions(&self) -> Result<(u32, u32)> {
114 let mut guard = self.reader.lock().or(Err(MCDError::PoisonMutex))?;
115 let reader: &mut BufReader<R> = guard.deref_mut();
116
117 let start_offset = self.start_offset();
118 reader.seek(SeekFrom::Start(start_offset as u64))?;
119
120 let mut reader = ImageReader::new(reader);
121 reader.set_format(self.image_format());
122
123 match reader.into_dimensions() {
124 Ok(dims) => Ok(dims),
125 Err(error) => Err(MCDError::from(error)),
126 }
127 }
128
129 /// Returns a decoded RgbaImage of the panorama image
130 pub fn as_rgba8(&self) -> Result<RgbaImage> {
131 Ok(self.dynamic_image()?.into_rgba8())
132 }
133
134 /// Returns a decoded RgbImage of the panorama image
135 pub fn as_rgb8(&self) -> Result<RgbImage> {
136 Ok(self.dynamic_image()?.into_rgb8())
137 }
138
139 fn dynamic_image(&self) -> Result<DynamicImage> {
140 let mut reader = self.reader.lock().or(Err(MCDError::PoisonMutex))?;
141 let start_offset = self.start_offset();
142
143 match reader.seek(SeekFrom::Start(start_offset as u64)) {
144 Ok(_seek) => {
145 let mut reader = ImageReader::new(reader.deref_mut());
146 reader.set_format(self.image_format);
147
148 // Remove the limits here, as it is possible that the images are larger than 512 MB
149 reader.no_limits();
150
151 Ok(reader.decode()?)
152 }
153 Err(error) => Err(error.into()),
154 }
155 }
156}
157
158impl<R> OpticalImage<R> {
159 fn start_offset(&self) -> i64 {
160 self.start_offset + 161
161 }
162
163 fn image_size(&self) -> i64 {
164 self.end_offset - self.start_offset()
165 }
166
167 /// Returns the format of the stored optical image
168 pub fn image_format(&self) -> ImageFormat {
169 self.image_format
170 }
171}
172
173/// Represents an image which is acquired on a slide
174pub trait OnSlide {
175 /// Returns the bounding box encompasing the image area on the slide (in μm)
176 fn slide_bounding_box(&self) -> BoundingBox<f64>;
177 /// Returns the affine transformation from pixel coordinates within the image to to the slide coordinates (μm)
178 fn to_slide_transform(&self) -> AffineTransform<f64>;
179}
180
181/// Represents a region of an image (in pixels)
182#[derive(Debug, Clone, Copy)]
183pub struct Region {
184 /// x-position of the top left corner of the region
185 pub x: u32,
186 /// y-position of the top left corner of the region
187 pub y: u32,
188 /// width of the region in pixels
189 pub width: u32,
190 /// height of the region in pixels
191 pub height: u32,
192}
193
194/// Represents a imaging mass cytometry (*.mcd) file.
195#[derive(Debug)]
196pub struct MCD<R> {
197 reader: Arc<Mutex<std::io::BufReader<R>>>,
198 location: Option<PathBuf>,
199
200 xmlns: Option<String>,
201
202 slides: HashMap<u16, Slide<R>>,
203 //acquisition_order: Vec<String>,
204 //acquisitions: HashMap<String, Arc<Acquisition>>,
205 //acquisition_rois: Vec<AcquisitionROI>,
206 //roi_points: Vec<ROIPoint>,
207 calibration_finals: HashMap<u16, CalibrationFinal>,
208 calibration_params: HashMap<u16, CalibrationParams>,
209 calibration_channels: HashMap<u16, CalibrationChannel>,
210 calibrations: HashMap<u16, Calibration>,
211 slide_fiducal_marks: HashMap<u16, SlideFiducialMarks>,
212 slide_profiles: HashMap<u16, SlideProfile>,
213}
214
215fn find_mcd_start(chunk: &[u8], chunk_size: usize) -> usize {
216 for start_index in 0..chunk_size {
217 if let Ok(_data) = std::str::from_utf8(&chunk[start_index..]) {
218 return start_index - 1;
219 }
220 }
221
222 0
223}
224
225fn u16_from_u8(a: &mut [u16], v: &[u8]) {
226 for i in 0..a.len() {
227 a[i] = (v[i * 2] as u16) | ((v[i * 2 + 1] as u16) << 8)
228 }
229}
230
231impl MCD<File> {
232 /// Open an .mcd file from the specified path.
233 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<MCD<File>> {
234 let mut mcd = MCD::parse(File::open(&path)?)?;
235 mcd.set_location(path);
236
237 Ok(mcd)
238 }
239
240 /// Returns the location (path) of the .mcd file
241 pub fn location(&self) -> Option<&Path> {
242 Some(self.location.as_ref()?.as_path())
243 }
244
245 /// Returns the location (path) of the .mcd file
246 pub fn set_location<P: AsRef<Path>>(&mut self, location: P) {
247 let mut path_buf = PathBuf::new();
248 path_buf.push(location);
249
250 self.location = Some(path_buf);
251 }
252
253 pub(crate) fn dcm_file(&self) -> Option<PathBuf> {
254 let mut path = PathBuf::from(self.location()?);
255 path.set_extension("dcm");
256
257 Some(path)
258 }
259
260 /// Use a temporary file for faster access to channel images.
261 ///
262 /// If this file does not already exist, then it is created.
263 ///
264 /// Data is stored in the *.mcd file spectrum-wise which means to load a single image, the entire acquired acquisition must be loaded first.
265 /// This method creates a temporary file (*.dcm) in the same location as the *.mcd file (if it doesn't already exist) which has the channel
266 /// data stored image-wise. If this file is present and loaded, then `Mcd` will choose the fastest method to use to return the requested data.
267 ///
268 /// # Errors
269 ///
270 /// If the location is not set either automatically via [`MCD::from_path`] or manually via [`MCD::set_location`] then a [`MCDError::LocationNotSpecified`]
271 /// will occur.
272 pub fn with_dcm(mut self) -> Result<Self> {
273 if std::fs::metadata(self.dcm_file().ok_or(MCDError::LocationNotSpecified)?).is_err() {
274 let dcm_file =
275 std::fs::File::create(self.dcm_file().ok_or(MCDError::LocationNotSpecified)?)?;
276 let mut dcm_file = BufWriter::new(dcm_file);
277
278 convert::convert(&self, &mut dcm_file)?;
279 }
280
281 convert::open(&mut self)?;
282
283 Ok(self)
284 }
285}
286
287impl<R: Read + Seek> MCD<R> {
288 fn new(reader: R) -> Self {
289 MCD {
290 reader: Arc::new(Mutex::new(BufReader::new(reader))),
291 location: None,
292 xmlns: None,
293 slides: HashMap::new(),
294 //panoramas: HashMap::new(),
295 //acquisition_channels: Vec::new(),
296 //acquisition_order: Vec::new(),
297 //acquisitions: HashMap::new(),
298 //acquisition_rois: Vec::new(),
299 calibration_finals: HashMap::new(),
300 calibration_params: HashMap::new(),
301 calibration_channels: HashMap::new(),
302 calibrations: HashMap::new(),
303 slide_fiducal_marks: HashMap::new(),
304 slide_profiles: HashMap::new(),
305 }
306 }
307
308 /// Parse *.mcd format
309 pub fn parse(reader: R) -> Result<Self> {
310 let mcd = MCD::new(reader);
311 let combined_xml = mcd.xml()?;
312
313 // let mut file = std::fs::File::create("tmp.xml").unwrap();
314 // file.write_all(combined_xml.as_bytes())?;
315
316 let mut parser = MCDParser::new(mcd);
317
318 // println!("Found combined XML {}", combined_xml);
319
320 let mut reader = quick_xml::Reader::from_str(&combined_xml);
321 // let mut buf = Vec::with_capacity(BUF_SIZE);
322
323 loop {
324 match reader.read_event() {
325 Ok(event) => {
326 // println!("Event: {:?}", event);
327 parser.process(event);
328
329 // Check whether we are finished or have encounted a fatal error
330 match parser.current_state() {
331 ParserState::FatalError => {
332 match parser.pop_error_back() {
333 // TODO: Probably a better way of doing this..
334 Some(value) => return Err(value),
335 None => println!(
336 "A fatal error occurred when parsing, but it wasn't recorded"
337 ),
338 }
339
340 break;
341 }
342 ParserState::Finished => {
343 break;
344 }
345 _ => (),
346 }
347 }
348 Err(error) => {
349 return Err(error.into());
350 // println!("An error occurred when reading: {}", error);
351 // break;
352 }
353 }
354
355 // buf.clear();
356 }
357
358 let mcd = parser.mcd();
359
360 if mcd.slides().is_empty() {
361 Err(MCDError::NoSlidePresent)
362 } else {
363 Ok(mcd)
364 }
365 }
366
367 /// Returns the raw XML metadata stored in the .mcd file
368 pub fn xml(&self) -> Result<String> {
369 let chunk_size_i64: i64 = 1000;
370 let mut cur_offset: i64 = 0;
371
372 let chunk_size = chunk_size_i64.try_into()?;
373
374 let mut buf_u8 = vec![0; chunk_size];
375
376 loop {
377 let mut reader = self.reader.lock().or(Err(MCDError::PoisonMutex))?;
378
379 reader.seek(SeekFrom::End(-cur_offset - chunk_size_i64))?;
380
381 reader.read_exact(&mut buf_u8)?;
382
383 match std::str::from_utf8(&buf_u8) {
384 Ok(_data) => {}
385 Err(_error) => {
386 // Found the final chunk, so find the start point
387 let start_index = find_mcd_start(&buf_u8, chunk_size);
388
389 let total_size = cur_offset + chunk_size_i64 - (start_index as i64);
390 buf_u8 = vec![0; total_size.try_into()?];
391
392 reader.seek(SeekFrom::End(-total_size))?;
393 reader.read_exact(&mut buf_u8)?;
394
395 break;
396 }
397 }
398
399 cur_offset += chunk_size_i64;
400 }
401
402 let mut combined_xml = String::new();
403
404 let mut buf_u16: Vec<u16> = vec![0; buf_u8.len() / 2];
405 u16_from_u8(&mut buf_u16, &buf_u8);
406
407 let data = String::from_utf16(&buf_u16)?;
408 combined_xml.push_str(&data);
409
410 Ok(combined_xml)
411 }
412}
413
414impl<R> MCD<R> {
415 pub(crate) fn reader(&self) -> &Arc<Mutex<BufReader<R>>> {
416 &self.reader
417 }
418
419 /// Returns a copy of the slide IDs, sorted by ID number
420 pub fn slide_ids(&self) -> Vec<u16> {
421 let mut ids: Vec<u16> = Vec::with_capacity(self.slides.len());
422
423 for id in self.slides.keys() {
424 ids.push(*id);
425 }
426
427 // TODO: We could just sort the slides once and then return references to the held vectors to avoid allocating
428 // new ones in `pub fn slides(&self)`
429 ids.sort_unstable();
430
431 ids
432 }
433
434 /// Returns slide with a given ID number, or `None` if no such slide exists
435 pub fn slide(&self, id: u16) -> Option<&Slide<R>> {
436 self.slides.get(&id)
437 }
438
439 /// Returns a vector of references to slides sorted by ID number. This allocates a new vector on each call.
440 pub fn slides(&self) -> Vec<&Slide<R>> {
441 let mut slides = Vec::new();
442
443 for id in self.slide_ids() {
444 slides.push(
445 self.slide(id)
446 .expect("Should only be finding slides that exist"),
447 );
448 }
449
450 slides
451 }
452
453 fn slides_mut(&mut self) -> &mut HashMap<u16, Slide<R>> {
454 &mut self.slides
455 }
456
457 /// Return a vector of references to all acquisitions in the .mcd file (iterates over all slides and all panoramas).
458 pub fn acquisitions(&self) -> Vec<&Acquisition<R>> {
459 let mut acquisitions = HashMap::new();
460
461 // This should be unnecessary - hopefully there is only one set of channels per dataset?
462 for slide in self.slides.values() {
463 for panorama in slide.panoramas() {
464 for acquisition in panorama.acquisitions() {
465 acquisitions.insert(acquisition.id(), acquisition);
466 }
467 }
468 }
469
470 let mut ordered_acquisitions = Vec::new();
471 for (_, acquisition) in acquisitions.drain() {
472 ordered_acquisitions.push(acquisition);
473 }
474
475 ordered_acquisitions.sort_by_key(|a| a.id());
476
477 ordered_acquisitions
478 }
479
480 /// Return an acquisition which matches the supplied `AcquisitionIdentifier` or None if no match found
481 pub fn acquisition<A: Into<AcquisitionIdentifier>>(
482 &self,
483 identifier: A,
484 ) -> Option<&Acquisition<R>> {
485 let identifier = identifier.into();
486
487 for slide in self.slides.values() {
488 for panorama in slide.panoramas() {
489 for acquisition in panorama.acquisitions() {
490 match &identifier {
491 AcquisitionIdentifier::Id(id) => {
492 if acquisition.id() == *id {
493 return Some(acquisition);
494 }
495 }
496 AcquisitionIdentifier::Order(order_number) => {
497 if acquisition.order_number() == *order_number {
498 return Some(acquisition);
499 }
500 }
501 AcquisitionIdentifier::Description(description) => {
502 if acquisition.description() == description {
503 return Some(acquisition);
504 }
505 }
506 }
507 }
508 }
509 }
510
511 None
512 }
513
514 /// Returns a list of acquisitions which are at least partially contained within the specified bounding box.
515 pub fn acquisitions_in(&self, region: &BoundingBox<f64>) -> Vec<&Acquisition<R>> {
516 let mut acquisitions = Vec::new();
517
518 for slide in self.slides.values() {
519 for panorama in slide.panoramas() {
520 for acquisition in panorama.acquisitions() {
521 if acquisition.in_region(region) {
522 acquisitions.push(acquisition);
523 }
524 }
525 }
526 }
527
528 acquisitions
529 }
530
531 /// Returns a vector of all channels present within any acquisition performed on the slide, sorted by channel order number.
532 pub fn channels(&self) -> Vec<&AcquisitionChannel> {
533 let mut channels = HashMap::new();
534
535 // This should be unnecessary - hopefully there is only one set of channels per dataset?
536 for slide in self.slides.values() {
537 for panorama in slide.panoramas() {
538 for acquisition in panorama.acquisitions() {
539 for channel in acquisition.channels() {
540 if !channels.contains_key(channel.name()) {
541 channels.insert(channel.name(), channel);
542 }
543 }
544 }
545 }
546 }
547
548 let mut ordered_channels = Vec::new();
549 for (_, channel) in channels.drain() {
550 ordered_channels.push(channel);
551 }
552
553 ordered_channels.sort_by_key(|a| [a.label(), a.name()]);
554
555 ordered_channels
556 }
557
558 /// Returns a vector of all channels, excluding those from the acquisitions with names matching those specified
559 pub fn channels_excluding(&self, exclusion_list: Vec<&str>) -> Vec<&AcquisitionChannel> {
560 let mut channels = HashMap::new();
561
562 // This should be unnecessary - hopefully there is only one set of channels per dataset?
563 for slide in self.slides.values() {
564 for panorama in slide.panoramas() {
565 for acquisition in panorama.acquisitions() {
566 if !exclusion_list.contains(&acquisition.description()) {
567 for channel in acquisition.channels() {
568 if !channels.contains_key(channel.name())
569 && channel.name() != "X"
570 && channel.name() != "Y"
571 && channel.name() != "Z"
572 {
573 channels.insert(channel.name(), channel);
574 }
575 }
576 }
577 }
578 }
579 }
580
581 let mut ordered_channels = Vec::new();
582 for (_, channel) in channels.drain() {
583 ordered_channels.push(channel);
584 }
585
586 ordered_channels.sort_by_key(|a| a.label().to_ascii_lowercase());
587
588 ordered_channels
589 }
590
591 /// Returns an instance of `CalibrationFinal` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
592 pub fn calibration_final(&self, id: u16) -> Option<&CalibrationFinal> {
593 self.calibration_finals.get(&id)
594 }
595
596 /// Returns an instance of `CalibrationParams` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
597 pub fn calibration_params(&self, id: u16) -> Option<&CalibrationParams> {
598 self.calibration_params.get(&id)
599 }
600
601 /// Returns an instance of `CalibrationChannel` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
602 pub fn calibration_channels(&self, id: u16) -> Option<&CalibrationChannel> {
603 self.calibration_channels.get(&id)
604 }
605
606 /// Returns an instance of `Calibration` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
607 pub fn calibration(&self, id: u16) -> Option<&Calibration> {
608 self.calibrations.get(&id)
609 }
610
611 /// Returns an instance of `SlideFiducialMarks` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
612 pub fn slide_fiducal_marks(&self, id: u16) -> Option<&SlideFiducialMarks> {
613 self.slide_fiducal_marks.get(&id)
614 }
615
616 /// Returns an instance of `SlideProfile` with the specified ID, or None if none exists (this is always the case in version 1 of the Schema)
617 pub fn slide_profile(&self, id: u16) -> Option<&SlideProfile> {
618 self.slide_profiles.get(&id)
619 }
620}
621
622#[rustfmt::skip]
623impl<R> Print for MCD<R> {
624 fn print<W: fmt::Write + ?Sized>(&self, writer: &mut W, indent: usize) -> fmt::Result {
625 // writeln!(writer, "MCD File: {}", self.location)?;
626
627 match self.xmlns.as_ref() {
628 Some(xmlns) => writeln!(writer, "XML Namespace: {}", xmlns)?,
629 None => {
630 writeln!(writer, "WARNING: Missing namespace")?;
631 }
632 }
633
634 for slide in self.slides.values() {
635 slide.print(writer, indent + 1)?;
636 }
637
638 let channels = self.channels();
639 write!(writer, "{:indent$}", "", indent = indent)?;
640 writeln!(writer, "{:-^1$}", "Channels", 25)?;
641
642 for channel in channels {
643 writeln!(
644 writer,
645 "{0: <2} | {1: <10} | {2: <10}",
646 channel.order_number(),
647 channel.name(),
648 channel.label()
649 )?;
650 }
651
652 Ok(())
653 }
654}
655
656impl<R> fmt::Display for MCD<R> {
657 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
658 self.print(f, 0)
659 }
660}
661
662//pub struct MCDPublic {}
663
664/*#[derive(Debug)]
665pub enum ImageFormat {
666 Png,
667}*/
668
669/// Represents a bounding rectangle
670#[derive(Debug, Clone)]
671pub struct BoundingBox<T: num_traits::Num + Copy> {
672 /// Minimum x coordinate for the bounding rectangle
673 pub min_x: T,
674 /// Minimum y coordinate for the bounding rectangle
675 pub min_y: T,
676 /// Width of bounding rectangle
677 pub width: T,
678 /// Height of bounding rectangle
679 pub height: T,
680}
681
682impl<T: num_traits::Num + Copy> BoundingBox<T> {
683 /// Maximum x coordinate for the bounding rectangle
684 pub fn max_x(&self) -> T {
685 self.min_x + self.width
686 }
687
688 /// Maximum y coordinate for the bounding rectangle
689 pub fn max_y(&self) -> T {
690 self.min_y + self.height
691 }
692}
693
694/// Represents a channel image (stored as a vector of f32).
695/// If the run was stopped mid acquisition width*height != valid_pixels
696pub struct ChannelImage {
697 region: Region,
698
699 acquisition_id: u16,
700 name: String,
701 label: String,
702
703 range: (f32, f32),
704 valid_pixels: usize,
705 data: Vec<f32>,
706}
707
708impl ChannelImage {
709 /// Returns the width (in pixels) of the image
710 pub fn width(&self) -> u32 {
711 self.region.width
712 }
713
714 /// Returns the height (in pixels) of the image
715 pub fn height(&self) -> u32 {
716 self.region.height
717 }
718
719 /// Returns a pair (min, max) of f32 describing the limits of the detected intensities in the image
720 pub fn intensity_range(&self) -> (f32, f32) {
721 self.range
722 }
723
724 /// Returns whether the data is complete (true) or whether the data acquisition aborted (false)
725 pub fn is_complete(&self) -> bool {
726 self.valid_pixels == (self.region.width * self.region.height) as usize
727 }
728
729 /// Returns the number of valid pixels in the image. If the run was aborted part way through `num_valid_pixels() < width() * height()`
730 pub fn num_valid_pixels(&self) -> usize {
731 self.valid_pixels
732 }
733
734 /// Returns the detected intensity values for this channel
735 pub fn intensities(&self) -> &[f32] {
736 &self.data
737 }
738
739 /// Returns the ID for the acquisition this channel belongs to.
740 pub fn acquisition_id(&self) -> u16 {
741 self.acquisition_id
742 }
743
744 /// Returns the name of this channel
745 pub fn name(&self) -> &str {
746 &self.name
747 }
748
749 /// Returns the label assigned to this channel
750 pub fn label(&self) -> &str {
751 &self.label
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use image::{ImageBuffer, Pixel, Rgba};
758
759 use super::*;
760
761 use std::time::Instant;
762
763 #[test]
764 fn test_load() -> Result<()> {
765 let filename = "../test/20200612_FLU_1923.mcd";
766
767 println!("test_load() {:?}", filename);
768
769 println!("{:?}", File::open(filename));
770 let start = Instant::now();
771 let mcd = MCD::from_path(filename)?;
772 println!("Time taken to parse .mcd: {:?}", start.elapsed());
773
774 // Optionally we can load/create the .dcm file for fast access to images
775 let start = Instant::now();
776 // let mcd = mcd.with_dcm()?;
777 println!("Time taken to parse .dcm: {:?}", start.elapsed());
778
779 let start = Instant::now();
780 let roi_001 = mcd.acquisition("ROI_001").unwrap();
781 println!("Time taken to find acquisition: {:?}", start.elapsed());
782
783 let dna = roi_001.channel(ChannelIdentifier::label("DNA1")).unwrap();
784
785 let start = Instant::now();
786 let dna_roi001 = roi_001.channel_image(dna, None).unwrap();
787 println!("Time taken to read channel image: {:?}", start.elapsed());
788
789 let mut acq_image: ImageBuffer<Rgba<u8>, Vec<u8>> =
790 ImageBuffer::new(dna_roi001.width(), dna_roi001.height());
791
792 let mut index = 0;
793 let max_value = 20.0;
794 for y in 0..dna_roi001.height() {
795 if index >= dna_roi001.valid_pixels {
796 break;
797 }
798
799 for x in 0..dna_roi001.width() {
800 if index >= dna_roi001.valid_pixels {
801 break;
802 }
803
804 let g = ((dna_roi001.data[index] / max_value) * 255.0) as u8;
805 let g = g as f64 / 255.0;
806
807 let cur_pixel = acq_image.get_pixel_mut(x as u32, y as u32).channels_mut();
808 cur_pixel[1] = (g * 255.0) as u8;
809 cur_pixel[3] = 255;
810
811 index += 1;
812 }
813 }
814
815 acq_image.save("dna.png").unwrap();
816
817 // println!("{:?}", cell);
818
819 Ok(())
820 }
821
822 #[test]
823 fn test_all_in_folder() -> Result<()> {
824 let paths = std::fs::read_dir("../test/").unwrap();
825
826 for path in paths {
827 let path = path?;
828
829 if path.path().extension().unwrap() != "mcd" {
830 println!("Skipping {:?} file.", path.path().extension().unwrap());
831 continue;
832 }
833
834 let _mcd = MCD::from_path(path.path())?;
835 }
836
837 Ok(())
838 }
839
840 // #[test]
841 // fn test_all_in_folder() -> Result<()> {
842 // let paths = std::fs::read_dir("test/").unwrap();
843
844 // for path in paths {
845 // let path = path?;
846
847 // if path.path().extension().unwrap() != "mcd" {
848 // println!("Skipping {:?} file.", path.path().extension().unwrap());
849 // continue;
850 // }
851
852 // let mcd = MCD::from_path(path.path())?.with_dcm()?;
853
854 // // let overview_image = mcd.slides()[0].create_overview_image(7500, None).unwrap();
855
856 // // overview_image.save("overview.png").unwrap();
857
858 // //let _xml = mcd.xml()?;
859
860 // //println!("{}", xml);
861
862 // for acquisition in mcd.acquisitions() {
863 // println!("[{}] {}", acquisition.id(), acquisition.description());
864 // }
865
866 // let acquisition = mcd.acquisitions()[0];
867
868 // let acquisition = mcd
869 // .acquisition(&AcquisitionIdentifier::Description(
870 // acquisition.description().to_string(),
871 // ))
872 // .unwrap();
873
874 // let x_channel = acquisition
875 // .channel_image(&ChannelIdentifier::Name("X".to_string()), None)
876 // .unwrap();
877
878 // println!("Loaded X Channel : {:?}", x_channel.num_valid_pixels());
879
880 // for channel in mcd.channels_excluding(vec!["ROI 12", "ROI 13", "ROI 14", "ROI 16"]) {
881 // println!(
882 // "[Channel {}] {} | {}",
883 // channel.id(),
884 // channel.label(),
885 // channel.name()
886 // );
887 // }
888
889 // let channel_identifier = ChannelIdentifier::Name("Ir(191)".to_string());
890 // println!("Subimage");
891 // let data = acquisition.channel_images(
892 // &[channel_identifier.clone()],
893 // // None,
894 // Some(Region {
895 // x: 1000,
896 // y: 1000,
897 // width: 500,
898 // height: 500,
899 // }),
900 // )?;
901
902 // let data = &data[0];
903
904 // let mut acq_image: ImageBuffer<Rgba<u8>, Vec<u8>> =
905 // ImageBuffer::new(data.width(), data.height());
906
907 // let mut index = 0;
908 // let max_value = 20.0;
909 // for y in 0..data.height() {
910 // if index >= data.valid_pixels {
911 // break;
912 // }
913
914 // for x in 0..data.width() {
915 // if index >= data.valid_pixels {
916 // break;
917 // }
918
919 // let g = ((data.data[index] / max_value) * 255.0) as u8;
920 // let g = g as f64 / 255.0;
921
922 // let cur_pixel = acq_image.get_pixel_mut(x as u32, y as u32).channels_mut();
923 // cur_pixel[1] = (g * 255.0) as u8;
924 // cur_pixel[3] = 255;
925
926 // index += 1;
927 // }
928 // }
929
930 // // acq_image.save("dna.png").unwrap();
931
932 // // let start = Instant::now();
933 // // let mut image_map = HashMap::new();
934
935 // // for acquisition in mcd.acquisitions() {
936 // // if let Ok(data) = acquisition.channel_image(
937 // // &channel_identifier,
938 // // // None
939 // // Some(Region {
940 // // x: 1000,
941 // // y: 1000,
942 // // width: 500,
943 // // height: 500,
944 // // }),
945 // // ) {
946 // // image_map.insert(format!("{}", acquisition.id()), ChannelImage(data));
947 // // }
948 // // }
949
950 // // let duration = start.elapsed();
951
952 // // println!("Time elapsed loading data is: {:?}", duration);
953 // }
954
955 // Ok(())
956 // }
957}