Skip to main content

fits_io/header/
bayer_pattern.rs

1use std::error::Error;
2
3/// The camera bayer pattern
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum BayerPattern {
6    /// Red, green on the first row; green, blue on the second.
7    RGGB,
8    /// Blue, green on the first row; green, red on the second.
9    BGGR,
10    /// Green, red on the first row; blue, green on the second.
11    GRBG,
12    /// Green, blue on the first row; red, green on the second.
13    GBRG,
14}
15
16/// Where each colour sits inside a Bayer tile, as `(x, y)` offsets from the top
17/// left of the 2x2 tile.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SuperpixelOffsets {
20    /// Where the red pixel sits in the group.
21    pub red: (u32, u32),
22    /// The two green samples, which are averaged together.
23    pub green: [(u32, u32); 2],
24    /// Where the blue pixel sits in the group.
25    pub blue: (u32, u32),
26}
27
28impl BayerPattern {
29    /// The position of each colour within this pattern's 2x2 tile.
30    ///
31    /// The pattern is named for its tile read left to right, top to bottom, so
32    /// `RGGB` puts red at `(0, 0)`, green at `(1, 0)` and `(0, 1)`, and blue at
33    /// `(1, 1)`.
34    pub fn superpixel_offsets(&self) -> SuperpixelOffsets {
35        match self {
36            BayerPattern::RGGB => SuperpixelOffsets {
37                red: (0, 0),
38                green: [(1, 0), (0, 1)],
39                blue: (1, 1),
40            },
41            BayerPattern::BGGR => SuperpixelOffsets {
42                red: (1, 1),
43                green: [(1, 0), (0, 1)],
44                blue: (0, 0),
45            },
46            BayerPattern::GRBG => SuperpixelOffsets {
47                red: (1, 0),
48                green: [(0, 0), (1, 1)],
49                blue: (0, 1),
50            },
51            BayerPattern::GBRG => SuperpixelOffsets {
52                red: (0, 1),
53                green: [(0, 0), (1, 1)],
54                blue: (1, 0),
55            },
56        }
57    }
58}
59
60impl From<BayerPattern> for String {
61    fn from(pattern: BayerPattern) -> Self {
62        match pattern {
63            BayerPattern::RGGB => "RGGB".to_string(),
64            BayerPattern::BGGR => "BGGR".to_string(),
65            BayerPattern::GRBG => "GRBG".to_string(),
66            BayerPattern::GBRG => "GBRG".to_string(),
67        }
68    }
69}
70
71impl TryFrom<String> for BayerPattern {
72    type Error = Box<dyn Error + Send + Sync>;
73
74    fn try_from(value: String) -> Result<Self, Self::Error> {
75        match value.to_lowercase().as_str() {
76            "rggb" => Ok(BayerPattern::RGGB),
77            "bggr" => Ok(BayerPattern::BGGR),
78            "grbg" => Ok(BayerPattern::GRBG),
79            "gbrg" => Ok(BayerPattern::GBRG),
80            _ => Err(From::from(format!("Invalid BAYERPAT value: {}", value))),
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::BayerPattern;
88
89    /// Reads a pattern's tile back out of its offsets, top left to bottom right.
90    fn tile(pattern: BayerPattern) -> String {
91        let offsets = pattern.superpixel_offsets();
92        let mut tile = [' '; 4];
93
94        let index = |(x, y): (u32, u32)| (y * 2 + x) as usize;
95        tile[index(offsets.red)] = 'R';
96        tile[index(offsets.green[0])] = 'G';
97        tile[index(offsets.green[1])] = 'G';
98        tile[index(offsets.blue)] = 'B';
99
100        tile.iter().collect()
101    }
102
103    #[test]
104    fn every_pattern_lays_out_the_tile_its_name_describes() {
105        assert_eq!(tile(BayerPattern::RGGB), "RGGB");
106        assert_eq!(tile(BayerPattern::BGGR), "BGGR");
107        assert_eq!(tile(BayerPattern::GRBG), "GRBG");
108        assert_eq!(tile(BayerPattern::GBRG), "GBRG");
109    }
110
111    #[test]
112    fn every_position_in_the_tile_is_used_exactly_once() {
113        for pattern in [
114            BayerPattern::RGGB,
115            BayerPattern::BGGR,
116            BayerPattern::GRBG,
117            BayerPattern::GBRG,
118        ] {
119            let offsets = pattern.superpixel_offsets();
120            let mut positions = vec![
121                offsets.red,
122                offsets.green[0],
123                offsets.green[1],
124                offsets.blue,
125            ];
126            positions.sort();
127
128            assert_eq!(
129                positions,
130                vec![(0, 0), (0, 1), (1, 0), (1, 1)],
131                "{:?} does not cover its tile",
132                pattern
133            );
134        }
135    }
136}