1use crate::bitpix::Bitpix;
9use crate::data::ImageData;
10use crate::data::Scaling;
11use crate::error::FitsError;
12use crate::error::Result;
13use crate::header::Header;
14use crate::keyword::key;
15
16#[derive(Debug, Clone)]
18pub struct RandomGroups {
19 pub parameter_names: Vec<String>,
21 pub group_shape: Vec<usize>,
24 pub gcount: usize,
25 pub pcount: usize,
26 pub bitpix: Bitpix,
27 array_scaling: Scaling,
28 param_scaling: Vec<ParamScale>,
30 samples: ImageData,
32}
33
34#[derive(Debug, Clone, Copy)]
37struct ParamScale {
38 pscal: f64,
39 pzero: f64,
40}
41
42impl RandomGroups {
43 pub(crate) fn from_data(header: &Header, data: &[u8]) -> Result<RandomGroups> {
44 let bitpix = header.bitpix()?;
45 let axes = header.axes()?;
46 let group_shape: Vec<usize> = axes.iter().skip(1).copied().collect();
48 let pcount = match header.get_integer("PCOUNT") {
49 Some(p) if p < 0 => return Err(FitsError::KeywordOutOfRange { name: "PCOUNT" }),
50 Some(p) => p as usize,
51 None => 0,
52 };
53 let gcount = match header.get_integer("GCOUNT") {
54 Some(g) if g < 1 => return Err(FitsError::KeywordOutOfRange { name: "GCOUNT" }),
55 Some(g) => g as usize,
56 None => 1,
57 };
58
59 let mut parameter_names = Vec::with_capacity(pcount);
60 let mut param_scaling = Vec::with_capacity(pcount);
61 for j in 1..=pcount {
62 parameter_names.push(
63 header
64 .get_text(key!("PTYPE{j}").as_str())
65 .unwrap_or("")
66 .to_string(),
67 );
68 param_scaling.push(ParamScale {
69 pscal: header.get_real(key!("PSCAL{j}").as_str()).unwrap_or(1.0),
70 pzero: header.get_real(key!("PZERO{j}").as_str()).unwrap_or(0.0),
71 });
72 }
73
74 let samples = ImageData::decode(data, bitpix);
75 let groups = RandomGroups {
76 parameter_names,
77 group_shape,
78 gcount,
79 pcount,
80 bitpix,
81 array_scaling: Scaling::from_header(header),
82 param_scaling,
83 samples,
84 };
85 let expected = groups.gcount * groups.group_len();
86 if groups.samples.len() != expected {
87 return Err(FitsError::DataSizeMismatch {
88 expected,
89 got: groups.samples.len(),
90 });
91 }
92 Ok(groups)
93 }
94
95 pub fn array_len(&self) -> usize {
101 self.group_shape.iter().product()
102 }
103
104 pub fn parameters_physical(&self, group: usize) -> Vec<f64> {
106 let base = group * self.group_len();
107 (0..self.pcount)
108 .map(|j| {
109 let ParamScale { pscal, pzero } = self.param_scaling[j];
110 pzero + pscal * elem_f64(&self.samples, base + j)
111 })
112 .collect()
113 }
114
115 pub fn parameter_physical(&self, group: usize, name: &str) -> Option<f64> {
121 let base = group * self.group_len();
122 let mut sum = 0.0;
123 let mut found = false;
124 for j in 0..self.pcount {
125 if self.parameter_names[j] == name {
126 found = true;
127 let ParamScale { pscal, pzero } = self.param_scaling[j];
128 sum += pzero + pscal * elem_f64(&self.samples, base + j);
129 }
130 }
131 found.then_some(sum)
132 }
133
134 pub fn array_physical(&self, group: usize) -> Vec<f64> {
136 let base = group * self.group_len() + self.pcount;
137 (0..self.array_len())
138 .map(|k| {
139 self.array_scaling.bzero
140 + self.array_scaling.bscale * elem_f64(&self.samples, base + k)
141 })
142 .collect()
143 }
144
145 fn group_len(&self) -> usize {
146 self.pcount + self.array_len()
147 }
148}
149
150fn elem_f64(samples: &ImageData, i: usize) -> f64 {
152 match samples {
153 ImageData::U8(v) => v[i] as f64,
154 ImageData::I16(v) => v[i] as f64,
155 ImageData::I32(v) => v[i] as f64,
156 ImageData::I64(v) => v[i] as f64,
157 ImageData::F32(v) => v[i] as f64,
158 ImageData::F64(v) => v[i],
159 }
160}
161
162#[cfg(test)]
163mod tests;