use crate::bitpix::Bitpix;
use crate::data::ImageData;
use crate::data::Scaling;
use crate::data::shape_product;
use crate::error::FitsError;
use crate::error::Result;
use crate::header::Header;
use crate::keyword::key;
#[derive(Debug, Clone)]
pub struct RandomGroups {
pub parameter_names: Vec<String>,
pub group_shape: Vec<usize>,
pub gcount: usize,
pub pcount: usize,
bitpix: Bitpix,
array_scaling: Scaling,
param_scaling: Vec<ParamScale>,
samples: ImageData,
}
#[derive(Debug, Clone, Copy)]
struct ParamScale {
pscal: f64,
pzero: f64,
}
impl RandomGroups {
pub(crate) fn from_data(header: &Header, data: &[u8]) -> Result<RandomGroups> {
let bitpix = header.bitpix()?;
let axes = header.axes()?;
let group_shape: Vec<usize> = axes.iter().skip(1).copied().collect();
let pcount = match header.get_integer("PCOUNT") {
Some(p) if p < 0 => return Err(FitsError::KeywordOutOfRange { name: "PCOUNT" }),
Some(p) => p as usize,
None => 0,
};
let gcount = match header.get_integer("GCOUNT") {
Some(g) if g < 1 => return Err(FitsError::KeywordOutOfRange { name: "GCOUNT" }),
Some(g) => g as usize,
None => 1,
};
let mut parameter_names = Vec::with_capacity(pcount);
let mut param_scaling = Vec::with_capacity(pcount);
for j in 1..=pcount {
parameter_names.push(
header
.get_text(key!("PTYPE{j}").as_str())
.unwrap_or("")
.to_string(),
);
param_scaling.push(ParamScale {
pscal: header.get_real(key!("PSCAL{j}").as_str()).unwrap_or(1.0),
pzero: header.get_real(key!("PZERO{j}").as_str()).unwrap_or(0.0),
});
}
let samples = ImageData::decode(data, bitpix);
let groups = RandomGroups {
parameter_names,
group_shape,
gcount,
pcount,
bitpix,
array_scaling: Scaling::from_header(header),
param_scaling,
samples,
};
let expected = groups.gcount * groups.group_len();
if groups.samples.len() != expected {
return Err(FitsError::DataSizeMismatch {
expected,
got: groups.samples.len(),
});
}
Ok(groups)
}
pub fn bitpix(&self) -> Bitpix {
self.bitpix
}
pub fn array_len(&self) -> usize {
shape_product(&self.group_shape)
}
pub fn parameters_physical(&self, group: usize) -> Vec<f64> {
let base = group * self.group_len();
(0..self.pcount)
.map(|j| {
let ParamScale { pscal, pzero } = self.param_scaling[j];
pzero + pscal * elem_f64(&self.samples, base + j)
})
.collect()
}
pub fn parameter_physical(&self, group: usize, name: &str) -> Option<f64> {
let base = group * self.group_len();
let mut sum = 0.0;
let mut found = false;
for j in 0..self.pcount {
if self.parameter_names[j] == name {
found = true;
let ParamScale { pscal, pzero } = self.param_scaling[j];
sum += pzero + pscal * elem_f64(&self.samples, base + j);
}
}
found.then_some(sum)
}
pub fn array_physical(&self, group: usize) -> Vec<f64> {
let base = group * self.group_len() + self.pcount;
(0..self.array_len())
.map(|k| {
self.array_scaling.bzero
+ self.array_scaling.bscale * elem_f64(&self.samples, base + k)
})
.collect()
}
fn group_len(&self) -> usize {
self.pcount + self.array_len()
}
}
fn elem_f64(samples: &ImageData, i: usize) -> f64 {
match samples {
ImageData::U8(v) => v[i] as f64,
ImageData::I16(v) => v[i] as f64,
ImageData::I32(v) => v[i] as f64,
ImageData::I64(v) => v[i] as f64,
ImageData::F32(v) => v[i] as f64,
ImageData::F64(v) => v[i],
}
}
#[cfg(test)]
mod tests;