use crate::fits::Fits;
use crate::hdu::{ExtensionHDU, HDU};
use crate::header::header::BLOCK_NUM_BYTES;
use crate::header::{ExtensionType, Header};
use crate::slice_ascii_table_hdu::SliceAsciiTableHDU;
use crate::slice_bin_table_hdu::SliceBinTableHDU;
use crate::slice_image_hdu::SliceImageHDU;
use log::debug;
use std::error::Error;
use std::io::{Cursor, Seek, SeekFrom};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct FitsSlice {
primary_hdu: SliceImageHDU,
extension_hdus: Vec<ExtensionHDU<Self>>,
}
impl FitsSlice {
pub fn from_slice(data: &[u8]) -> Result<Self, Box<dyn Error + Send + Sync>> {
Self::from_vec(data.to_vec())
}
pub fn from_vec(data: Vec<u8>) -> Result<Self, Box<dyn Error + Send + Sync>> {
#[cfg(feature = "gzip")]
let data = if is_gzipped(&data) {
decompress(&data)?
} else {
data
};
let data = Arc::new(data);
let mut reader: Box<dyn crate::util::ReadSeek> =
Box::new(Cursor::new(crate::util::SharedBytes(Arc::clone(&data))));
let header =
Header::from_reader(&mut reader)?.ok_or("Could not read primary FITS header")?;
header.validate_primary()?;
debug!("Read primary header: {:?}", header);
let data_offset = header.bytes_len();
let primary_hdu = SliceImageHDU::new(header, Arc::clone(&data), data_offset);
let mut extension_hdus = vec![];
let mut offset = primary_hdu.byte_size();
loop {
if offset as usize >= data.len() {
break;
}
reader.seek(SeekFrom::Start(offset))?;
let Some(header) = Header::from_reader(&mut reader)? else {
break;
};
header.validate_extension()?;
let extension_type = header
.extension()
.ok_or("This is not a valid fits extension. Card XTENSION is missing or invalid")?;
let data_offset = offset as usize + header.bytes_len();
match extension_type {
ExtensionType::Image => {
let hdu = SliceImageHDU::new(header, Arc::clone(&data), data_offset);
offset += hdu.byte_size();
extension_hdus.push(ExtensionHDU::Image(hdu));
}
ExtensionType::BinTable if header.is_compressed_image() => {
let hdu = SliceImageHDU::new(header, Arc::clone(&data), data_offset);
offset += hdu.byte_size();
extension_hdus.push(ExtensionHDU::Image(hdu));
}
ExtensionType::BinTable => {
let hdu = SliceBinTableHDU::new(header, Arc::clone(&data), data_offset);
offset += hdu.byte_size();
extension_hdus.push(ExtensionHDU::BinTable(hdu));
}
ExtensionType::AsciiTable => {
let hdu = SliceAsciiTableHDU::new(header, Arc::clone(&data), data_offset);
offset += hdu.byte_size();
extension_hdus.push(ExtensionHDU::AsciiTable(hdu));
}
}
}
Ok(Self {
primary_hdu,
extension_hdus,
})
}
#[cfg(feature = "tokio")]
pub async fn from_vec_async(data: Vec<u8>) -> Result<Self, Box<dyn Error + Send + Sync>> {
tokio::task::spawn_blocking(move || Self::from_vec(data)).await?
}
pub fn new() -> Self {
Self {
primary_hdu: SliceImageHDU::empty(),
extension_hdus: vec![],
}
}
}
impl Default for FitsSlice {
fn default() -> Self {
Self::new()
}
}
impl Fits for FitsSlice {
type ImageHDU = SliceImageHDU;
type BinTableHDU = SliceBinTableHDU;
type AsciiTableHDU = SliceAsciiTableHDU;
fn primary_hdu(&self) -> &Self::ImageHDU {
&self.primary_hdu
}
fn primary_hdu_mut(&mut self) -> &mut Self::ImageHDU {
&mut self.primary_hdu
}
fn extension_count(&self) -> usize {
self.extension_hdus.len()
}
fn extension_hdu(&self, index: usize) -> Option<&ExtensionHDU<Self>> {
self.extension_hdus.get(index)
}
fn extension_hdu_mut(&mut self, index: usize) -> Option<&mut ExtensionHDU<Self>> {
self.extension_hdus.get_mut(index)
}
fn extension_hdus(&self) -> impl Iterator<Item = &ExtensionHDU<Self>> {
self.extension_hdus.iter()
}
fn extension_hdus_mut(&mut self) -> impl Iterator<Item = &mut ExtensionHDU<Self>> {
self.extension_hdus.iter_mut()
}
fn push_extension(&mut self, extension: ExtensionHDU<Self>) {
self.extension_hdus.push(extension);
}
fn remove_extension(&mut self, index: usize) -> Option<ExtensionHDU<Self>> {
(index < self.extension_hdus.len()).then(|| self.extension_hdus.remove(index))
}
fn to_vec(&self) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
let mut bytes = Vec::new();
if self.primary_hdu.header().is_compressed_image() {
append_hdu(&mut bytes, &empty_primary_header(), &[], 0)?;
append_hdu(
&mut bytes,
&self
.primary_hdu
.header()
.conformed(Some(ExtensionType::BinTable)),
self.primary_hdu.data_bytes(),
0,
)?;
} else {
append_hdu(
&mut bytes,
&self.primary_hdu.header().conformed(None),
self.primary_hdu.data_bytes(),
0,
)?;
}
for extension in &self.extension_hdus {
match extension {
ExtensionHDU::Image(hdu) => append_hdu(
&mut bytes,
&hdu.header()
.conformed(Some(extension_type_of(hdu.header()))),
hdu.data_bytes(),
0,
)?,
ExtensionHDU::BinTable(hdu) => append_hdu(
&mut bytes,
&hdu.header().conformed(Some(ExtensionType::BinTable)),
hdu.data_bytes(),
0,
)?,
ExtensionHDU::AsciiTable(hdu) => append_hdu(
&mut bytes,
&hdu.header().conformed(Some(ExtensionType::AsciiTable)),
hdu.data_bytes(),
b' ',
)?,
}
}
Ok(bytes)
}
}
#[cfg(feature = "gzip")]
fn is_gzipped(data: &[u8]) -> bool {
data.starts_with(&[0x1f, 0x8b])
}
#[cfg(feature = "gzip")]
fn decompress(data: &[u8]) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
use std::io::Read;
let mut decoder = flate2::read::GzDecoder::new(data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
Ok(decompressed)
}
fn append_hdu(
bytes: &mut Vec<u8>,
header: &Header,
data: &[u8],
padding: u8,
) -> Result<(), Box<dyn Error + Send + Sync>> {
header.validate_against_data(data.len())?;
let mut data = data.to_vec();
let overhang = data.len() % BLOCK_NUM_BYTES;
if overhang != 0 {
data.resize(data.len() + BLOCK_NUM_BYTES - overhang, padding);
}
bytes.extend_from_slice(&header.checksummed_bytes(&data));
bytes.extend_from_slice(&data);
Ok(())
}
fn extension_type_of(header: &Header) -> ExtensionType {
if header.is_compressed_image() {
ExtensionType::BinTable
} else {
ExtensionType::Image
}
}
fn empty_primary_header() -> Header {
let mut header = Header::default();
let _ = header.set_card(crate::header::card_keys::EXTEND, true);
header.conformed(None)
}