cgats/
lib.rs

1#![warn(missing_docs)]
2#![allow(clippy::tabs_in_doc_comments)]
3//! This crate contains utilities for parsing and creating CGATS color data files.
4
5macro_rules! err {
6    ($e: expr) => { Err($e.to_string().into()) }
7}
8
9use std::{
10    fmt,
11    fs::File,
12    io::{BufWriter, Read, Write},
13    ops::{Index, IndexMut},
14    path::Path,
15};
16
17mod cmp;
18mod color;
19mod delta;
20mod field;
21mod parse;
22mod math;
23mod vendor;
24
25pub use color::ColorType;
26pub use delta::*;
27pub use math::*;
28pub use vendor::Vendor;
29pub use field::Field;
30
31/// A generic heap allocated error
32pub type BoxErr = Box<dyn std::error::Error>;
33/// A generic Result with a [`BoxErr`]
34pub type Result<T> = std::result::Result<T, BoxErr>;
35
36pub use DataPoint::*;
37/// The building block of CGATS data
38#[derive(Debug, Clone)]
39pub enum DataPoint {
40    /// String type
41    Alpha(String),
42    /// Float type
43    Float(f32),
44    /// Integer type
45    Int(i32),
46}
47
48impl DataPoint {
49    /// Create a new string type
50    pub fn new_alpha<S: ToString>(alpha: S) -> Self {
51        Alpha(alpha.to_string())
52    }
53
54    /// Create a new float type
55    pub fn new_float<F: Into<f32>>(float: F) -> Self {
56        Float(float.into())
57    }
58
59    /// Create a new integer type
60    pub fn new_int<I: Into<i32>>(int: I) -> Self {
61        Int(int.into())
62    }
63
64    /// Convert to integer type
65    pub fn to_int(&self) -> Result<Self> {
66        Ok(match self {
67            Alpha(a) => Int(a.parse()?),
68            Float(f) => Int(f.round() as i32),
69            Int(i) => Int(i.to_owned()),
70        })
71    }
72
73    /// Convert to float type
74    pub fn to_float(&self) -> Result<Self> {
75        Ok(match self {
76            Alpha(a) => Float(a.parse()?),
77            Float(f) => Float(f.to_owned()),
78            Int(i) => Float(*i as f32),
79        })
80    }
81
82    /// Extract the float value. Panics if the type cannot be converted to a Float.
83    pub fn to_float_unchecked(&self) -> f32 {
84        match self.to_float() {
85            Ok(Float(f)) => f,
86            _ => panic!("unchecked conversion to float failed"),
87        }
88    }
89
90    /// Convert to string type
91    pub fn to_alpha(&self) -> Self {
92        DataPoint::new_alpha(self)
93    }
94}
95
96impl AsRef<Self> for DataPoint {
97    fn as_ref(&self) -> &Self {
98        self
99    }
100}
101
102use std::cmp::Ordering::*;
103impl PartialOrd for DataPoint {
104    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
105        match (self.to_float(), other.to_float()) {
106            (Ok(f_self), Ok(f_other)) => f_self.partial_cmp(&f_other),
107            _ => match (self, other) {
108                (Alpha(a_self), Alpha(a_other)) => a_self.partial_cmp(a_other),
109                (Alpha(_), Float(_)|Int(_)) => Some(Greater),
110                (Float(_)|Int(_), Alpha(_)) => Some(Less),
111                _ => unreachable!("DataPoint PartialOrd")
112            }
113        }
114    }
115}
116
117/// CGATS header data
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum MetaData {
120    /// A Key:Value pair
121    KeyVal {
122        /// The key associated with the value
123        key: String,
124        /// The value associated with the key
125        val: String,
126    },
127    /// A comment line
128    Comment(String),
129    /// A blank line
130    Blank,
131}
132
133impl MetaData {
134    /// Return the key, if it has one
135    pub fn key(&self) -> Option<&str> {
136        if let MetaData::KeyVal{ key, .. } = self {
137            Some(key)
138        } else {
139            None
140        }
141    }
142
143    /// Return the value, if it has one
144    pub fn value(&self) -> Option<&str> {
145        Some(match self {
146            MetaData::KeyVal { val, .. } => val,
147            MetaData::Comment(val) => val,
148            MetaData::Blank => return None,
149        })
150    }
151
152    /// Return a mutable reference to the value, if it has one
153    pub fn value_mut(&mut self) -> Option<&mut String> {
154        Some(match self {
155            MetaData::KeyVal { val, .. } => val,
156            MetaData::Comment(val) => val,
157            MetaData::Blank => return None,
158        })
159    }
160}
161
162impl CgatsFmt for MetaData {
163    fn cgats_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        match self {
165            MetaData::KeyVal { key, val } => writeln!(f, "{}\t{}", key, val),
166            MetaData::Comment(comment) => writeln!(f, "{}", comment),
167            MetaData::Blank => writeln!(f),
168        }
169    }
170}
171
172impl fmt::Display for DataPoint {
173    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174        self.cgats_fmt(f)
175    }
176}
177
178impl CgatsFmt for DataPoint {
179    fn cgats_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180        match self {
181            Alpha(a) => write!(f, "{a}"),
182            Int(i) => write!(f, "{i}"),
183            Float(n) => match f.precision() {
184                Some(p) => write!(f, "{n:.p$}"),
185                None => write!(f, "{n}"),
186            }
187        }
188    }
189}
190
191#[test]
192fn display_datapoint() {
193    let f = Float(1.2345);
194    assert_eq!(format!("{:0.2}", f), "1.23");
195    assert_eq!(format!("{:0.3}", f), "1.235");
196
197    let i = Int(42);
198    assert_eq!(format!("{:0.3}", i), "42");
199    assert_eq!(format!("{:0.1}", i), "42");
200}
201
202/// The CGATS data
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct Cgats {
205    vendor: Vendor,
206    metadata: Vec<MetaData>,
207    data_format: DataFormat,
208    data: Vec<DataPoint>,
209}
210
211#[test]
212fn new_cgats() {
213    let cgats: Cgats =
214        "CGATS.17
215        BEGIN_DATA_FORMAT
216        END_DATA_FORMAT
217        BEGIN_DATA
218        END_DATA"
219        .parse().unwrap();
220
221    assert_eq!(cgats, Cgats::new());
222}
223
224impl Cgats {
225    /// Creates a new empty [`Cgats`] object
226    pub fn new() -> Self {
227        Cgats {
228            vendor: Vendor::Cgats,
229            metadata: Vec::new(),
230            data_format: DataFormat::new(),
231            data: Vec::new()
232        }
233    }
234
235    /// Creates a new empty [`Cgats`] object with the specified capacity
236    pub fn with_capacity(cap: usize) -> Self {
237        Cgats {
238            data: Vec::with_capacity(cap),
239            ..Self::new()
240        }
241    }
242
243    /// Returns a summary string:
244    ///
245    /// [`Vendor`][[`ColorType`]s; `n`]
246    ///
247    /// ```
248    /// use cgats::Cgats;
249    ///
250    /// let cgats: Cgats =
251    /// "CGATS.17
252    /// BEGIN_DATA_FORMAT
253    /// SAMPLE_ID	RGB_R	RGB_G	RGB_B
254    /// END_DATA_FORMAT
255    /// BEGIN_DATA
256    /// 1	0	0	0
257    /// 2	128	128	128
258    /// 3	255	255	255
259    /// END_DATA"
260    /// .parse().unwrap();
261    ///
262    /// assert_eq!(cgats.summary(), "Cgats[Rgb; 3]");
263    /// ```
264    pub fn summary(&self) -> String {
265        format!(
266            "{}[{}; {}]",
267            self.vendor(),
268            self.color_types().iter().join(", "),
269            self.n_rows()
270        )
271    }
272
273    /// Returns a reference the [`Vendor`]
274    pub fn vendor(&self) -> &Vendor {
275        &self.vendor
276    }
277
278    /// Find the [`MetaData`] value by key
279    /// ```
280    /// use cgats::Cgats;
281    ///
282    /// let cgats: Cgats =
283    /// "CGATS.17
284    /// NUMBER_OF_FIELDS 4
285    /// BEGIN_DATA_FORMAT
286    /// SAMPLE_ID	RGB_R	RGB_G	RGB_B
287    /// END_DATA_FORMAT
288    /// NUMBER_OF_SETS 3
289    /// BEGIN_DATA
290    /// 1	0	0	0
291    /// 2	128	128	128
292    /// 3	255	255	255
293    /// END_DATA"
294    /// .parse().unwrap();
295    ///
296    /// assert_eq!(cgats.get_metadata("NUMBER_OF_FIELDS"), Some("4"));
297    /// assert_eq!(cgats.get_metadata("NUMBER_OF_SETS"), Some("3"));
298    /// ```
299    pub fn get_metadata<'a>(&'a self, key: &str) -> Option<&'a str> {
300        self.metadata.iter()
301            .find(|meta| meta.key() == Some(key))
302            .and_then(MetaData::value)
303    }
304
305    /// Returns a mutable reference to a metadata value by key
306    pub fn get_metadata_mut(&mut self, key: &str) -> Option<&mut String> {
307        self.metadata.iter_mut()
308            .find(|meta| meta.key() == Some(key))
309            .and_then(MetaData::value_mut)
310    }
311
312    /// Insert a MetaData Key:Value, overwriting if it exists
313    pub fn insert_metadata_keyval<S: ToString, T: ToString>(&mut self, key: S, val: T) {
314        if let Some(value) = self.get_metadata_mut(&key.to_string()) {
315            *value = val.to_string();
316        } else {
317            self.metadata.push(MetaData::KeyVal{key: key.to_string(), val: val.to_string()});
318        }
319    }
320
321    /// Iterator over the [`DataPoint`]s in a [`Cgats`] object
322    pub fn iter(&self) -> impl Iterator<Item=&DataPoint> {
323        self.data.iter()
324    }
325
326    /// Iterator over the [`DataPoint`]s with their corresponding `Field` keys
327    ///
328    /// ```
329    /// use cgats::{Cgats, Field::*, DataPoint::*};
330    ///
331    /// let cgats: Cgats =
332    /// "CGATS.17
333    /// BEGIN_DATA_FORMAT
334    /// SAMPLE_ID	DE2000
335    /// END_DATA_FORMAT
336    /// BEGIN_DATA
337    /// 1	1.23
338    /// 2	3.21
339    /// END_DATA"
340    /// .parse().unwrap();
341    ///
342    ///let mut iter = cgats.iter_with_fields();
343    ///
344    /// assert_eq!(iter.next(), Some((&SAMPLE_ID, &Int(1))));
345    /// assert_eq!(iter.next(), Some((&DE_2000, &Float(1.23))));
346    /// assert_eq!(iter.next(), Some((&SAMPLE_ID, &Int(2))));
347    /// assert_eq!(iter.next(), Some((&DE_2000, &Float(3.21))));
348    /// assert_eq!(iter.next(), None);
349    /// ```
350    pub fn iter_with_fields(&self) -> impl Iterator<Item=(&Field, &DataPoint)> {
351        self.data_format
352            .fields
353            .iter()
354            .cycle()
355            .zip(self.iter())
356    }
357
358    /// Mutable iterator over the [`DataPoint`]s with their corresponding `Field` keys.
359    /// [`Field`] values are cloned.
360    ///
361    /// ```
362    /// use cgats::{Cgats, Field::*, DataPoint::*};
363    ///
364    /// let mut cgats: Cgats =
365    /// "CGATS.17
366    /// BEGIN_DATA_FORMAT
367    /// SAMPLE_ID	DE2000
368    /// END_DATA_FORMAT
369    /// BEGIN_DATA
370    /// 1	1.23
371    /// 2	3.21
372    /// END_DATA"
373    /// .parse().unwrap();
374    ///
375    /// {
376    ///     let mut iter = cgats.iter_mut_with_fields();
377    ///    
378    ///     assert_eq!(iter.next(), Some((SAMPLE_ID, &mut Int(1))));
379    ///     assert_eq!(iter.next(), Some((DE_2000, &mut Float(1.23))));
380    ///     assert_eq!(iter.next(), Some((SAMPLE_ID, &mut Int(2))));
381    ///    
382    ///     if let Some((_field, data_point)) = iter.next() {
383    ///         *data_point = Float(4.56);
384    ///     }
385    ///    
386    ///     assert_eq!(iter.next(), None);
387    /// }
388    ///
389    /// assert_eq!(cgats[3], Float(4.56));
390    /// ```
391    pub fn iter_mut_with_fields(&mut self) -> impl Iterator<Item=(Field, &mut DataPoint)> {
392        self.data_format
393            .fields
394            .clone()
395            .into_iter()
396            .cycle()
397            .zip(self.iter_mut())
398    }
399
400    /// Iterator over the fields of the [`DataFormat`]
401    pub fn fields(&self) -> impl Iterator<Item=&Field> {
402        self.data_format.fields()
403    }
404
405    /// Mutable iterator over the fields of the [`DataFormat`]
406    pub fn fields_mut(&mut self) -> impl Iterator<Item=&mut Field> {
407        self.data_format.fields_mut()
408    }
409
410    /// Mutable iterator over the [`DataPoint`]s in a [`Cgats`] object
411    pub fn iter_mut(&mut self) -> impl Iterator<Item=&mut DataPoint> {
412        self.data.iter_mut()
413    }
414
415    /// Read a file into a [`Cgats`] object
416    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
417        let mut string = String::new();
418        File::open(path)?.read_to_string(&mut string)?;
419        string.parse()
420    }
421
422    /// Write a [`Cgats`] object to a file
423    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
424        let buf = &mut BufWriter::new(File::create(path)?);
425        self.write(buf)
426    }
427
428    /// Write a [`Cgats`] object to a [`Write`] object
429    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
430        Ok(write!(writer, "{}", self)?)
431    }
432
433    /// Returns the total number of data points in the set (rows x cols)
434    pub fn len(&self) -> usize {
435        self.data.len()
436    }
437
438    /// Returns true if `self.len() == 0`
439    pub fn is_empty(&self) -> bool {
440        self.len() == 0
441    }
442
443    /// Returns the total number of rows (samples) in the set
444    pub fn n_rows(&self) -> usize {
445        self.data.len().checked_div(self.n_cols()).unwrap_or(0)
446    }
447
448    /// Returns the total number of columns (fields) in the set
449    pub fn n_cols(&self) -> usize {
450        self.data_format.len()
451    }
452
453    /// Iterator over the data points in a given sample row
454    pub fn get_row(&self, row: usize) -> Option<impl Iterator<Item=&DataPoint>> {
455        let index = row * self.n_cols();
456        Some(self.data.get(index..index+self.n_cols())?.iter())
457    }
458
459    /// Returns the first row containing the given values
460    pub fn get_row_by_values<F: AsRef<Field>, D: AsRef<DataPoint>>(
461        &self,
462        fields_values: &[(F, D)]
463    ) -> Option<impl Iterator<Item=&DataPoint>> {
464        self.get_row(self.get_row_index_by_values(fields_values)?)
465    }
466
467    /// Returns a row index to the first row containing the given values
468    pub fn get_row_index_by_values<F: AsRef<Field>, D: AsRef<DataPoint>>(
469        &self,
470        fields_values: &[(F, D)],
471    ) -> Option<usize> {
472        if !fields_values.iter().all(|(field, _value)| self.data_format.contains(field.as_ref())) {
473            return None;
474        }
475
476        let mut row_index = None;
477        for (index, row) in self.rows().enumerate() {
478            let this_row = self.data_format.fields.iter().cycle().zip(row).collect::<Vec<_>>();
479            if fields_values.iter().all(|fv| this_row.contains(&(fv.0.as_ref(), fv.1.as_ref())))
480            {
481                row_index = Some(index);
482                break;
483            }
484        }
485
486        row_index
487    }
488
489    /// Remove a row from a sample set by row index and return the row. Returns none if the row
490    /// index is greater than or equal to the number of rows.
491    /// ```
492    /// use cgats::Cgats;
493    ///
494    /// let mut cgats: Cgats =
495    /// "CGATS.17
496    /// BEGIN_DATA_FORMAT
497    /// SAMPLE_ID	DE2000
498    /// END_DATA_FORMAT
499    /// BEGIN_DATA
500    /// 1	1.23
501    /// 2	3.21
502    /// 3	4.56
503    /// END_DATA"
504    /// .parse().unwrap();
505    ///
506    /// {
507    ///     let mut row = cgats.remove_row(1).unwrap();
508    ///     assert_eq!(row.next().unwrap(), 2);
509    ///     assert_eq!(row.next().unwrap(), 3.21);
510    ///     assert_eq!(row.next(), None);
511    /// }
512    ///
513    /// {
514    ///     let mut row = cgats.remove_row(1).unwrap();
515    ///     assert_eq!(row.next().unwrap(), 3);
516    ///     assert_eq!(row.next().unwrap(), 4.56);
517    ///     assert_eq!(row.next(), None);
518    /// }
519    ///
520    /// {
521    ///     assert!(cgats.remove_row(1).is_none());
522    /// }
523    /// ```
524    pub fn remove_row(&mut self, row: usize) -> Option<impl Iterator<Item = DataPoint> + '_> {
525        if row >= self.n_rows() {
526            None
527        } else {
528            let range = (row * self.n_cols())..((row * self.n_cols()) + self.n_cols());
529            Some(self.data.drain(range))
530        }
531    }
532
533    /// Move a row from one row index to another. Returns an error if the indices are out of range
534    /// ```
535    /// use cgats::Cgats;
536    ///
537    /// let mut cgats: Cgats =
538    /// "CGATS.17
539    /// BEGIN_DATA_FORMAT
540    /// SAMPLE_ID	DE2000
541    /// END_DATA_FORMAT
542    /// BEGIN_DATA
543    /// 1	1.23
544    /// 2	3.21
545    /// 3	4.56
546    /// END_DATA"
547    /// .parse().unwrap();
548    ///
549    /// cgats.move_row(2, 1).unwrap();
550    ///
551    /// {
552    ///     let mut last_row = cgats.get_row(2).unwrap();
553    ///     assert_eq!(last_row.next().unwrap(), &2);
554    ///     assert_eq!(last_row.next().unwrap(), &3.21);
555    ///     assert_eq!(last_row.next(), None);
556    /// }
557    ///
558    /// cgats.move_row(0, 2).unwrap();
559    ///
560    /// {
561    ///     let mut last_row = cgats.get_row(2).unwrap();
562    ///     assert_eq!(last_row.next().unwrap(), &1);
563    ///     assert_eq!(last_row.next().unwrap(), &1.23);
564    ///     assert_eq!(last_row.next(), None);
565    /// }
566    /// ```
567    pub fn move_row(&mut self, from_row: usize, to_row: usize) -> Result<()> {
568        if from_row == to_row {
569            Ok(())
570        } else {
571            #[allow(clippy::needless_collect)]
572            let row = self.remove_row(from_row)
573                .ok_or(format!("could not remove row {from_row}"))?
574                .collect::<Vec<_>>();
575            self.insert_row(to_row, row.into_iter())
576        }
577    }
578
579    /// Re-order rows to transpose for chart layouts with a given current chart width (`LGOROWLENGTH`)
580    pub fn transpose_chart(&mut self, chart_width: usize) {
581        let mut transpose_map: Vec<usize> = Vec::new();
582        let mut new_data: Vec<DataPoint> = Vec::with_capacity(self.len());
583
584        let chart_height = chart_height(self.n_rows(), chart_width);
585
586        for x in 0..chart_width {
587            for y in 0..chart_height {
588                transpose_map.push(row_index_by_chart_pos(chart_width, (x, y)));
589            }
590        }
591
592        for row_index in transpose_map {
593            if let Some(row) = self.get_row(row_index) {
594                for data_point in row {
595                    new_data.push(data_point.clone());
596                }
597            }
598        }
599
600        self.data = new_data;
601        self.insert_metadata_keyval("LGOROWLENGTH", chart_height);
602    }
603
604        /// Iterator of mutable references to data points in a given sample row
605    pub fn get_row_mut(&mut self, row: usize) -> impl Iterator<Item=&mut DataPoint> {
606        let n_rows = self.n_rows();
607        self.iter_mut().skip(row).step_by(n_rows)
608    }
609
610    /// Iterator over the rows of [`DataPoint`]s
611    pub fn rows(&self) -> impl Iterator<Item=impl Iterator<Item=&DataPoint>> {
612        (0..self.n_rows()).into_iter()
613            .filter_map(|row| self.get_row(row))
614    }
615
616    /// Returns an iterator over the data points in a given column (field)
617    pub fn get_col(&self, col: usize) -> impl Iterator<Item=&DataPoint> {
618        self.iter().skip(col).step_by(self.n_cols())
619    }
620
621    /// Find a column by it's [`Field`]. Returns [`None`] if the [`Field`] does not exist.
622    pub fn get_col_by_field(&self, field: &Field) -> Option<impl Iterator<Item = &DataPoint>> {
623        Some(self.get_col(self.index_by_field(field)?))
624    }
625
626    /// Iterator of mutable references to data points in a given column (field)
627    pub fn get_col_mut(&mut self, col: usize) -> impl Iterator<Item=&mut DataPoint> {
628        let n_cols = self.n_cols();
629        self.iter_mut().skip(col).step_by(n_cols)
630    }
631
632    /// Find a mutable column by it's [`Field`]. Returns [`None`] if the [`Field`] does not exist.
633    pub fn get_col_mut_by_field(
634        &mut self,
635        field: &Field,
636    ) -> Option<impl Iterator<Item = &mut DataPoint>> {
637        Some(self.get_col_mut(self.index_by_field(field)?))
638    }
639
640    /// Iterator over the columns of [`DataPoint`]s
641    pub fn cols(&self) -> impl Iterator<Item=impl Iterator<Item=&DataPoint>> {
642        (0..self.n_cols()).into_iter()
643            .map(|col| self.get_col(col))
644    }
645
646    /// Iterator over columns of [`DataPoint`]s with their corresponding [`Field`]s
647    pub fn cols_with_fields(&self) -> impl Iterator<Item=(&Field, impl Iterator<Item=&DataPoint>)> {
648        self.data_format
649            .fields
650            .iter()
651            .zip(self.cols())
652    }
653
654    /// Returns a list of [`ColorType`]s based on the contents of the `DATA_FORMAT`
655    pub fn color_types(&self) -> Vec<ColorType> {
656        let mut color_types = Vec::new();
657        if let Some(color_type) = self.cb_color_type() {
658            color_types.push(color_type);
659        }
660        color_types.append(&mut self.data_format.color_types());
661        color_types
662    }
663
664    fn cb_color_type(&self) -> Option<ColorType> {
665        if self.vendor != Vendor::ColorBurst || self.n_rows() % 21 != 0 {
666            None
667        } else {
668            Some(match self.n_rows() / 21 {
669                0..=2 => return None,
670                3 => ColorType::Rgb,
671                4 => ColorType::Cmyk,
672                5 => ColorType::FiveClr,
673                6 => ColorType::SixClr,
674                7 => ColorType::SevenClr,
675                8 => ColorType::EightClr,
676                _ => ColorType::NClr,
677            })
678        }
679    }
680
681    /// Determines if the data contains a [`ColorType`]
682    pub fn has_color_type(&self, color_type: &ColorType) -> bool {
683        self.color_types().contains(color_type)
684    }
685
686    /// Re-number the `SAMPLE_ID` field starting from 0
687    pub fn reindex_sample_id(&mut self) {
688        self.reindex_sample_id_at(0)
689    }
690
691    /// Re-number the `SAMPLE_ID` field starting from a given integer
692    pub fn reindex_sample_id_at(&mut self, start: usize) {
693        if let Some(i) = self.index_by_field(&SAMPLE_ID) {
694            self.get_col_mut(i)
695                .enumerate()
696                .for_each(|(i, dp)| *dp = Int((i + start) as i32));
697        } else {
698            self.insert_column(0, SAMPLE_ID, (start..(self.n_rows() + start)).map(|i| Int(i as i32)))
699                .expect("column must have same number of rows")
700        }
701    }
702
703    /// Returns the position of a column with a given [`Field`]
704    ///
705    /// Returns [`None`] if the [`DataFormat`] does not contain the [`Field`]
706    pub fn index_by_field(&self, field: &Field) -> Option<usize> {
707        self.data_format.index_by_field(field)
708    }
709
710    /// Insert a column of [`DataPoint`]s
711    pub fn insert_column(
712        &mut self,
713        index: usize,
714        label: Field,
715        iter: impl Iterator<Item=DataPoint>
716    ) -> Result<()> {
717        let iter = iter.collect::<Vec<DataPoint>>();
718        if self.is_empty() || iter.len() == self.n_rows() {
719            self.data_format.fields.insert(index, label);
720            let cols = self.n_cols();
721
722            let mut insert = index;
723            for dp in iter {
724                self.data.insert(insert, dp);
725                insert += cols;
726            }
727
728            Ok(())
729        } else {
730            Err(format!(
731                "column contains {} items but number of rows is {}",
732                iter.len(),
733                self.n_rows(),
734            ).into())
735        }
736    }
737
738    /// Append a column of [`DataPoint`]s
739    pub fn push_column(&mut self, field: Field, iter: impl Iterator<Item=DataPoint>) -> Result<()> {
740        self.insert_column(self.n_cols(), field, iter)
741    }
742
743    /// Insert a row of [`DataPoint`]s at a row index
744    pub fn insert_row(&mut self, index: usize, iter: impl Iterator<Item=DataPoint>) -> Result<()> {
745        let iter = iter.collect::<Vec<DataPoint>>();
746        if iter.len() != self.n_cols() {
747            Err(format!("row contains {} items but number of cols is {}", iter.len(), self.n_cols()).into())
748        } else {
749            let mut insert = index * self.n_cols();
750            for dp in iter.into_iter() {
751                self.data.insert(insert, dp);
752                insert += 1;
753            }
754            Ok(())
755        }
756    }
757
758    /// Append a row of [`DataPoint`]s
759    pub fn push_row(&mut self, iter: impl Iterator<Item=DataPoint>) -> Result<()> {
760        self.insert_row(self.n_rows(), iter)
761    }
762
763    /// Append the rows from another [`Cgats`]. Returns an error if the [`DataFormat`]s do not
764    /// match.
765    pub fn append(&mut self, other: &Cgats) -> Result<()> {
766        if self.data_format != other.data_format {
767            return err!("DATA_FORMAT does not match");
768        }
769        for row in other.rows() {
770            self.push_row(row.cloned())?;
771        }
772        if self.data_format.contains(&Field::SAMPLE_ID) {
773            self.reindex_sample_id();
774        }
775        Ok(())
776    }
777
778    /// Concatenate the rows from multiple [`Cgats`]
779    pub fn concatenate<'a>(root: &Cgats, other: impl Iterator<Item=&'a Cgats>) -> Result<Self> {
780        let mut root = root.clone();
781        for cgats in other {
782            root.append(cgats)?;
783        }
784        if root.data_format.contains(&Field::SAMPLE_ID) {
785            root.reindex_sample_id();
786        }
787        Ok(root)
788    }
789
790    /// Convert the CGATS to a ColorBurst linearization format.
791    pub fn to_colorburst(&self) -> Result<Self> {
792        self.to_colorburst_by_value()
793            .or_else(|_|self.to_colorburst_in_order())
794    }
795
796    /// Convert the CGATS to a ColorBurst linearization format.
797    /// Assumes the patches are in the correct order.
798    fn to_colorburst_in_order(&self) -> Result<Self> {
799        let d_red = self.get_col_by_field(&Field::D_RED).ok_or("D_RED field not found")?;
800        let d_green = self.get_col_by_field(&Field::D_GREEN).ok_or("D_GREEN field not found")?;
801        let d_blue = self.get_col_by_field(&Field::D_BLUE).ok_or("D_BLUE field not found")?;
802        let d_vis = self.get_col_by_field(&Field::D_VIS).ok_or("D_VIS field not found")?;
803        let lab_l = self.get_col_by_field(&Field::LAB_L).ok_or("LAB_L field not found")?;
804        let lab_a = self.get_col_by_field(&Field::LAB_A).ok_or("LAB_A field not found")?;
805        let lab_b = self.get_col_by_field(&Field::LAB_B).ok_or("LAB_B field not found")?;
806
807        let mut cgats = Cgats::with_capacity(self.n_rows() * 7);
808        cgats.vendor = Vendor::ColorBurst;
809
810        cgats.push_column(Field::D_RED,   d_red.cloned())?;
811        cgats.push_column(Field::D_GREEN, d_green.cloned())?;
812        cgats.push_column(Field::D_BLUE,  d_blue.cloned())?;
813        cgats.push_column(Field::D_VIS,   d_vis.cloned())?;
814        cgats.push_column(Field::LAB_L,   lab_l.cloned())?;
815        cgats.push_column(Field::LAB_A,   lab_a.cloned())?;
816        cgats.push_column(Field::LAB_B,   lab_b.cloned())?;
817
818        Ok(cgats)
819    }
820
821    /// Convert the CGATS to a ColorBurst linearization format.
822    /// Reorders patches by value.
823    fn to_colorburst_by_value(&self) -> Result<Self> {
824        let mut cgats = self.clone();
825        cgats.vendor = Vendor::ColorBurst;
826
827        let fields = match self.color_types()
828            .into_iter()
829            .find(move |color| COLORBURST_COLOR_TYPES.contains(color))
830            .ok_or("unable to determine color type")?
831        {
832            ColorType::Rgb      => color::FORMAT_RGB.as_slice(),
833            ColorType::Cmyk     => color::FORMAT_CMYK.as_slice(),
834            ColorType::FiveClr  => color::FORMAT_5CLR.as_slice(),
835            ColorType::SixClr   => color::FORMAT_6CLR.as_slice(),
836            ColorType::SevenClr => color::FORMAT_7CLR.as_slice(),
837            ColorType::EightClr => color::FORMAT_8CLR.as_slice(),
838            n => return err!(format!("unsupported color type: {n}")),
839        };
840
841        cgats.data.clear();
842
843        for color in fields {
844            for value in COLORBURST_INPUT {
845                let mut row = Vec::with_capacity(fields.len());
846                row.push((color, Float(value)));
847                for channel in fields {
848                    if color != channel {
849                        row.push((channel, Float(0.0)));
850                    }
851                }
852                let row = self.get_row_by_values(row.as_slice())
853                    .ok_or(format!("unable to find {color} with value {value}"))?;
854                cgats.push_row(row.cloned())?;
855            }
856        }
857
858        cgats.to_colorburst_in_order()
859    }
860
861    /// Convert ColorBurst linearization format to a conventional CGATS format
862    pub fn colorburst_to_cgats(&self) -> Result<Self> {
863        if self.vendor != Vendor::ColorBurst {
864            err!("not a ColorBurst linearization format")
865        } else if self.n_rows() % 21 != 0 {
866                err!("row count must be a multiple of 21 for ColorBurst")
867        } else {
868            let mut cgats = self.clone();
869            cgats.vendor = Vendor::Cgats;
870            cgats.reindex_sample_id();
871            let n_channels = self.n_rows() / COLORBURST_INPUT.len();
872            for i in 0..n_channels {
873                let field = Field::from_channel_index(n_channels, i)?;
874                let mut column = Vec::<DataPoint>::with_capacity(self.n_rows());
875                let mut index = i * 21;
876                for _val in 0..index {
877                    column.push(DataPoint::new_float(0.0));
878                }
879                for val in COLORBURST_INPUT {
880                    column.push(DataPoint::new_float(val));
881                    index += 1;
882                }
883                for _val in index..cgats.n_rows() {
884                    column.push(DataPoint::new_float(0.0));
885                }
886                let insert_index = cgats.index_by_field(&SAMPLE_ID).expect("SAMPLE_ID field not found");
887                cgats.insert_column(insert_index + i + 1, field, column.into_iter())?;
888            }
889            Ok(cgats)
890        }
891    }
892}
893
894#[test]
895fn transpose() {
896    let mut cgats = Cgats::from_file("test_files/transpose.txt").unwrap();
897    cgats.transpose_chart(2);
898    let mut transposed = Cgats::from_file("test_files/transposed.txt").unwrap();
899    assert_eq!(cgats, transposed);
900    cgats.transpose_chart(4);
901    transposed.transpose_chart(4);
902    assert_eq!(cgats, transposed);
903
904    let mut cgats = Cgats::from_file("test_files/transpose_uneven.txt").unwrap();
905    cgats.transpose_chart(4);
906    let mut transposed = Cgats::from_file("test_files/transposed_uneven.txt").unwrap();
907    assert_eq!(cgats, transposed);
908    cgats.transpose_chart(3);
909    transposed.transpose_chart(3);
910    assert_eq!(cgats, transposed);
911}
912
913#[test]
914fn test_chart_height() {
915    assert_eq!(chart_height(5,  5), 1);
916    assert_eq!(chart_height(5,  6), 1);
917    assert_eq!(chart_height(5,  100), 1);
918    assert_eq!(chart_height(9,  3), 3);
919    assert_eq!(chart_height(10, 4), 3);
920    assert_eq!(chart_height(10, 3), 4);
921    assert_eq!(chart_height(12, 4), 3);
922    assert_eq!(chart_height(12, 3), 4);
923}
924
925fn chart_height(patches: usize, width: usize) -> usize {
926    let full_rows = patches / width;
927    if patches % width == 0 {
928        full_rows
929    } else {
930        full_rows + 1
931    }
932}
933
934fn row_index_by_chart_pos(chart_width: usize, chart_xy: (usize, usize)) -> usize {
935    chart_xy.1 * chart_width + chart_xy.0
936}
937
938#[test]
939fn row_by_values() {
940    let cgats = Cgats::from_file("test_files/cgats1.tsv").unwrap();
941    let cyan_100 = &[
942        (&CMYK_C, &Int(100)),
943        (&CMYK_M, &Int(0)),
944        (&CMYK_Y, &Int(0)),
945        (&CMYK_K, &Int(0)),
946    ];
947    let magenta_100 = &[
948        (&CMYK_C, &Int(0)),
949        (&CMYK_M, &Int(100)),
950        (&CMYK_Y, &Int(0)),
951        (&CMYK_K, &Int(0)),
952    ];
953    let yellow_100 = &[
954        (&CMYK_C, &Int(0)),
955        (&CMYK_M, &Int(0)),
956        (&CMYK_Y, &Int(100)),
957        (&CMYK_K, &Int(0)),
958    ];
959    let row = cgats.get_row_by_values(cyan_100).unwrap().collect::<Vec<_>>();
960    assert_eq!(row[1], "Cyan");
961    let row = cgats.get_row_by_values(magenta_100).unwrap().collect::<Vec<_>>();
962    assert_eq!(row[1], "Magenta");
963    let row = cgats.get_row_by_values(yellow_100).unwrap().collect::<Vec<_>>();
964    assert_eq!(row[1], "Yellow");
965}
966
967#[test]
968fn cb_to_cgats() {
969    let cb = Cgats::from_file("test_files/colorburst0.txt").unwrap();
970    let cgats = cb.colorburst_to_cgats().unwrap();
971    println!("{cgats}");
972    let mut magenta = cgats.get_col_by_field(&Field::CMYK_M).unwrap().skip(21);
973    assert_eq!(magenta.next().unwrap(), &0.0);
974    assert_eq!(magenta.next().unwrap(), &5.0);
975    assert_eq!(magenta.next().unwrap(), &10.0);
976
977    let cb = Cgats::from_file("test_files/colorburst1.lin").unwrap();
978    let cgats = cb.colorburst_to_cgats().unwrap();
979    let mut green = cgats.get_col_by_field(&Field::SIXCLR_6).unwrap().skip(21 * 5);
980    assert_eq!(green.next().unwrap(), &0.0);
981    assert_eq!(green.next().unwrap(), &5.0);
982    assert_eq!(green.next().unwrap(), &10.0);
983    println!("{cgats}");
984}
985
986#[test]
987fn cgats_to_cb() {
988    let cgats = Cgats::from_file("test_files/cblin_i1.txt").unwrap();
989    let cb = cgats.to_colorburst_by_value().unwrap();
990    eprintln!("{cb:0.4}");
991    assert_eq!(cb.get_row(20).unwrap().next().unwrap(), &1.23);
992
993    let cgats = Cgats::from_file("test_files/colorburst4.txt").unwrap();
994    let cb = cgats.to_colorburst().unwrap();
995    eprintln!("{cb:0.4}");
996    assert_eq!(cb.get_row(20).unwrap().next().unwrap(), &1.06);
997}
998
999const COLORBURST_INPUT: [f32; 21] = [
1000    0.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0,
1001    55.0, 60.0, 65.0, 70.0, 75.0, 80.0, 85.0, 90.0, 95.0, 100.0,
1002];
1003
1004const COLORBURST_COLOR_TYPES: &[ColorType] = &[
1005    ColorType::Rgb,
1006    ColorType::Cmyk,
1007    ColorType::FiveClr,
1008    ColorType::SixClr,
1009    ColorType::SevenClr,
1010    ColorType::EightClr,
1011    ColorType::NClr,
1012];
1013
1014impl Default for Cgats {
1015    fn default() -> Self {
1016        Self::new()
1017    }
1018}
1019
1020impl Index<usize> for Cgats {
1021    type Output = DataPoint;
1022    /// Index into [`Cgats`] by [`usize`]
1023    /// ```
1024    /// use cgats::Cgats;
1025    ///
1026    /// let mut cgats: Cgats =
1027    /// "CGATS.17
1028    /// BEGIN_DATA_FORMAT
1029    /// RGB_R	RGB_G	RGB_B
1030    /// END_DATA_FORMAT
1031    /// BEGIN_DATA
1032    /// 0	1	2
1033    /// 126	127	128
1034    /// 253	254	255
1035    /// END_DATA"
1036    /// .parse().unwrap();
1037    ///
1038    /// assert_eq!(cgats[0], 0);
1039    /// assert_eq!(cgats[5], 128);
1040    /// assert_eq!(cgats[8], 255);
1041    /// ```
1042    fn index(&self, index: usize) -> &Self::Output {
1043        &self.data[index]
1044    }
1045}
1046
1047impl IndexMut<usize> for Cgats {
1048    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1049        &mut self.data[index]
1050    }
1051}
1052
1053impl Index<(usize, usize)> for Cgats {
1054    type Output = DataPoint;
1055    /// Index into [`Cgats`] by (column, row)
1056    ///
1057    /// ```
1058    /// use cgats::Cgats;
1059    ///
1060    /// let mut cgats: Cgats =
1061    /// "CGATS.17
1062    /// BEGIN_DATA_FORMAT
1063    /// RGB_R	RGB_G	RGB_B
1064    /// END_DATA_FORMAT
1065    /// BEGIN_DATA
1066    /// 0	1	2
1067    /// 126	127	128
1068    /// 253	254	255
1069    /// END_DATA"
1070    /// .parse().unwrap();
1071    ///
1072    /// assert_eq!(cgats[(0, 0)], 0);
1073    /// assert_eq!(cgats[(2, 1)], 128);
1074    /// assert_eq!(cgats[(2, 2)], 255);
1075    /// ```
1076    fn index(&self, index: (usize, usize)) -> &Self::Output {
1077        let (col, row) = index;
1078        let index = row * self.n_cols() + col;
1079        &self[index]
1080    }
1081}
1082
1083impl IndexMut<(usize, usize)> for Cgats {
1084    fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
1085        let (col, row) = index;
1086        let index = row * self.n_cols() + col;
1087        &mut self[index]
1088    }
1089}
1090
1091impl CgatsFmt for Cgats {
1092    fn cgats_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1093        use std::fmt::Write;
1094        self.vendor.cgats_fmt(f)?;
1095        for meta in self.metadata.iter() {
1096            meta.cgats_fmt(f)?;
1097        }
1098        if self.vendor != Vendor::ColorBurst {
1099            self.data_format.cgats_fmt(f)?;
1100        }
1101        writeln!(f, "BEGIN_DATA")?;
1102        for row in self.rows() {
1103            let result = &mut String::new();
1104            for dp in row {
1105                match f.precision() {
1106                    Some(p) => write!(result, "{dp:.p$}")?,
1107                    None => write!(result, "{dp}")?,
1108                }
1109                result.push('\t');
1110            }
1111            result.pop();
1112            writeln!(f, "{}", result)?;
1113        }
1114        writeln!(f, "END_DATA")
1115    }
1116}
1117
1118impl fmt::Display for Cgats {
1119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1120        self.cgats_fmt(f)
1121    }
1122}
1123
1124/// Representation of the DATA_FORMAT fields
1125#[derive(Debug, Clone, PartialEq, Eq)]
1126pub struct DataFormat {
1127    fields: Vec<Field>,
1128}
1129
1130use Field::*;
1131impl DataFormat {
1132    /// Creates a new DATA_FORMAT
1133    pub fn new() -> Self {
1134        DataFormat { fields: Vec::new() }
1135    }
1136
1137    /// Returns the number of fields in the DATA_FORMAT
1138    pub fn len(&self) -> usize {
1139        self.fields.len()
1140    }
1141
1142    /// Returns true if self.len() == 0
1143    pub fn is_empty(&self) -> bool {
1144        self.len() == 0
1145    }
1146
1147    /// Iterator over the [`Field`]s in a [`DataFormat`]
1148    pub fn fields(&self) -> impl Iterator<Item=&Field> {
1149        self.fields.iter()
1150    }
1151
1152    /// Mutable iterator over the [`Field`]s in a [`DataFormat`]
1153    pub fn fields_mut(&mut self) -> impl Iterator<Item=&mut Field> {
1154        self.fields.iter_mut()
1155    }
1156
1157    /// Returns column index of a given [`Field`].
1158    /// Returns [`None`] if the [`DataFormat`] does not contain the [`Field`].
1159    pub fn index_by_field(&self, field: &Field) -> Option<usize> {
1160        self.fields.iter().position(|f| f == field)
1161    }
1162
1163    /// Returns an iterator over the fields of a DATA_FORMAT
1164    pub fn iter(&self) -> impl Iterator<Item=&Field> {
1165        self.fields.iter()
1166    }
1167
1168    /// Implied ColorBurst [`DataFormat`]
1169    pub fn colorburst() -> Self {
1170        DataFormat {
1171            fields: vec![D_RED, D_GREEN, D_BLUE, D_VIS, LAB_L, LAB_A, LAB_B],
1172        }
1173    }
1174
1175    /// Check if the [`DataFormat`] contains a [`Field`]
1176    pub fn contains(&self, field: &Field) -> bool {
1177        self.fields.contains(field)
1178    }
1179}
1180
1181impl Default for DataFormat {
1182    fn default() -> Self {
1183        Self::new()
1184    }
1185}
1186
1187impl FromIterator<Field> for DataFormat {
1188    fn from_iter<I: IntoIterator<Item=Field>>(iter: I) -> Self {
1189        DataFormat {
1190            fields: iter.into_iter().collect(),
1191        }
1192    }
1193}
1194
1195/// Trait to format data for writing to CGATS files
1196pub trait CgatsFmt {
1197    /// Format data to a [`fmt::Formatter`]
1198    fn cgats_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
1199}
1200
1201impl CgatsFmt for DataFormat {
1202    fn cgats_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1203        writeln!(f, "BEGIN_DATA_FORMAT\n{}\nEND_DATA_FORMAT", self.fields.iter().join("\t"))
1204    }
1205}
1206
1207/// Trait to join strings with another string in between
1208trait Join: Iterator {
1209    /// Join an iterator of strings with another string in between
1210    fn join(&mut self, sep: &str) -> String
1211    where
1212        Self::Item: std::fmt::Display,
1213    {
1214        use std::fmt::Write;
1215        match self.next() {
1216            None => String::new(),
1217            Some(first) => {
1218                let (lower, _) = self.size_hint();
1219                let mut result = String::with_capacity(sep.len() * lower);
1220                write!(&mut result, "{}", first).expect("unable to join");
1221                for elt in self {
1222                    result.push_str(sep);
1223                    write!(&mut result, "{}", elt).expect("unable to join");
1224                }
1225                result
1226            }
1227        }
1228    }
1229}
1230
1231impl<T, D> Join for T
1232where
1233    T: Iterator<Item=D>,
1234    D: std::fmt::Display,
1235{}
1236
1237#[test]
1238fn row_col() {
1239    use Field::*;
1240    let cgats = Cgats {
1241        vendor: Vendor::Cgats,
1242        metadata: Vec::new(),
1243        data_format: DataFormat {
1244            fields: vec![
1245                RGB_R, RGB_B, RGB_B,
1246            ],
1247        },
1248        data: vec![
1249            Float(0.0), Float(1.0), Float(2.0),
1250            Float(3.0), Float(4.0), Float(5.0),
1251            Float(6.0), Float(7.0), Float(8.0),
1252        ],
1253    };
1254
1255    assert_eq!(
1256        cgats.get_row(1).unwrap().collect::<Vec<_>>(),
1257        [&Float(3.0), &Float(4.0), &Float(5.0)],
1258    );
1259    assert_eq!(
1260        cgats.get_col(1).collect::<Vec<_>>(),
1261        [&Float(1.0), &Float(4.0), &Float(7.0)],
1262    );
1263
1264    eprintln!("{}", cgats);
1265}
1266
1267#[test]
1268fn format() -> Result<()> {
1269    let cgats = Cgats::from_file("test_files/curve0.txt")?;
1270    eprintln!("{}", cgats);
1271    Ok(())
1272}
1273
1274#[test]
1275fn reindex() -> Result<()> {
1276    let mut cgats: Cgats =
1277    "CGATS.17
1278    BEGIN_DATA_FORMAT
1279    SAMPLE_ID	RGB_R	RGB_G	RGB_B
1280    END_DATA_FORMAT
1281    BEGIN_DATA
1282    1	0	0	0
1283    2	128	128	128
1284    3	255	255	255
1285    END_DATA"
1286    .parse().unwrap();
1287
1288    assert_eq!(cgats.get_col(0).collect::<Vec<_>>(), vec![&1, &2, &3]);
1289    assert_eq!(cgats.index_by_field(&SAMPLE_ID), Some(0));
1290    cgats.reindex_sample_id();
1291    assert_eq!(cgats.get_col(0).collect::<Vec<_>>(), vec![&0, &1, &2]);
1292    assert_eq!(cgats.index_by_field(&SAMPLE_ID), Some(0));
1293    cgats.reindex_sample_id_at(5);
1294    assert_eq!(cgats.get_col(0).collect::<Vec<_>>(), vec![&5, &6, &7]);
1295    assert_eq!(cgats.index_by_field(&SAMPLE_ID), Some(0));
1296
1297    let mut cgats: Cgats =
1298    "CGATS.17
1299    BEGIN_DATA_FORMAT
1300    RGB_R	RGB_G	RGB_B
1301    END_DATA_FORMAT
1302    BEGIN_DATA
1303    0	0	0
1304    128	128	128
1305    255	255	255
1306    END_DATA"
1307    .parse().unwrap();
1308
1309    assert_eq!(cgats.get_col(0).collect::<Vec<_>>(), vec![&0, &128, &255]);
1310    assert_eq!(cgats.index_by_field(&SAMPLE_ID), None);
1311    cgats.reindex_sample_id();
1312    assert_eq!(cgats.get_col(0).collect::<Vec<_>>(), vec![&0, &1, &2]);
1313    assert_eq!(cgats.index_by_field(&SAMPLE_ID), Some(0));
1314
1315    Ok(())
1316}
1317
1318#[test]
1319fn insert_row() -> Result<()> {
1320    let mut cgats: Cgats =
1321    "CGATS.17
1322    BEGIN_DATA_FORMAT
1323    SAMPLE_ID	RGB_R	RGB_G	RGB_B
1324    END_DATA_FORMAT
1325    BEGIN_DATA
1326    1	0	0	0
1327    2	128	128	128
1328    3	255	255	255
1329    END_DATA"
1330    .parse().unwrap();
1331
1332    assert_eq!(cgats.get_row(2).unwrap().collect::<Vec<_>>(), vec![&3, &255, &255, &255]);
1333
1334    let row = vec![
1335        DataPoint::Int(0), DataPoint::Int(190), DataPoint::Int(191), DataPoint::Int(192)
1336    ];
1337    cgats.insert_row(0, row.into_iter())?;
1338    eprintln!("{}", cgats);
1339    assert_eq!(cgats.get_row(0).unwrap().collect::<Vec<_>>(), vec![&0, &190, &191, &192]);
1340
1341    let row = vec![
1342        DataPoint::Int(5), DataPoint::Int(67), DataPoint::Int(68), DataPoint::Int(69)
1343    ];
1344    cgats.push_row(row.clone().into_iter())?;
1345    eprintln!("{}", cgats);
1346    assert_eq!(cgats.get_row(4).unwrap().collect::<Vec<_>>(), vec![&5, &67, &68, &69]);
1347
1348    cgats.reindex_sample_id();
1349    eprintln!("{}", cgats);
1350
1351    let mut cgats: Cgats =
1352    "CGATS.17
1353    BEGIN_DATA_FORMAT
1354    SAMPLE_ID	RGB_R	RGB_G	RGB_B
1355    END_DATA_FORMAT
1356    BEGIN_DATA
1357    END_DATA"
1358    .parse().unwrap();
1359
1360    dbg!(cgats.n_rows(), cgats.n_cols());
1361    eprintln!("{}", cgats);
1362    cgats.push_row(row.into_iter())?;
1363    eprintln!("{}", cgats);
1364    assert_eq!(cgats.get_row(0).unwrap().collect::<Vec<_>>(), vec![&5, &67, &68, &69]);
1365
1366    Ok(())
1367}