1use std::os::raw::{c_int, c_void};
3
4use fitsio::FitsFile;
5use fitsio::hdu::HduInfo;
6use fitsio::images::ImageType;
7use fitsio::sys::{self, LONGLONG};
8
9use crate::error::{AtfitsError, Result, ffcall};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PixelType {
14 F32,
15 F64,
16}
17
18impl PixelType {
19 pub fn from_bitpix(bitpix: i64) -> Self {
21 if bitpix == -64 {
22 PixelType::F64
23 } else {
24 PixelType::F32
25 }
26 }
27}
28
29pub trait CubeElem: Copy + Send + Sync + 'static {
35 const BITPIX: i64;
37 fn read_full(fptr: &mut FitsFile) -> Result<Vec<Self>>;
39 fn read_section(fptr: &mut FitsFile, start: usize, end: usize) -> Result<Vec<Self>>;
41 fn write_section(fptr: &mut FitsFile, start: usize, end: usize, data: &[Self]) -> Result<()>;
43}
44
45pub fn read_plane<T: CubeElem>(
47 fptr: &mut FitsFile,
48 plane_len: usize,
49 index: usize,
50) -> Result<Vec<T>> {
51 let (start, end) = plane_range(plane_len, index)?;
52 T::read_section(fptr, start, end)
53}
54
55pub fn write_plane<T: CubeElem>(fptr: &mut FitsFile, index: usize, data: &[T]) -> Result<()> {
57 let (start, end) = plane_range(data.len(), index)?;
58 T::write_section(fptr, start, end, data)
59}
60
61fn plane_range(plane_len: usize, index: usize) -> Result<(usize, usize)> {
62 plane_len
63 .checked_mul(index)
64 .and_then(|start| Some((start, start.checked_add(plane_len)?)))
65 .ok_or_else(|| AtfitsError::Other(format!("plane {index} overflows usize")))
66}
67
68fn primary_image(fptr: &mut FitsFile) -> Result<(usize, bool)> {
70 match fptr.primary_hdu()?.info {
71 HduInfo::ImageInfo { shape, image_type } => {
72 let npix = if shape.is_empty() {
73 0
74 } else {
75 shape.iter().product()
76 };
77 let integer = !matches!(image_type, ImageType::Float | ImageType::Double);
78 Ok((npix, integer))
79 }
80 _ => Err(AtfitsError::Other("primary HDU is not an image".into())),
81 }
82}
83
84fn check_range(start: usize, end: usize, npix: usize) -> Result<()> {
85 if start > end || end > npix {
86 return Err(AtfitsError::Other(format!(
87 "pixel range {start}..{end} is outside the image ({npix} pixels)"
88 )));
89 }
90 Ok(())
91}
92
93macro_rules! impl_cube_elem {
94 ($t:ty, $bitpix:expr, $code:expr) => {
95 impl CubeElem for $t {
96 const BITPIX: i64 = $bitpix;
97
98 fn read_full(fptr: &mut FitsFile) -> Result<Vec<Self>> {
99 let (npix, _) = primary_image(fptr)?;
100 Self::read_section(fptr, 0, npix)
101 }
102
103 fn read_section(fptr: &mut FitsFile, start: usize, end: usize) -> Result<Vec<Self>> {
104 let (npix, _) = primary_image(fptr)?;
105 check_range(start, end, npix)?;
106 let mut out: Vec<$t> = vec![0.0; end - start];
107 let mut nulval = <$t>::NAN;
108 let mut anynul: c_int = 0;
109 ffcall(|s| unsafe {
110 sys::ffgpv(
111 fptr.as_raw(),
112 $code as c_int,
113 start as LONGLONG + 1,
114 out.len() as LONGLONG,
115 &mut nulval as *mut $t as *mut c_void,
116 out.as_mut_ptr() as *mut c_void,
117 &mut anynul,
118 s,
119 )
120 })?;
121 Ok(out)
122 }
123
124 fn write_section(
125 fptr: &mut FitsFile,
126 start: usize,
127 end: usize,
128 data: &[Self],
129 ) -> Result<()> {
130 let (npix, integer) = primary_image(fptr)?;
131 check_range(start, end, npix)?;
132 if data.len() != end - start {
133 return Err(AtfitsError::Other(format!(
134 "pixel range {start}..{end} needs {} values, got {}",
135 end - start,
136 data.len()
137 )));
138 }
139 if !(integer && data.iter().any(|v| v.is_nan())) {
140 return ffcall(|s| unsafe {
142 sys::ffppr(
143 fptr.as_raw(),
144 $code as c_int,
145 start as LONGLONG + 1,
146 data.len() as LONGLONG,
147 data.as_ptr() as *mut c_void,
148 s,
149 )
150 });
151 }
152 let mut nulval = <$t>::NEG_INFINITY;
154 let mut buf: Vec<$t> = data
155 .iter()
156 .map(|&v| if v.is_nan() { nulval } else { v })
157 .collect();
158 ffcall(|s| unsafe {
159 sys::ffppn(
160 fptr.as_raw(),
161 $code as c_int,
162 start as LONGLONG + 1,
163 buf.len() as LONGLONG,
164 buf.as_mut_ptr() as *mut c_void,
165 &mut nulval as *mut $t as *mut c_void,
166 s,
167 )
168 })
169 }
170 }
171 };
172}
173impl_cube_elem!(f32, -32, sys::TFLOAT);
174impl_cube_elem!(f64, -64, sys::TDOUBLE);