Skip to main content

fits_io/image/
group.rs

1use crate::header::Header;
2
3/// One group of a random-groups HDU.
4///
5/// The convention predates image extensions: rather than one array, the primary
6/// HDU holds a run of groups, each carrying a few parameters — a time, a
7/// baseline, a coordinate — followed by the array those parameters describe. It
8/// is mostly met in older radio interferometry data.
9#[derive(Debug, Clone)]
10pub struct Group {
11    parameters: Vec<f64>,
12    names: Vec<String>,
13    data: Vec<f64>,
14}
15
16impl Group {
17    pub(crate) fn new(parameters: Vec<f64>, names: Vec<String>, data: Vec<f64>) -> Self {
18        Self {
19            parameters,
20            names,
21            data,
22        }
23    }
24
25    /// This group's parameters, in physical units.
26    ///
27    /// PSCALn and PZEROn have already been applied, so these are the values the
28    /// parameters stand for rather than the numbers stored.
29    pub fn parameters(&self) -> &[f64] {
30        &self.parameters
31    }
32
33    /// The parameter named by a PTYPEn card.
34    ///
35    /// The standard allows a parameter to be split across several entries that
36    /// share a name, so that a value needing more precision than the array's
37    /// type offers can be summed from its parts. Those parts are added here.
38    pub fn parameter(&self, name: &str) -> Option<f64> {
39        let mut total = None;
40
41        for (index, parameter_name) in self.names.iter().enumerate() {
42            if parameter_name == name {
43                total = Some(total.unwrap_or(0.0) + self.parameters.get(index).copied()?);
44            }
45        }
46
47        total
48    }
49
50    /// The names of this group's parameters, from the PTYPEn cards.
51    pub fn parameter_names(&self) -> &[String] {
52        &self.names
53    }
54
55    /// This group's array, in physical units.
56    pub fn data(&self) -> &[f64] {
57        &self.data
58    }
59}
60
61/// Reads the parameters and array of one group out of its bytes.
62pub(crate) fn decode_group(header: &Header, bytes: &[u8]) -> Option<Group> {
63    let bitpix = header.bitpix()?;
64    let width = bitpix.byte_size();
65
66    let count = header.pcount().unwrap_or(0).max(0) as usize;
67    let zero = header.bzero_or_default();
68    let scale = header.bscale_or_default();
69
70    let mut parameters = Vec::with_capacity(count);
71    let mut names = Vec::with_capacity(count);
72
73    for index in 0..count {
74        let raw = bitpix.read_be(bytes.get(index * width..)?)?;
75
76        // A parameter carries its own scaling, separate from the array's.
77        let parameter_scale = header.parameter_scaling_factor(index).unwrap_or(1.0);
78        let parameter_zero = header.parameter_scaling_zero_point(index).unwrap_or(0.0);
79
80        parameters.push(parameter_zero + parameter_scale * raw);
81        names.push(header.parameter_type(index).unwrap_or_default().to_string());
82    }
83
84    let array = bytes.get(count * width..)?;
85    let data = array
86        .chunks_exact(width)
87        .filter_map(|raw| bitpix.read_be(raw))
88        .map(|raw| zero + scale * raw)
89        .collect();
90
91    Some(Group::new(parameters, names, data))
92}