1#![warn(missing_docs)]
2#![allow(clippy::tabs_in_doc_comments)]
3macro_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
31pub type BoxErr = Box<dyn std::error::Error>;
33pub type Result<T> = std::result::Result<T, BoxErr>;
35
36pub use DataPoint::*;
37#[derive(Debug, Clone)]
39pub enum DataPoint {
40 Alpha(String),
42 Float(f32),
44 Int(i32),
46}
47
48impl DataPoint {
49 pub fn new_alpha<S: ToString>(alpha: S) -> Self {
51 Alpha(alpha.to_string())
52 }
53
54 pub fn new_float<F: Into<f32>>(float: F) -> Self {
56 Float(float.into())
57 }
58
59 pub fn new_int<I: Into<i32>>(int: I) -> Self {
61 Int(int.into())
62 }
63
64 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum MetaData {
120 KeyVal {
122 key: String,
124 val: String,
126 },
127 Comment(String),
129 Blank,
131}
132
133impl MetaData {
134 pub fn key(&self) -> Option<&str> {
136 if let MetaData::KeyVal{ key, .. } = self {
137 Some(key)
138 } else {
139 None
140 }
141 }
142
143 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 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#[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 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 pub fn with_capacity(cap: usize) -> Self {
237 Cgats {
238 data: Vec::with_capacity(cap),
239 ..Self::new()
240 }
241 }
242
243 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 pub fn vendor(&self) -> &Vendor {
275 &self.vendor
276 }
277
278 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 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 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 pub fn iter(&self) -> impl Iterator<Item=&DataPoint> {
323 self.data.iter()
324 }
325
326 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 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 pub fn fields(&self) -> impl Iterator<Item=&Field> {
402 self.data_format.fields()
403 }
404
405 pub fn fields_mut(&mut self) -> impl Iterator<Item=&mut Field> {
407 self.data_format.fields_mut()
408 }
409
410 pub fn iter_mut(&mut self) -> impl Iterator<Item=&mut DataPoint> {
412 self.data.iter_mut()
413 }
414
415 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 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 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
430 Ok(write!(writer, "{}", self)?)
431 }
432
433 pub fn len(&self) -> usize {
435 self.data.len()
436 }
437
438 pub fn is_empty(&self) -> bool {
440 self.len() == 0
441 }
442
443 pub fn n_rows(&self) -> usize {
445 self.data.len().checked_div(self.n_cols()).unwrap_or(0)
446 }
447
448 pub fn n_cols(&self) -> usize {
450 self.data_format.len()
451 }
452
453 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 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 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 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 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 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 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 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 pub fn get_col(&self, col: usize) -> impl Iterator<Item=&DataPoint> {
618 self.iter().skip(col).step_by(self.n_cols())
619 }
620
621 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 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 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 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 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 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 pub fn has_color_type(&self, color_type: &ColorType) -> bool {
683 self.color_types().contains(color_type)
684 }
685
686 pub fn reindex_sample_id(&mut self) {
688 self.reindex_sample_id_at(0)
689 }
690
691 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 pub fn index_by_field(&self, field: &Field) -> Option<usize> {
707 self.data_format.index_by_field(field)
708 }
709
710 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 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 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 pub fn push_row(&mut self, iter: impl Iterator<Item=DataPoint>) -> Result<()> {
760 self.insert_row(self.n_rows(), iter)
761 }
762
763 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 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 pub fn to_colorburst(&self) -> Result<Self> {
792 self.to_colorburst_by_value()
793 .or_else(|_|self.to_colorburst_in_order())
794 }
795
796 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
1126pub struct DataFormat {
1127 fields: Vec<Field>,
1128}
1129
1130use Field::*;
1131impl DataFormat {
1132 pub fn new() -> Self {
1134 DataFormat { fields: Vec::new() }
1135 }
1136
1137 pub fn len(&self) -> usize {
1139 self.fields.len()
1140 }
1141
1142 pub fn is_empty(&self) -> bool {
1144 self.len() == 0
1145 }
1146
1147 pub fn fields(&self) -> impl Iterator<Item=&Field> {
1149 self.fields.iter()
1150 }
1151
1152 pub fn fields_mut(&mut self) -> impl Iterator<Item=&mut Field> {
1154 self.fields.iter_mut()
1155 }
1156
1157 pub fn index_by_field(&self, field: &Field) -> Option<usize> {
1160 self.fields.iter().position(|f| f == field)
1161 }
1162
1163 pub fn iter(&self) -> impl Iterator<Item=&Field> {
1165 self.fields.iter()
1166 }
1167
1168 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 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
1195pub trait CgatsFmt {
1197 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
1207trait Join: Iterator {
1209 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}