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}