use std::os::raw::{c_int, c_long, c_void};
use fitsio::FitsFile;
use fitsio::sys::{self, LONGLONG};
use crate::error::{AtfitsError, Result, ffcall};
mod sealed {
pub trait Sealed {}
}
pub trait Pixel: sealed::Sealed + Copy + Default + Send + Sync + 'static {
const DATATYPE: c_int;
const BITPIX: i64;
fn to_be(self) -> Self;
}
macro_rules! impl_pixel {
($t:ty, $code:ident, $bitpix:expr, $v:ident => $be:expr) => {
impl sealed::Sealed for $t {}
impl Pixel for $t {
const DATATYPE: c_int = sys::$code as c_int;
const BITPIX: i64 = $bitpix;
fn to_be(self) -> Self {
let $v = self;
$be
}
}
};
}
impl_pixel!(u8, TBYTE, 8, v => v);
impl_pixel!(i16, TSHORT, 16, v => v.to_be());
impl_pixel!(i32, TINT, 32, v => v.to_be());
impl_pixel!(i64, TLONGLONG, 64, v => v.to_be());
impl_pixel!(f32, TFLOAT, -32, v => f32::from_bits(v.to_bits().to_be()));
impl_pixel!(f64, TDOUBLE, -64, v => f64::from_bits(v.to_bits().to_be()));
pub fn swap_to_be<T: Pixel>(data: &mut [T]) {
for v in data {
*v = v.to_be();
}
}
pub fn as_bytes<T: Pixel>(data: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(data.as_ptr().cast(), std::mem::size_of_val(data)) }
}
pub fn disable_scaling(fptr: &mut FitsFile) -> Result<()> {
ffcall(|s| unsafe { sys::ffpscl(fptr.as_raw(), 1.0, 0.0, s) })
}
pub fn image_shape(fptr: &mut FitsFile) -> Result<Vec<usize>> {
let mut naxis: c_int = 0;
ffcall(|s| unsafe { sys::ffgidm(fptr.as_raw(), &mut naxis, s) })?;
let mut naxes: Vec<LONGLONG> = vec![0; naxis as usize];
ffcall(|s| unsafe { sys::ffgiszll(fptr.as_raw(), naxis, naxes.as_mut_ptr(), s) })?;
Ok(naxes.into_iter().map(|n| n as usize).collect())
}
pub fn read_pixels<T: Pixel>(
fptr: &mut FitsFile,
ranges: Option<&[(usize, usize)]>,
nulval: Option<T>,
out: &mut Vec<T>,
) -> Result<()> {
let shape = image_shape(fptr)?;
out.clear();
out.resize(subset_len(&shape, ranges)?, T::default());
read_checked(fptr, &shape, ranges, nulval, out)
}
pub fn read_pixels_into<T: Pixel>(
fptr: &mut FitsFile,
ranges: Option<&[(usize, usize)]>,
nulval: Option<T>,
out: &mut [T],
) -> Result<()> {
let shape = image_shape(fptr)?;
let n = subset_len(&shape, ranges)?;
if out.len() != n {
return Err(AtfitsError::Other(format!(
"buffer holds {} pixels, selection has {n}",
out.len()
)));
}
read_checked(fptr, &shape, ranges, nulval, out)
}
fn subset_len(shape: &[usize], ranges: Option<&[(usize, usize)]>) -> Result<usize> {
let Some(ranges) = ranges else {
return Ok(if shape.is_empty() {
0
} else {
shape.iter().product()
});
};
if ranges.len() != shape.len() {
return Err(AtfitsError::Other(format!(
"{} ranges for a {}-axis image",
ranges.len(),
shape.len()
)));
}
for (i, (&(a, b), &n)) in ranges.iter().zip(shape).enumerate() {
if a > b || b > n {
return Err(AtfitsError::Other(format!(
"range {a}..{b} is outside NAXIS{}={n}",
i + 1
)));
}
}
Ok(ranges.iter().map(|(a, b)| b - a).product())
}
fn read_checked<T: Pixel>(
fptr: &mut FitsFile,
shape: &[usize],
ranges: Option<&[(usize, usize)]>,
nulval: Option<T>,
out: &mut [T],
) -> Result<()> {
let whole = ranges.is_none_or(|r| r.iter().zip(shape).all(|(&(a, b), &n)| a == 0 && b == n));
if whole {
return read_flat(fptr, 0, nulval, out);
}
if out.is_empty() {
return Ok(());
}
let r = ranges.unwrap_or_default();
let mut first: Vec<c_long> = r.iter().map(|&(a, _)| a as c_long + 1).collect();
let mut last: Vec<c_long> = r.iter().map(|&(_, b)| b as c_long).collect();
let mut inc: Vec<c_long> = vec![1; r.len()];
let mut nulval = nulval;
let mut anynul: c_int = 0;
ffcall(|s| unsafe {
sys::ffgsv(
fptr.as_raw(),
T::DATATYPE,
first.as_mut_ptr(),
last.as_mut_ptr(),
inc.as_mut_ptr(),
nul_ptr(&mut nulval),
out.as_mut_ptr().cast(),
&mut anynul,
s,
)
})
}
fn nul_ptr<T>(nulval: &mut Option<T>) -> *mut c_void {
nulval
.as_mut()
.map_or(std::ptr::null_mut(), |v| (v as *mut T).cast())
}
pub(crate) fn read_flat<T: Pixel>(
fptr: &mut FitsFile,
start: usize,
nulval: Option<T>,
out: &mut [T],
) -> Result<()> {
if out.is_empty() {
return Ok(());
}
let mut nulval = nulval;
let mut anynul: c_int = 0;
ffcall(|s| unsafe {
sys::ffgpv(
fptr.as_raw(),
T::DATATYPE,
start as LONGLONG + 1,
out.len() as LONGLONG,
nul_ptr(&mut nulval),
out.as_mut_ptr().cast(),
&mut anynul,
s,
)
})
}
pub fn write_pixels<T: Pixel>(fptr: &mut FitsFile, start: usize, data: &[T]) -> Result<()> {
let shape = image_shape(fptr)?;
let npix = subset_len(&shape, None)?;
if start.checked_add(data.len()).is_none_or(|end| end > npix) {
return Err(AtfitsError::Other(format!(
"{} pixels at offset {start} overrun the image ({npix} pixels)",
data.len()
)));
}
if data.is_empty() {
return Ok(());
}
ffcall(|s| unsafe {
sys::ffppr(
fptr.as_raw(),
T::DATATYPE,
start as LONGLONG + 1,
data.len() as LONGLONG,
data.as_ptr() as *mut c_void,
s,
)
})
}