imc_rs/
acquisition.rs

1use core::fmt;
2use std::{
3    collections::{HashMap, HashSet},
4    io::{BufReader, Cursor, Read, Seek, SeekFrom},
5    sync::{Arc, Mutex, MutexGuard},
6};
7
8use byteorder::{LittleEndian, ReadBytesExt};
9use image::ImageFormat;
10use nalgebra::Vector2;
11
12use crate::{
13    channel::{AcquisitionChannel, ChannelIdentifier},
14    convert::DCMLocation,
15    error::{MCDError, Result},
16    mcd::AcquisitionXML,
17    transform::AffineTransform,
18    BoundingBox, ChannelImage, OnSlide, OpticalImage, Print, Region,
19};
20
21#[derive(Debug, Clone)]
22pub enum DataFormat {
23    Float,
24}
25
26// #[derive(Debug)]
27// pub struct DataLocation {
28//     pub reader: Arc<Mutex<BufReader<File>>>,
29
30//     pub offsets: Vec<u64>,
31//     pub sizes: Vec<u64>,
32// }
33
34/// AcquisitionIdentifier is a way of identifying a specific acquisition
35#[derive(Debug)]
36pub enum AcquisitionIdentifier {
37    /// Identified by unique identifier
38    Id(u16),
39    /// Identified by the number specifying the order in which the acquisition was acquired
40    Order(i16),
41    /// Match the description of the acquistion (specified by the user)
42    Description(String),
43}
44
45impl AcquisitionIdentifier {
46    /// Create an acquisition identifier based on a description
47    pub fn description(description: &str) -> Self {
48        AcquisitionIdentifier::Description(description.into())
49    }
50}
51
52impl From<&str> for AcquisitionIdentifier {
53    fn from(description: &str) -> Self {
54        Self::Description(description.into())
55    }
56}
57
58impl fmt::Display for AcquisitionIdentifier {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        match self {
61            AcquisitionIdentifier::Id(id) => {
62                write!(f, "acquisition id: {}", id)
63            }
64            AcquisitionIdentifier::Order(order) => {
65                write!(f, "acquisition order: {}", order)
66            }
67            AcquisitionIdentifier::Description(description) => {
68                write!(f, "acquisition description: {}", description)
69            }
70        }
71    }
72}
73
74#[derive(Debug, Clone, Copy)]
75pub enum ProfilingType {
76    Global,
77}
78
79/// Trait describing a collection of acquisitions and providing methods to summarise
80/// the collection (e.g. to get the list of channels)
81pub trait Acquisitions {
82    /// Returns a list of unique channels from the collection of acquisitions
83    fn channels(&self) -> Vec<&AcquisitionChannel>;
84}
85
86impl<R> Acquisitions for Vec<&Acquisition<R>> {
87    fn channels(&self) -> Vec<&AcquisitionChannel> {
88        let mut channels = HashMap::new();
89
90        for acquisition in self {
91            for channel in acquisition.channels() {
92                if !channels.contains_key(channel.name()) {
93                    channels.insert(channel.name(), channel);
94                }
95            }
96        }
97
98        let mut ordered_channels = Vec::new();
99        for (_, channel) in channels.drain() {
100            ordered_channels.push(channel);
101        }
102
103        ordered_channels.sort_by_key(|a| a.label().to_ascii_lowercase());
104
105        ordered_channels
106    }
107}
108
109/// Acquisition represents a single region analysed by IMC.
110#[derive(Debug)]
111pub struct Acquisition<R> {
112    pub(crate) reader: Option<Arc<Mutex<BufReader<R>>>>,
113    pub(crate) dcm_location: Option<DCMLocation>,
114
115    id: u16,
116    description: String,
117    ablation_power: f64,
118    ablation_distance_between_shots_x: f64,
119    ablation_distance_between_shots_y: f64,
120    ablation_frequency: f64,
121    acquisition_roi_id: i16,
122    order_number: i16,
123    signal_type: String,
124    dual_count_start: String,
125    data_start_offset: i64,
126    data_end_offset: i64,
127    start_timestamp: String,
128    end_timestamp: String,
129    after_ablation_image_start_offset: i64,
130    after_ablation_image_end_offset: i64,
131    before_ablation_image_start_offset: i64,
132    before_ablation_image_end_offset: i64,
133    roi_start_x_pos_um: f64,
134    roi_start_y_pos_um: f64,
135    roi_end_x_pos_um: f64,
136    roi_end_y_pos_um: f64,
137    movement_type: String,
138    segment_data_format: DataFormat,
139    value_bytes: u8,
140    max_x: i32,
141    max_y: i32,
142    plume_start: i32,
143    plume_end: i32,
144    template: String,
145
146    profiling_type: Option<ProfilingType>,
147
148    channels: Vec<AcquisitionChannel>,
149}
150
151impl<R> Clone for Acquisition<R> {
152    fn clone(&self) -> Self {
153        Self {
154            reader: self.reader.clone(),
155            dcm_location: self.dcm_location.clone(),
156            id: self.id,
157            description: self.description.clone(),
158            ablation_power: self.ablation_power,
159            ablation_distance_between_shots_x: self.ablation_distance_between_shots_x,
160            ablation_distance_between_shots_y: self.ablation_distance_between_shots_y,
161            ablation_frequency: self.ablation_frequency,
162            acquisition_roi_id: self.acquisition_roi_id,
163            order_number: self.order_number,
164            signal_type: self.signal_type.clone(),
165            dual_count_start: self.dual_count_start.clone(),
166            data_start_offset: self.data_start_offset,
167            data_end_offset: self.data_end_offset,
168            start_timestamp: self.start_timestamp.clone(),
169            end_timestamp: self.end_timestamp.clone(),
170            after_ablation_image_start_offset: self.after_ablation_image_start_offset,
171            after_ablation_image_end_offset: self.after_ablation_image_end_offset,
172            before_ablation_image_start_offset: self.before_ablation_image_start_offset,
173            before_ablation_image_end_offset: self.before_ablation_image_end_offset,
174            roi_start_x_pos_um: self.roi_start_x_pos_um,
175            roi_start_y_pos_um: self.roi_start_y_pos_um,
176            roi_end_x_pos_um: self.roi_end_x_pos_um,
177            roi_end_y_pos_um: self.roi_end_y_pos_um,
178            movement_type: self.movement_type.clone(),
179            segment_data_format: self.segment_data_format.clone(),
180            value_bytes: self.value_bytes,
181            max_x: self.max_x,
182            max_y: self.max_y,
183            plume_start: self.plume_start,
184            plume_end: self.plume_end,
185            template: self.template.clone(),
186            profiling_type: self.profiling_type,
187            channels: self.channels.clone(),
188        }
189    }
190}
191
192pub struct SpectrumIterator<'a, R> {
193    acquisition: &'a Acquisition<R>,
194    reader: MutexGuard<'a, BufReader<R>>,
195    buffer: Vec<u8>,
196}
197
198impl<'a, R: Seek> SpectrumIterator<'a, R> {
199    fn new(acquisition: &'a Acquisition<R>) -> Self {
200        let mut reader = acquisition.reader.as_ref().unwrap().lock().unwrap();
201
202        let offset = acquisition.data_start_offset as u64;
203
204        // TODO: Handle this properly without unwrapping
205        reader.seek(SeekFrom::Start(offset)).unwrap();
206
207        SpectrumIterator {
208            acquisition,
209            reader,
210            buffer: vec![0u8; 4 * acquisition.channels.len()],
211        }
212    }
213}
214
215impl<'a, R: Read + Seek> Iterator for SpectrumIterator<'a, R> {
216    type Item = Vec<f32>;
217
218    fn next(&mut self) -> Option<Vec<f32>> {
219        let cur_pos = self.reader.seek(SeekFrom::Current(0)).unwrap();
220        if cur_pos >= self.acquisition.data_end_offset as u64 {
221            None
222        } else {
223            let mut spectrum = Vec::with_capacity(self.acquisition.channels.len());
224
225            self.reader.read_exact(&mut self.buffer).unwrap();
226            let mut buffer = Cursor::new(&mut self.buffer);
227
228            for _i in 0..self.acquisition.channels.len() {
229                let float = buffer.read_f32::<LittleEndian>().unwrap();
230
231                spectrum.push(float);
232            }
233
234            Some(spectrum)
235        }
236    }
237}
238
239impl<R> Acquisition<R> {
240    /// Returns the ID associated with the acquisition
241    pub fn id(&self) -> u16 {
242        self.id
243    }
244
245    /// Returns a description of the acquisition
246    pub fn description(&self) -> &str {
247        &self.description
248    }
249
250    /// Returns a number representing the order in which the acquisition was acquired (0 being first).
251    pub fn order_number(&self) -> i16 {
252        self.order_number
253    }
254
255    /// Returns the width of the acquired region (in pixels)
256    pub fn width(&self) -> i32 {
257        self.max_x
258    }
259
260    /// Returns the height of the acquired region (in pixels)
261    pub fn height(&self) -> i32 {
262        self.max_y
263    }
264
265    /// Returns the ablation frequency
266    pub fn ablation_frequency(&self) -> f64 {
267        self.ablation_frequency
268    }
269
270    /// Returns the region ID for the acquisition
271    pub fn acquisition_roi_id(&self) -> i16 {
272        self.acquisition_roi_id
273    }
274
275    /// Returns the profiling type for the acquisition, if one is present. This is not present in version 1 of the schema
276    pub fn profiling_type(&self) -> Option<&ProfilingType> {
277        self.profiling_type.as_ref()
278    }
279
280    /*fn image_data(&self, start: i64, end: i64) -> Result<Vec<u8>, std::io::Error> {
281        let mutex = self
282            .reader
283            .as_ref()
284            .expect("Should have copied the reader across");
285        let reader = mutex.lock().unwrap();
286
287        read_image_data(reader, start, end)
288    }
289
290    fn dynamic_image(&self, start: i64, end: i64) -> DynamicImage {
291        let mut reader = ImageReader::new(Cursor::new(self.image_data(start, end).unwrap()));
292        reader.set_format(ImageFormat::Png);
293        reader.decode().unwrap()
294    }*/
295
296    /// Returns the optical image of the acquisition region prior to ablation
297    pub fn before_ablation_image(&self) -> OpticalImage<R> {
298        OpticalImage {
299            reader: self.reader.as_ref().unwrap().clone(),
300            start_offset: self.before_ablation_image_start_offset,
301            end_offset: self.before_ablation_image_end_offset,
302            image_format: ImageFormat::Png,
303        }
304        // match self.dynamic_image(
305        //     self.before_ablation_image_start_offset,
306        //     self.before_ablation_image_end_offset,
307        // ) {
308        //     DynamicImage::ImageRgba8(rgba8) => rgba8,
309        //     _ => panic!("Unexpected DynamicImage type"),
310        // }
311    }
312
313    /// Returns the optical image of the acquisition region after ablation
314    pub fn after_ablation_image(&self) -> OpticalImage<R> {
315        OpticalImage {
316            reader: self.reader.as_ref().unwrap().clone(),
317            start_offset: self.after_ablation_image_start_offset,
318            end_offset: self.after_ablation_image_end_offset,
319            image_format: ImageFormat::Png,
320        }
321    }
322
323    /// Returns a list of all channels acquired within this acquisition
324    pub fn channels(&self) -> &[AcquisitionChannel] {
325        &self.channels
326    }
327
328    pub(crate) fn channels_mut(&mut self) -> &mut Vec<AcquisitionChannel> {
329        &mut self.channels
330    }
331
332    /// Returns whether the acquisition has run to completion (checks the size of the recorded data
333    /// compared to the expected data size)
334    pub fn is_complete(&self) -> bool {
335        let expected_size: usize = self.channels().len()
336            * self.max_x as usize
337            * self.max_y as usize
338            * self.value_bytes as usize;
339        let measured_size: usize = self.data_end_offset as usize - self.data_start_offset as usize;
340
341        // println!("Expected: {} | Measured: {}", expected_size, measured_size);
342
343        expected_size == measured_size
344    }
345
346    /// Returns a `Region` describing the pixel region contained within the specified bounding box
347    pub fn pixels_in(&self, region: &BoundingBox<f64>) -> Option<Region> {
348        let transform = self.to_slide_transform();
349
350        let top_left = transform.transform_from_slide(region.min_x, region.min_y)?;
351        let top_right = transform.transform_from_slide(region.max_x(), region.min_y)?;
352
353        let bottom_right = transform.transform_from_slide(region.max_x(), region.max_y())?;
354        let bottom_left = transform.transform_from_slide(region.min_x, region.max_y())?;
355
356        let min_x = top_left
357            .x
358            .min(top_right.x)
359            .min(bottom_right.x)
360            .min(bottom_left.x)
361            .max(0.0)
362            .floor();
363
364        let max_x = top_left
365            .x
366            .max(top_right.x)
367            .max(bottom_right.x)
368            .max(bottom_left.x)
369            .min(self.width() as f64)
370            .ceil();
371
372        let min_y = top_left
373            .y
374            .min(top_right.y)
375            .min(bottom_right.y)
376            .min(bottom_left.y)
377            .max(0.0)
378            .floor();
379
380        let max_y = top_left
381            .y
382            .max(top_right.y)
383            .max(bottom_right.y)
384            .max(bottom_left.y)
385            .min(self.height() as f64)
386            .ceil();
387
388        Some(Region {
389            x: min_x as u32,
390            y: (self.height() as u32 - max_y as u32),
391            width: (max_x - min_x) as u32,
392            height: (max_y - min_y) as u32,
393        })
394    }
395
396    /// Tests whether the acquisition is (at least partially) contained within the specified bounding box (slide coordinates).
397    pub fn in_region(&self, region: &BoundingBox<f64>) -> bool {
398        let slide_box = self.slide_bounding_box();
399
400        if slide_box.min_x < region.max_x()
401            && slide_box.max_x() > region.min_x
402            && slide_box.max_y() > region.min_y
403            && slide_box.min_y < region.max_y()
404        {
405            return true;
406        }
407
408        false
409    }
410
411    /// Returns the size of a single spectrum in bytes
412    #[inline]
413    pub fn spectrum_size(&self) -> usize {
414        self.channels().len() * self.value_bytes as usize
415    }
416
417    /// Returns the number of spectra acquired as part of the acquisition
418    #[inline]
419    pub fn num_spectra(&self) -> usize {
420        let measured_size: usize = self.data_end_offset as usize - self.data_start_offset as usize;
421
422        measured_size / self.spectrum_size()
423    }
424}
425
426impl<R: Read + Seek> Acquisition<R> {
427    /// Returns the ChannelImage for the channel matching the `ChannelIdentifier`. This contains the intensities of the channel
428    /// for each detected pixel, the number of valid pixels and the width and height of the image.
429    pub fn channel_image<C: Into<ChannelIdentifier>>(
430        &self,
431        identifier: C,
432        region: Option<Region>,
433    ) -> Result<ChannelImage> {
434        Ok(self
435            .channel_images(&[identifier.into()], region)?
436            .drain(..)
437            .last()
438            .expect("A channel image should always be returned, as we always pass one identifier"))
439    }
440
441    /// Returns array of ChannelImages for the channels matching the `ChannelIdentifier`s. This contains the intensities of the channel
442    /// for each detected pixel, the number of valid pixels and the width and height of the image.
443    pub fn channel_images<C: AsRef<ChannelIdentifier>>(
444        &self,
445        identifiers: &[C],
446        region: Option<Region>,
447    ) -> Result<Vec<ChannelImage>> {
448        // println!("Searching identifiers: {:?}", identifiers);
449        // println!("Searching from channels: {:?}", self.channels());
450
451        let channels: Vec<_> = identifiers
452            .iter()
453            .map(|identifier| {
454                self.channel(identifier).ok_or(MCDError::InvalidChannel {
455                    channel: identifier.as_ref().clone(),
456                })
457            })
458            .collect::<Result<Vec<_>>>()?;
459
460        let order_numbers: Vec<_> = channels
461            .iter()
462            .map(|channel| channel.order_number() as usize)
463            .collect();
464
465        let region = match region {
466            Some(region) => region,
467            None => crate::Region {
468                x: 0,
469                y: 0,
470                width: self.width() as u32,
471                height: self.height() as u32,
472            },
473        };
474
475        let last_row = self.num_spectra() / self.width() as usize;
476        let last_col = self.width() as usize - (self.num_spectra() % self.width() as usize);
477
478        let valid_region_row = (region.y + region.height).min(last_row as u32);
479        let valid_region_col = (region.x + region.width).min(last_col as u32);
480
481        // println!("{} / {}", valid_region_row, region.y);
482        let valid_pixels = if region.y >= valid_region_row {
483            (valid_region_row - 1) * region.width + valid_region_col
484        } else {
485            ((valid_region_row - region.y - 1) * region.width) + (valid_region_col - region.x)
486        };
487
488        let mut data = if let Some(data_location) = &self.dcm_location {
489            data_location.read_channels(&order_numbers, &region)?
490        } else {
491            let mut data: Vec<Vec<f32>> =
492                vec![
493                    Vec::with_capacity((region.width * region.height) as usize);
494                    identifiers.len()
495                ];
496
497            let order_hash: HashSet<usize> = HashSet::from_iter(order_numbers.iter().copied());
498
499            for y in region.y..(region.y + region.height) {
500                for x in region.x..(region.x + region.width) {
501                    for (channel_index, intensity) in self
502                        .spectrum(x, y)?
503                        .iter()
504                        .enumerate()
505                        .filter(|(index, _intensity)| order_hash.contains(index))
506                        .map(|(_, intensity)| *intensity)
507                        .enumerate()
508                    {
509                        data[channel_index].push(intensity);
510                    }
511                }
512            }
513
514            data
515        };
516
517        let images: Vec<_> = data
518            .drain(..)
519            .zip(channels.iter())
520            .map(|(data, channel)| {
521                let mut min_value = f32::MAX;
522                let mut max_value = f32::MIN;
523
524                for &data_point in data.iter() {
525                    if data_point < min_value {
526                        min_value = data_point;
527                    }
528                    if data_point > max_value {
529                        max_value = data_point;
530                    }
531                }
532
533                ChannelImage {
534                    region,
535                    acquisition_id: channel.acquisition_id(),
536                    name: channel.name().to_string(),
537                    label: channel.label().to_string(),
538                    range: (min_value, max_value),
539                    valid_pixels: valid_pixels as usize,
540                    data,
541                }
542            })
543            .collect();
544
545        Ok(images)
546
547        // Ok(ChannelImage {
548        //     region,
549        //     range: (min_value, max_value),
550        //     valid_pixels: valid_pixels as usize,
551        //     data,
552        // })
553    }
554
555    /// Returns the channel which matches the given identifier, or None if no match found
556    pub fn channel<C: AsRef<ChannelIdentifier>>(
557        &self,
558        identifier: C,
559    ) -> Option<&AcquisitionChannel> {
560        self.channels
561            .iter()
562            .find(|&channel| channel.is(identifier.as_ref()))
563    }
564
565    // pub fn channel_index(&self, identifier: &ChannelIdentifier) -> Option<usize> {
566    //     for (index, channel) in self.channels.iter().enumerate() {
567    //         if channel.is(identifier) {
568    //             return Some(index);
569    //         }
570    //     }
571
572    //     None
573    // }
574
575    // There are a number of potential issues with the ROI positions that we attempt to fix here
576    pub(crate) fn fix_roi_positions(&mut self) {
577        // In version 2 of the schema, it seems like ROIStartXPosUm and ROIStartYPosUm are 1000x what they should be, so try and detect this and correct for it
578        if self.roi_start_x_pos_um > 75000.0 {
579            self.roi_start_x_pos_um /= 1000.0;
580        }
581
582        // In version 2 of the schema, it seems like ROIStartXPosUm and ROIStartYPosUm are 1000x what they should be, so try and detect this and correct for it
583        if self.roi_start_y_pos_um > 75000.0 {
584            self.roi_start_y_pos_um /= 1000.0;
585        }
586
587        // There seems to be a bug where the start and end x pos is recorded as the same value
588        if (self.roi_start_x_pos_um == self.roi_end_x_pos_um) || self.roi_end_x_pos_um == 0.0 {
589            self.roi_end_x_pos_um = self.roi_start_x_pos_um
590                + (self.max_x as f64 * self.ablation_distance_between_shots_x);
591        }
592
593        if self.roi_end_y_pos_um == 0.0 {
594            self.roi_end_y_pos_um = self.roi_start_y_pos_um
595                - (self.max_y as f64 * self.ablation_distance_between_shots_y);
596        }
597    }
598}
599
600impl<R: Read + Seek> Acquisition<R> {
601    /// Provides an iterator over all spectra (each pixel) within the acquisition
602    pub fn spectra(&self) -> SpectrumIterator<R> {
603        SpectrumIterator::new(self)
604    }
605
606    /// Returns a spectrum at the specified (x, y) coordinate
607    pub fn spectrum(&self, x: u32, y: u32) -> Result<Vec<f32>> {
608        let index = y as usize * self.max_x as usize + x as usize;
609
610        if index >= self.num_spectra() {
611            return Err(MCDError::InvalidIndex {
612                index,
613                num_spectra: self.num_spectra(),
614            });
615        }
616
617        let offset = self.data_start_offset as u64
618            + (index * self.channels.len() * self.value_bytes as usize) as u64;
619
620        let mut spectrum = Vec::with_capacity(self.channels.len());
621        let mut reader = self.reader.as_ref().unwrap().lock().unwrap();
622
623        // TODO: Handle this properly without unwrapping
624        reader.seek(SeekFrom::Start(offset))?;
625
626        let mut buffer = [0u8; 4];
627        for _i in 0..self.channels.len() {
628            reader.read_exact(&mut buffer)?;
629            let float = f32::from_le_bytes(buffer);
630            spectrum.push(float);
631        }
632
633        Ok(spectrum)
634    }
635}
636
637impl<R> OnSlide for Acquisition<R> {
638    /// Returns the affine transformation from pixel coordinates within the acquisition to to the slide coordinates (μm)
639    fn to_slide_transform(&self) -> AffineTransform<f64> {
640        let mut moving_points = Vec::new();
641        let mut fixed_points = Vec::new();
642
643        moving_points.push(Vector2::new(
644            self.roi_start_x_pos_um,
645            self.roi_start_y_pos_um,
646            //25000.0 - self.roi_start_y_pos_um,
647        ));
648        moving_points.push(Vector2::new(
649            self.roi_end_x_pos_um,
650            self.roi_start_y_pos_um,
651            //25000.0 - self.roi_start_y_pos_um,
652        ));
653        moving_points.push(Vector2::new(
654            self.roi_start_x_pos_um,
655            self.roi_end_y_pos_um,
656            //25000.0 - self.roi_end_y_pos_um,
657        ));
658        //moving_points.push(Vector2::new(roi_end_x_pos_um, self.roi_end_y_pos_um));
659
660        let y = (self.roi_start_y_pos_um - self.roi_end_y_pos_um)
661            / self.ablation_distance_between_shots_y;
662
663        fixed_points.push(Vector2::new(0.0, y));
664        fixed_points.push(Vector2::new(self.max_x as f64, y));
665        //fixed_points.push(Vector2::new(0.0, self.max_y as f64));
666        fixed_points.push(Vector2::new(0.0, 0.0));
667
668        //fixed_points.push(Vector2::new(self.max_x as f64, self.max_y as f64));
669
670        AffineTransform::from_points(moving_points, fixed_points)
671    }
672
673    /// Returns the bounding box encompasing the acquisition area on the slide (in μm)
674    fn slide_bounding_box(&self) -> BoundingBox<f64> {
675        BoundingBox {
676            min_x: f64::min(self.roi_start_x_pos_um, self.roi_end_x_pos_um),
677            min_y: f64::min(self.roi_start_y_pos_um, self.roi_end_y_pos_um),
678            width: (self.roi_end_x_pos_um - self.roi_start_x_pos_um).abs(),
679            height: (self.roi_end_y_pos_um - self.roi_start_y_pos_um).abs(),
680        }
681    }
682}
683
684#[rustfmt::skip]
685impl<R> Print for Acquisition<R> {
686    fn print<W: fmt::Write + ?Sized>(&self, writer: &mut W, indent: usize) -> fmt::Result {
687        write!(writer, "{:indent$}", "", indent = indent)?;
688        writeln!(writer, "{:-^1$}", "Acquisition", 48)?;
689
690        writeln!(writer, "{:indent$}{: <22} | {}", "", "ID",          self.id,          indent = indent)?;
691        writeln!(writer, "{:indent$}{: <22} | {}", "", "Description", self.description, indent = indent)?;
692        writeln!(writer, "{:indent$}{: <22} | {}", "", "Order number", self.order_number, indent = indent)?;
693        writeln!(
694            writer,
695            "{:indent$}{: <22} | {} x {}",
696            "",
697            "Dimensions (pixels)",
698            self.max_x,
699            self.max_y,
700            indent = indent
701        )?;
702        writeln!(
703            writer,
704            "{:indent$}{: <22} | {} x {}",
705            "",
706            "Distance between shots",
707            self.ablation_distance_between_shots_x,
708            self.ablation_distance_between_shots_y,
709            indent = indent
710        )?;
711        writeln!(
712            writer,
713            "{:indent$}{: <22} | {}",
714            "",
715            "Signal type",
716            self.signal_type,
717            indent = indent
718        )?;
719        writeln!(
720            writer,
721            "{:indent$}{: <22} | {}",
722            "",
723            "Ablation power",
724            self.ablation_power,
725            indent = indent
726        )?;
727        writeln!(
728            writer,
729            "{:indent$}{: <22} | {}",
730            "",
731            "Dual count start",
732            self.dual_count_start,
733            indent = indent
734        )?;
735        writeln!(
736            writer,
737            "{:indent$}{: <22} | {}",
738            "",
739            "Start timestamp",
740            self.start_timestamp,
741            indent = indent
742        )?;
743        writeln!(
744            writer,
745            "{:indent$}{: <22} | {}",
746            "",
747            "End timestamp",
748            self.end_timestamp,
749            indent = indent
750        )?;
751        writeln!(
752            writer,
753            "{:indent$}{: <22} | ({:.4} μm, {:.4} μm)",
754            "",
755            "ROI",
756            self.roi_start_x_pos_um,
757            self.roi_start_y_pos_um,
758            indent = indent
759        )?;
760        writeln!(
761            writer,
762            "{:indent$}{: <22} | ({:.4} μm, {:.4} μm)",
763            "",
764            "",
765            self.roi_end_x_pos_um,
766            self.roi_end_y_pos_um,
767            indent = indent
768        )?;
769        writeln!(
770            writer,
771            "{:indent$}{: <22} | {}",
772            "",
773            "Movement type",
774            self.movement_type,
775            indent = indent
776        )?;
777        writeln!(
778            writer,
779            "{:indent$}{: <22} | {:?}",
780            "",
781            "Segment data format",
782            self.segment_data_format,
783            indent = indent
784        )?;
785        writeln!(
786            writer,
787            "{:indent$}{: <22} | {}",
788            "",
789            "Value bytes",
790            self.value_bytes,
791            indent = indent
792        )?;
793        writeln!(
794            writer,
795            "{:indent$}{: <22} | {}",
796            "",
797            "Plume start",
798            self.plume_start,
799            indent = indent
800        )?;
801        writeln!(
802            writer,
803            "{:indent$}{: <22} | {}",
804            "",
805            "Plume end",
806            self.plume_end,
807            indent = indent
808        )?;
809        writeln!(
810            writer,
811            "{:indent$}{: <22} | {}",
812            "",
813            "Template",
814            self.template,
815            indent = indent
816        )?;
817
818        Ok(())
819    }
820}
821
822impl<R> fmt::Display for Acquisition<R> {
823    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
824        self.print(f, 0)
825    }
826}
827
828impl<R> From<AcquisitionXML> for Acquisition<R> {
829    fn from(acquisition: AcquisitionXML) -> Self {
830        Acquisition {
831            reader: None,
832            dcm_location: None,
833
834            id: acquisition.id.unwrap(),
835            description: acquisition.description.unwrap(),
836            ablation_power: acquisition.ablation_power.unwrap(),
837            ablation_distance_between_shots_x: acquisition
838                .ablation_distance_between_shots_x
839                .unwrap(),
840            ablation_distance_between_shots_y: acquisition
841                .ablation_distance_between_shots_y
842                .unwrap(),
843            ablation_frequency: acquisition.ablation_frequency.unwrap(),
844            acquisition_roi_id: acquisition.acquisition_roi_id.unwrap(),
845            order_number: acquisition.order_number.unwrap(),
846            signal_type: acquisition.signal_type.unwrap(),
847            dual_count_start: acquisition.dual_count_start.unwrap(),
848            data_start_offset: acquisition.data_start_offset.unwrap(),
849            data_end_offset: acquisition.data_end_offset.unwrap(),
850            start_timestamp: acquisition.start_timestamp.unwrap(),
851            end_timestamp: acquisition.end_timestamp.unwrap(),
852            after_ablation_image_start_offset: acquisition
853                .after_ablation_image_start_offset
854                .unwrap(),
855            after_ablation_image_end_offset: acquisition.after_ablation_image_end_offset.unwrap(),
856            before_ablation_image_start_offset: acquisition
857                .before_ablation_image_start_offset
858                .unwrap(),
859            before_ablation_image_end_offset: acquisition.before_ablation_image_end_offset.unwrap(),
860            roi_start_x_pos_um: acquisition.roi_start_x_pos_um.unwrap(),
861            roi_start_y_pos_um: acquisition.roi_start_y_pos_um.unwrap(),
862            roi_end_x_pos_um: acquisition.roi_end_x_pos_um.unwrap(),
863            roi_end_y_pos_um: acquisition.roi_end_y_pos_um.unwrap(),
864            movement_type: acquisition.movement_type.unwrap(),
865            segment_data_format: acquisition.segment_data_format.unwrap(),
866            value_bytes: acquisition.value_bytes.unwrap(),
867            max_x: acquisition.max_x.unwrap(),
868            max_y: acquisition.max_y.unwrap(),
869            plume_start: acquisition.plume_start.unwrap(),
870            plume_end: acquisition.plume_end.unwrap(),
871            template: acquisition.template.unwrap(),
872
873            profiling_type: acquisition.profiling_type,
874
875            channels: Vec::new(),
876        }
877    }
878}