use crate::ascii_table::AsciiTable;
use crate::bintable::BinTable;
use crate::checksum;
use crate::error::{Error, Result};
use crate::header::Header;
use crate::image_data::ImageData;
use crate::io_utils;
use crate::keyword::HeaderValue;
use std::io::{Read, Seek, Write};
#[derive(Debug, Clone)]
pub enum HduData {
Empty,
Image(ImageData),
AsciiTable(AsciiTable),
BinTable(BinTable),
}
#[derive(Debug, Clone)]
pub struct Hdu {
pub header: Header,
pub data: HduData,
}
impl Hdu {
pub fn new(header: Header, data: HduData) -> Self {
Hdu { header, data }
}
pub fn primary_image(image: ImageData) -> Self {
let mut header = Header::new();
header.set(
"SIMPLE",
HeaderValue::Logical(true),
Some("conforms to FITS standard"),
);
image.fill_header(&mut header);
Hdu {
header,
data: HduData::Image(image),
}
}
pub fn primary_empty() -> Self {
let mut header = Header::new();
header.set(
"SIMPLE",
HeaderValue::Logical(true),
Some("conforms to FITS standard"),
);
header.set("BITPIX", HeaderValue::Integer(8), None);
header.set("NAXIS", HeaderValue::Integer(0), None);
Hdu {
header,
data: HduData::Empty,
}
}
pub fn image_extension(image: ImageData) -> Self {
let mut header = Header::new();
header.set(
"XTENSION",
HeaderValue::String("IMAGE".into()),
Some("image extension"),
);
image.fill_header(&mut header);
header.set("PCOUNT", HeaderValue::Integer(0), None);
header.set("GCOUNT", HeaderValue::Integer(1), None);
Hdu {
header,
data: HduData::Image(image),
}
}
pub fn ascii_table_extension(table: AsciiTable) -> Self {
let mut header = Header::new();
table.fill_header(&mut header);
Hdu {
header,
data: HduData::AsciiTable(table),
}
}
pub fn bintable_extension(table: BinTable) -> Self {
let mut header = Header::new();
table.fill_header(&mut header);
Hdu {
header,
data: HduData::BinTable(table),
}
}
pub fn as_compressed_image(&self) -> Option<crate::tile_compress::CompressedImage<'_>> {
if !crate::tile_compress::CompressedImage::detect(&self.header) {
return None;
}
match &self.data {
HduData::BinTable(table) => {
crate::tile_compress::CompressedImage::from_bintable(&self.header, table).ok()
}
_ => None,
}
}
pub fn read_from<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let header = Header::read_from(reader)?;
let data_bytes = header.data_byte_count()?;
let is_primary = header.find("SIMPLE").is_some();
let xtension = header.get_string("XTENSION").map(|s| s.to_string());
if data_bytes == 0 {
io_utils::skip_data_block(reader, 0)?;
return Ok(Hdu {
header,
data: HduData::Empty,
});
}
let pcount = header.get_int("PCOUNT").unwrap_or(0) as usize;
let raw = io_utils::read_data_block(reader, data_bytes)?;
let data = if is_primary || xtension.as_deref() == Some("IMAGE") {
let naxis = header.get_int("NAXIS").unwrap_or(0) as usize;
if naxis == 0 {
HduData::Empty
} else {
let image_bytes = if pcount > 0 {
&raw[..raw.len() - pcount]
} else {
&raw
};
let img = ImageData::from_header_and_data(&header, image_bytes)?;
HduData::Image(img)
}
} else if xtension.as_deref() == Some("TABLE") {
let table = AsciiTable::from_header_and_data(&header, &raw)?;
HduData::AsciiTable(table)
} else if xtension.as_deref() == Some("BINTABLE") {
let table = BinTable::from_header_and_data(&header, &raw)?;
HduData::BinTable(table)
} else if let Some(ext) = &xtension {
return Err(Error::UnsupportedExtension(ext.clone()));
} else {
HduData::Empty
};
Ok(Hdu { header, data })
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write_impl(writer, false)
}
pub fn write_with_checksum<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write_impl(writer, true)
}
fn write_impl<W: Write>(&self, writer: &mut W, with_checksum: bool) -> Result<()> {
let mut header = self.header.clone();
match &self.data {
HduData::Empty => {}
HduData::Image(img) => img.fill_header(&mut header),
HduData::AsciiTable(table) => table.fill_header(&mut header),
HduData::BinTable(table) => table.fill_header(&mut header),
}
let data_bytes = match &self.data {
HduData::Empty => Vec::new(),
HduData::Image(img) => img.pixels.to_bytes(),
HduData::AsciiTable(table) => table.raw_data.clone(),
HduData::BinTable(table) => table.to_bytes(),
};
let padded_data = io_utils::pad_to_block(&data_bytes);
if with_checksum {
let header_bytes = checksum::stamp_hdu(&mut header, &padded_data)?;
writer.write_all(&header_bytes)?;
} else {
header.write_to(writer)?;
}
io_utils::write_data_block(writer, &data_bytes)?;
Ok(())
}
pub fn verify_datasum(&self) -> Result<()> {
let data_bytes = match &self.data {
HduData::Empty => Vec::new(),
HduData::Image(img) => img.pixels.to_bytes(),
HduData::AsciiTable(table) => table.raw_data.clone(),
HduData::BinTable(table) => table.to_bytes(),
};
let padded = io_utils::pad_to_block(&data_bytes);
checksum::verify_from_header(&self.header, &padded)
}
}