rustronomy_fits/
header_data_unit.rs1use core::fmt;
21use std::{borrow::Cow, error::Error, fmt::Display};
22
23use crate::{
24 bitpix::Bitpix,
25 extensions::{image::ImgParser, table::AsciiTblParser, Extension},
26 hdu_err::*,
27 header::Header,
28 raw::{
29 raw_io::{RawFitsReader, RawFitsWriter},
30 BlockSized,
31 },
32};
33
34const VALID_EXTENSION_NAMES: [&'static str; 3] = ["'IMAGE '", "'TABLE '", "'BINTABLE'"];
35
36#[derive(Debug, Clone)]
37pub struct HeaderDataUnit {
38 header: Header,
39 data: Option<Extension>,
40}
41
42impl HeaderDataUnit {
43 pub(crate) fn decode_hdu(raw: &mut RawFitsReader) -> Result<Self, Box<dyn Error>> {
48 let header = Header::decode_header(raw)?;
50
51 let extension = match &header.get_value("XTENSION") {
53 None => {
54 if header.get_value_as::<usize>("NAXIS")? == 0 {
61 None
64 } else {
65 Some(Self::read_img(raw, &header)?)
67 }
68 }
69 Some(extension_type) => {
70 match extension_type.as_str() {
75 "'IMAGE '" => Some(Self::read_img(raw, &header)?),
76 _kw @ "'TABLE '" => Some(Self::read_table(raw, &header)?),
77 kw @ "'BINTABLE'" => Err(Self::not_impl(kw))?,
78 kw => Err(InvalidRecordValueError::new("XTENSION", kw, &VALID_EXTENSION_NAMES))?,
79 }
80 }
81 };
82
83 Ok(HeaderDataUnit { header: header, data: extension })
85 }
86
87 fn read_table(raw: &mut RawFitsReader, header: &Header) -> Result<Extension, Box<dyn Error>> {
88 let naxis: usize = header.get_value_as("NAXIS")?;
106 let bitpix: isize = header.get_value_as("BITPIX")?;
107 let pcount: usize = header.get_value_as("PCOUNT")?;
108 let gcount: usize = header.get_value_as("GCOUNT")?;
109 if naxis != 2 {
111 Err(InvalidRecordValueError::new("NAXIS", &format!("{naxis}"), &["2"]))?
112 }
113 if bitpix != 8 {
114 Err(InvalidRecordValueError::new("BITPIX", &format!("{bitpix}"), &["8"]))?
115 }
116 if pcount != 0 {
117 Err(InvalidRecordValueError::new("PCOUNT", &format!("{pcount}"), &["0"]))?
118 }
119 if gcount != 1 {
120 Err(InvalidRecordValueError::new("GCOUNT", &format!("{gcount}"), &["1"]))?
121 }
122
123 let nfields: usize = header.get_value_as("TFIELDS")?;
125 let row_len: usize = header.get_value_as("NAXIS1")?;
126 let nrows: usize = header.get_value_as("NAXIS2")?;
127
128 let mut row_index_col_start: Vec<usize> = Vec::new();
129 for i in 1..=nfields {
130 row_index_col_start.push(
131 header.get_value_as::<usize>(&format!("TBCOL{i}"))? - 1,
134 );
135 }
136
137 let mut field_format: Vec<String> = Vec::new();
138 for i in 1..=nfields {
139 field_format.push(header.get_value_as(&format!("TFORM{i}"))?)
140 }
141
142 let labels = match header.get_value("TTYPE1") {
143 None => None,
144 Some(_) => {
145 let mut tmp: Vec<String> = Vec::new();
152 for i in 1..=nfields {
153 tmp.push(header.get_value_as(&format!("TTYPE{i}"))?);
154 }
155 Some(
156 tmp
158 .into_iter()
159 .map(|mut ttype_keyword| {
160 ttype_keyword.remove(0);
163 ttype_keyword.pop();
164 header.get_value_as(ttype_keyword.trim())
165 })
166 .collect::<Result<Vec<String>, Box<dyn Error>>>()?,
167 )
168 }
169 };
170
171 let tbl = AsciiTblParser::decode_tbl(
173 raw,
174 row_len,
175 nrows,
176 nfields,
177 row_index_col_start,
178 field_format,
179 labels,
180 )?;
181
182 Ok(tbl)
184 }
185
186 fn read_img(raw: &mut RawFitsReader, header: &Header) -> Result<Extension, Box<dyn Error>> {
187 let naxis: usize = header.get_value_as("NAXIS")?;
189
190 let mut axes: Vec<usize> = Vec::new();
192 for i in 1..=naxis {
193 axes.push(header.get_value_as(&format!("NAXIS{i}"))?);
194 }
195
196 let bitpix = Bitpix::from_code(&header.get_value_as("BITPIX")?)?;
198
199 Ok(ImgParser::decode_img(raw, &axes, bitpix)?)
201 }
202
203 pub(crate) fn encode_hdu(self, writer: &mut RawFitsWriter) -> Result<(), Box<dyn Error>> {
204 self.header.encode_header(writer)?;
206
207 match self.data {
209 Some(data) => data.write_to_buffer(writer)?,
210 _ => {} }
212
213 Ok(())
215 }
216
217 fn not_impl(keyword: &str) -> Box<NotImplementedErr> {
218 Box::new(NotImplementedErr::new(keyword.to_string()))
219 }
220
221 pub fn get_header(&self) -> &Header {
227 &self.header
228 }
229 pub fn get_data(&self) -> Option<&Extension> {
230 self.data.as_ref()
231 }
232
233 pub fn to_parts(self) -> (Header, Option<Extension>) {
235 (self.header, self.data)
236 }
237
238 pub fn pretty_print_header(&self) -> String {
239 format!(
240 "[Header] - #records: {}, size: {}",
241 self.header.get_block_len(),
242 self.header.get_num_records()
243 )
244 }
245
246 pub fn pretty_print_data(&self) -> String {
247 let data_string: Cow<str> = match &self.data {
248 None => "(NO_DATA)".into(),
249 Some(data) => format!("{data}").into(),
250 };
251 format!("[Data] {data_string}")
252 }
253}
254
255impl BlockSized for HeaderDataUnit {
256 fn get_block_len(&self) -> usize {
257 self.header.get_block_len()
258 + match &self.data {
259 None => 0,
260 Some(data) => data.get_block_len(),
261 }
262 }
263}
264
265impl Display for HeaderDataUnit {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
267 write!(f, "{}", self.pretty_print_header())?;
268 write!(f, "{}", self.pretty_print_data())?;
269 Ok(())
270 }
271}