use crate::bitpix::Bitpix;
use crate::endian::decode_be;
use crate::endian::decode_be_into_slice;
use crate::endian::extend_be;
use crate::header::Header;
#[derive(Debug, Clone, PartialEq)]
pub enum ImageData {
U8(Vec<u8>),
I16(Vec<i16>),
I32(Vec<i32>),
I64(Vec<i64>),
F32(Vec<f32>),
F64(Vec<f64>),
}
pub(crate) fn shape_product(shape: &[usize]) -> usize {
if shape.is_empty() {
0
} else {
shape.iter().product()
}
}
impl ImageData {
pub fn bitpix(&self) -> Bitpix {
match self {
ImageData::U8(_) => Bitpix::U8,
ImageData::I16(_) => Bitpix::I16,
ImageData::I32(_) => Bitpix::I32,
ImageData::I64(_) => Bitpix::I64,
ImageData::F32(_) => Bitpix::F32,
ImageData::F64(_) => Bitpix::F64,
}
}
pub fn len(&self) -> usize {
match self {
ImageData::U8(v) => v.len(),
ImageData::I16(v) => v.len(),
ImageData::I32(v) => v.len(),
ImageData::I64(v) => v.len(),
ImageData::F32(v) => v.len(),
ImageData::F64(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn decode(bytes: &[u8], bitpix: Bitpix) -> ImageData {
assert_eq!(
bytes.len() % bitpix.elem_size(),
0,
"data length must be a whole number of {bitpix:?} elements"
);
match bitpix {
Bitpix::U8 => ImageData::U8(bytes.to_vec()),
Bitpix::I16 => ImageData::I16(decode_be(bytes, i16::from_be_bytes)),
Bitpix::I32 => ImageData::I32(decode_be(bytes, i32::from_be_bytes)),
Bitpix::I64 => ImageData::I64(decode_be(bytes, i64::from_be_bytes)),
Bitpix::F32 => ImageData::F32(decode_be(bytes, f32::from_be_bytes)),
Bitpix::F64 => ImageData::F64(decode_be(bytes, f64::from_be_bytes)),
}
}
pub(crate) fn encode_into(&self, out: &mut Vec<u8>) {
match self {
ImageData::U8(v) => out.extend_from_slice(v),
ImageData::I16(v) => extend_be(out, v, i16::to_be_bytes),
ImageData::I32(v) => extend_be(out, v, i32::to_be_bytes),
ImageData::I64(v) => extend_be(out, v, i64::to_be_bytes),
ImageData::F32(v) => extend_be(out, v, f32::to_be_bytes),
ImageData::F64(v) => extend_be(out, v, f64::to_be_bytes),
}
}
fn physical(&self, scaling: &Scaling) -> Vec<f64> {
self.physical_as(scaling)
}
fn physical_f32(&self, scaling: &Scaling) -> Vec<f32> {
self.physical_as(scaling)
}
fn physical_as<O: PhysicalOut>(&self, scaling: &Scaling) -> Vec<O> {
let Scaling {
bscale,
bzero,
blank,
} = *scaling;
let scale = |x: f64| O::from_f64(bzero + bscale * x);
match self {
ImageData::U8(v) => scale_ints(v, blank, scale),
ImageData::I16(v) => scale_ints(v, blank, scale),
ImageData::I32(v) => scale_ints(v, blank, scale),
ImageData::I64(v) => scale_ints(v, blank, scale),
ImageData::F32(v) => v.iter().map(|&x| scale(x as f64)).collect(),
ImageData::F64(v) => v.iter().map(|&x| scale(x)).collect(),
}
}
pub(crate) fn unsigned(&self, scaling: &Scaling) -> Option<UnsignedView> {
if scaling.blank.is_some() {
return None;
}
match (self, SampleType::from_scaling(self.bitpix(), scaling)) {
(ImageData::U8(v), SampleType::I8) => Some(UnsignedView::from_signed_byte(v)),
(ImageData::I16(v), SampleType::U16) => Some(UnsignedView::from_offset_i16(v)),
(ImageData::I32(v), SampleType::U32) => Some(UnsignedView::from_offset_i32(v)),
(ImageData::I64(v), SampleType::U64) => Some(UnsignedView::from_offset_i64(v)),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ImageView<'a> {
U8(&'a [u8]),
I16(&'a [i16]),
I32(&'a [i32]),
I64(&'a [i64]),
F32(&'a [f32]),
F64(&'a [f64]),
}
impl ImageView<'_> {
pub fn bitpix(&self) -> Bitpix {
match self {
ImageView::U8(_) => Bitpix::U8,
ImageView::I16(_) => Bitpix::I16,
ImageView::I32(_) => Bitpix::I32,
ImageView::I64(_) => Bitpix::I64,
ImageView::F32(_) => Bitpix::F32,
ImageView::F64(_) => Bitpix::F64,
}
}
pub fn len(&self) -> usize {
match self {
ImageView::U8(v) => v.len(),
ImageView::I16(v) => v.len(),
ImageView::I32(v) => v.len(),
ImageView::I64(v) => v.len(),
ImageView::F32(v) => v.len(),
ImageView::F64(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub(crate) fn swap_into_words(src: &[u8], bitpix: Bitpix, words: &mut Vec<u64>) {
let count = src.len() / bitpix.elem_size();
words.resize(src.len().div_ceil(8), 0);
let p = words.as_mut_ptr() as *mut u8;
unsafe {
match bitpix {
Bitpix::I16 => decode_be_into_slice(
src,
std::slice::from_raw_parts_mut(p as *mut i16, count),
i16::from_be_bytes,
),
Bitpix::I32 => decode_be_into_slice(
src,
std::slice::from_raw_parts_mut(p as *mut i32, count),
i32::from_be_bytes,
),
Bitpix::I64 => decode_be_into_slice(
src,
std::slice::from_raw_parts_mut(p as *mut i64, count),
i64::from_be_bytes,
),
Bitpix::F32 => decode_be_into_slice(
src,
std::slice::from_raw_parts_mut(p as *mut f32, count),
f32::from_be_bytes,
),
Bitpix::F64 => decode_be_into_slice(
src,
std::slice::from_raw_parts_mut(p as *mut f64, count),
f64::from_be_bytes,
),
Bitpix::U8 => unreachable!("U8 is handled by the caller, never swapped"),
}
}
}
pub(crate) fn view_words(words: &[u64], bitpix: Bitpix, nbytes: usize) -> ImageView<'_> {
let count = nbytes / bitpix.elem_size();
let p = words.as_ptr() as *const u8;
unsafe {
match bitpix {
Bitpix::U8 => ImageView::U8(std::slice::from_raw_parts(p, count)),
Bitpix::I16 => ImageView::I16(std::slice::from_raw_parts(p as *const i16, count)),
Bitpix::I32 => ImageView::I32(std::slice::from_raw_parts(p as *const i32, count)),
Bitpix::I64 => ImageView::I64(std::slice::from_raw_parts(p as *const i64, count)),
Bitpix::F32 => ImageView::F32(std::slice::from_raw_parts(p as *const f32, count)),
Bitpix::F64 => ImageView::F64(std::slice::from_raw_parts(p as *const f64, count)),
}
}
}
#[cfg(feature = "compression")]
fn samples_as_bytes(data: &ImageData) -> &[u8] {
unsafe {
let (ptr, len) = match data {
ImageData::U8(v) => return v,
ImageData::I16(v) => (v.as_ptr() as *const u8, v.len() * 2),
ImageData::I32(v) => (v.as_ptr() as *const u8, v.len() * 4),
ImageData::I64(v) => (v.as_ptr() as *const u8, v.len() * 8),
ImageData::F32(v) => (v.as_ptr() as *const u8, v.len() * 4),
ImageData::F64(v) => (v.as_ptr() as *const u8, v.len() * 8),
};
std::slice::from_raw_parts(ptr, len)
}
}
#[cfg(feature = "compression")]
pub(crate) fn copy_samples_into_words(samples: &ImageData, words: &mut Vec<u64>) -> usize {
let bytes = samples_as_bytes(samples);
words.resize(bytes.len().div_ceil(8), 0);
unsafe {
std::slice::from_raw_parts_mut(words.as_mut_ptr() as *mut u8, bytes.len())
.copy_from_slice(bytes);
}
bytes.len()
}
#[derive(Debug)]
pub struct RawImage<'a> {
pub shape: Vec<usize>,
pub bitpix: Bitpix,
pub scaling: Scaling,
data: ImageBytes<'a>,
}
#[derive(Debug)]
enum ImageBytes<'a> {
Raw(&'a [u8]),
#[cfg_attr(not(feature = "compression"), allow(dead_code))]
Decoded(ImageData),
}
impl<'a> RawImage<'a> {
pub(crate) fn raw(
shape: Vec<usize>,
bitpix: Bitpix,
scaling: Scaling,
bytes: &'a [u8],
) -> RawImage<'a> {
RawImage {
shape,
bitpix,
scaling,
data: ImageBytes::Raw(bytes),
}
}
#[cfg(feature = "compression")]
pub(crate) fn decoded(samples: ImageData, shape: Vec<usize>, scaling: Scaling) -> RawImage<'a> {
RawImage {
shape,
bitpix: samples.bitpix(),
scaling,
data: ImageBytes::Decoded(samples),
}
}
pub fn decode(&self) -> ImageData {
match &self.data {
ImageBytes::Raw(bytes) => ImageData::decode(bytes, self.bitpix),
ImageBytes::Decoded(samples) => samples.clone(),
}
}
pub fn u8(&self) -> Option<&[u8]> {
match &self.data {
ImageBytes::Raw(bytes) if self.bitpix == Bitpix::U8 => Some(bytes),
ImageBytes::Decoded(ImageData::U8(v)) => Some(v),
_ => None,
}
}
pub fn raw_bytes(&self) -> Option<&[u8]> {
match &self.data {
ImageBytes::Raw(bytes) => Some(bytes),
ImageBytes::Decoded(_) => None,
}
}
pub fn physical(&self) -> Vec<f64> {
match &self.data {
ImageBytes::Raw(bytes) => ImageData::decode(bytes, self.bitpix).physical(&self.scaling),
ImageBytes::Decoded(samples) => samples.physical(&self.scaling),
}
}
pub fn physical_f32(&self) -> Vec<f32> {
match &self.data {
ImageBytes::Raw(bytes) => {
ImageData::decode(bytes, self.bitpix).physical_f32(&self.scaling)
}
ImageBytes::Decoded(samples) => samples.physical_f32(&self.scaling),
}
}
pub fn unsigned(&self) -> Option<UnsignedView> {
match &self.data {
ImageBytes::Raw(bytes) => ImageData::decode(bytes, self.bitpix).unsigned(&self.scaling),
ImageBytes::Decoded(samples) => samples.unsigned(&self.scaling),
}
}
pub fn sample_type(&self) -> SampleType {
SampleType::from_scaling(self.bitpix, &self.scaling)
}
}
pub(crate) const U16_OFFSET: f64 = 32_768.0; pub(crate) const U32_OFFSET: f64 = 2_147_483_648.0; pub(crate) const U64_OFFSET: f64 = 9_223_372_036_854_775_808.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SampleType {
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
F32,
F64,
}
impl SampleType {
pub fn from_scaling(bitpix: Bitpix, scaling: &Scaling) -> SampleType {
let offset = scaling.bscale == 1.0;
match bitpix {
Bitpix::U8 if offset && scaling.bzero == -128.0 => SampleType::I8,
Bitpix::U8 => SampleType::U8,
Bitpix::I16 if offset && scaling.bzero == U16_OFFSET => SampleType::U16,
Bitpix::I16 => SampleType::I16,
Bitpix::I32 if offset && scaling.bzero == U32_OFFSET => SampleType::U32,
Bitpix::I32 => SampleType::I32,
Bitpix::I64 if offset && scaling.bzero == U64_OFFSET => SampleType::U64,
Bitpix::I64 => SampleType::I64,
Bitpix::F32 => SampleType::F32,
Bitpix::F64 => SampleType::F64,
}
}
pub fn is_unsigned(self) -> bool {
matches!(
self,
SampleType::U8 | SampleType::U16 | SampleType::U32 | SampleType::U64
)
}
pub fn is_float(self) -> bool {
matches!(self, SampleType::F32 | SampleType::F64)
}
pub fn is_integer(self) -> bool {
!self.is_float()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnsignedView {
I8(Vec<i8>),
U16(Vec<u16>),
U32(Vec<u32>),
U64(Vec<u64>),
}
impl UnsignedView {
pub(crate) fn from_signed_byte(stored: &[u8]) -> UnsignedView {
UnsignedView::I8(stored.iter().map(|&x| (x ^ 0x80) as i8).collect())
}
pub(crate) fn from_offset_i16(stored: &[i16]) -> UnsignedView {
UnsignedView::U16(stored.iter().map(|&x| (x as u16) ^ 0x8000).collect())
}
pub(crate) fn from_offset_i32(stored: &[i32]) -> UnsignedView {
UnsignedView::U32(stored.iter().map(|&x| (x as u32) ^ 0x8000_0000).collect())
}
pub(crate) fn from_offset_i64(stored: &[i64]) -> UnsignedView {
UnsignedView::U64(
stored
.iter()
.map(|&x| (x as u64) ^ 0x8000_0000_0000_0000)
.collect(),
)
}
}
#[derive(Debug, Clone)]
pub struct Image {
pub shape: Vec<usize>,
pub samples: ImageData,
pub scaling: Scaling,
}
impl Image {
pub fn from_u16(shape: Vec<usize>, data: &[u16]) -> Image {
Image::offset_image(
shape,
ImageData::I16(data.iter().map(|&x| (x ^ 0x8000) as i16).collect()),
U16_OFFSET,
)
}
pub fn from_u32(shape: Vec<usize>, data: &[u32]) -> Image {
Image::offset_image(
shape,
ImageData::I32(data.iter().map(|&x| (x ^ 0x8000_0000) as i32).collect()),
U32_OFFSET,
)
}
pub fn from_u64(shape: Vec<usize>, data: &[u64]) -> Image {
Image::offset_image(
shape,
ImageData::I64(
data.iter()
.map(|&x| (x ^ 0x8000_0000_0000_0000) as i64)
.collect(),
),
U64_OFFSET,
)
}
pub fn from_i8(shape: Vec<usize>, data: &[i8]) -> Image {
Image::offset_image(
shape,
ImageData::U8(data.iter().map(|&x| (x as u8) ^ 0x80).collect()),
-128.0,
)
}
fn offset_image(shape: Vec<usize>, samples: ImageData, bzero: f64) -> Image {
Image {
shape,
samples,
scaling: Scaling {
bscale: 1.0,
bzero,
blank: None,
},
}
}
pub fn unsigned(&self) -> Option<UnsignedView> {
self.samples.unsigned(&self.scaling)
}
pub fn physical(&self) -> Vec<f64> {
self.samples.physical(&self.scaling)
}
pub fn physical_f32(&self) -> Vec<f32> {
self.samples.physical_f32(&self.scaling)
}
pub fn sample_type(&self) -> SampleType {
SampleType::from_scaling(self.samples.bitpix(), &self.scaling)
}
}
fn scale_ints<T, O>(v: &[T], blank: Option<i64>, scale: impl Fn(f64) -> O) -> Vec<O>
where
T: Copy + Into<i64>,
O: PhysicalOut,
{
v.iter()
.map(|&x| {
let xi: i64 = x.into();
if blank == Some(xi) {
O::from_f64(f64::NAN)
} else {
scale(xi as f64)
}
})
.collect()
}
trait PhysicalOut: Copy {
fn from_f64(value: f64) -> Self;
}
impl PhysicalOut for f64 {
fn from_f64(value: f64) -> f64 {
value
}
}
impl PhysicalOut for f32 {
fn from_f64(value: f64) -> f32 {
value as f32
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Scaling {
pub bscale: f64,
pub bzero: f64,
pub blank: Option<i64>,
}
impl Scaling {
pub(crate) fn from_header(header: &Header) -> Scaling {
Scaling {
bscale: header.get_real("BSCALE").unwrap_or(1.0),
bzero: header.get_real("BZERO").unwrap_or(0.0),
blank: header.get_integer("BLANK"),
}
}
pub fn is_identity(&self) -> bool {
self.bscale == 1.0 && self.bzero == 0.0
}
}
#[cfg(feature = "ndarray")]
#[derive(Debug, Clone, PartialEq)]
pub enum ImageArray {
U8(ndarray::ArrayD<u8>),
I16(ndarray::ArrayD<i16>),
I32(ndarray::ArrayD<i32>),
I64(ndarray::ArrayD<i64>),
F32(ndarray::ArrayD<f32>),
F64(ndarray::ArrayD<f64>),
}
#[cfg(feature = "ndarray")]
fn fortran_array<T>(shape: &[usize], data: Vec<T>) -> ndarray::ArrayD<T> {
use ndarray::ShapeBuilder as _;
let dims: Vec<usize> = if shape.is_empty() {
vec![0]
} else {
shape.to_vec()
};
ndarray::ArrayD::from_shape_vec(ndarray::IxDyn(&dims).f(), data)
.expect("decoded buffer length equals the axis product")
}
#[cfg(feature = "ndarray")]
impl ImageData {
pub fn into_ndarray(self, shape: &[usize]) -> ImageArray {
match self {
ImageData::U8(v) => ImageArray::U8(fortran_array(shape, v)),
ImageData::I16(v) => ImageArray::I16(fortran_array(shape, v)),
ImageData::I32(v) => ImageArray::I32(fortran_array(shape, v)),
ImageData::I64(v) => ImageArray::I64(fortran_array(shape, v)),
ImageData::F32(v) => ImageArray::F32(fortran_array(shape, v)),
ImageData::F64(v) => ImageArray::F64(fortran_array(shape, v)),
}
}
}
#[cfg(feature = "ndarray")]
impl RawImage<'_> {
pub fn physical_array(&self) -> ndarray::ArrayD<f64> {
fortran_array(&self.shape, self.physical())
}
pub fn to_ndarray(&self) -> ImageArray {
self.decode().into_ndarray(&self.shape)
}
}
#[cfg(feature = "ndarray")]
impl Image {
pub fn physical_array(&self) -> ndarray::ArrayD<f64> {
fortran_array(&self.shape, self.physical())
}
pub fn into_ndarray(self) -> ImageArray {
let Image { shape, samples, .. } = self;
samples.into_ndarray(&shape)
}
}
#[cfg(test)]
mod tests;