astro_rs/fits/
mod.rs

1//! Serialize and deserialize FITS data.
2//! See <https://archive.stsci.edu/fits/fits_standard/fits_standard.html> for the FITS API.
3
4mod hdu_types;
5mod header;
6mod header_value;
7
8use std::fmt::Debug;
9use std::io::{BufReader, BufWriter, Cursor, Read, Write};
10use std::slice::IterMut;
11
12pub use hdu_types::*;
13pub use header::*;
14pub use header_value::*;
15
16/// The expected keyword for the name of an extension.
17pub const EXTNAME_KEYWORD: [u8; 8] = *b"EXTNAME ";
18const BLANK_KEYWORD: [u8; 8] = *b"        ";
19
20/// A representation of the entirety of a FITS file.
21#[derive(Debug)]
22pub struct HduList<R> {
23    reader: BufReader<R>,
24    hdus: Vec<Hdu>,
25}
26
27impl Default for HduList<Cursor<Vec<u8>>> {
28    fn default() -> Self {
29        Self {
30            reader: BufReader::new(Cursor::new(Vec::new())),
31            hdus: Default::default(),
32        }
33    }
34}
35
36impl<R: Read> HduList<R> {
37    /// Constructs an empty HduList.
38    pub fn new(reader: BufReader<R>) -> Self {
39        HduList {
40            reader,
41            hdus: Vec::new(),
42        }
43    }
44
45    /// Retrieves the HDU at the given index, or None if an HDU doesn't exist at the index.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use astro_rs::fits::*;
51    ///
52    /// let mut hdu_list = HduList::default();
53    /// assert!(hdu_list.get_by_index(0).is_none());
54    ///
55    /// hdu_list.push(primary_hdu::default());
56    /// assert!(hdu_list.get_by_index(0).is_some());
57    /// ```
58    pub fn get_by_index(&mut self, index: usize) -> Option<&mut Hdu> {
59        let mut cur_hdus = self.hdus.len();
60        while cur_hdus <= index {
61            let new_hdu = self.read_hdu()?;
62            self.hdus.push(new_hdu);
63            cur_hdus += 1;
64        }
65        Some(&mut self.hdus[index])
66    }
67
68    /// Retrieves the HDU with the given value for the `EXTNAME` keyword, or None if an HDU
69    /// with the given name doesn't exist.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use astro_rs::fits::*;
75    ///
76    /// let mut hdu_list = HduList::default();
77    /// // empty list
78    /// assert!(hdu_list.get_by_name("hdu_name").is_none());
79    ///
80    /// // name does not match
81    /// let mut img_hdu = image_hdu::default();
82    /// let name_card = FitsHeaderCard::from(*b"EXTNAME = 'name_of_hdu'                                                         ");
83    /// img_hdu.header.cards.insert(img_hdu.header.cards.len() - 1, name_card);
84    /// hdu_list.push(img_hdu);
85    /// assert!(hdu_list.get_by_name("hdu_name").is_none());
86    ///
87    /// // name matches
88    /// let mut img_hdu = image_hdu::default();
89    /// let name_card = FitsHeaderCard::from(*b"EXTNAME = 'hdu_name'                                                            ");
90    /// img_hdu.header.cards.insert(img_hdu.header.cards.len() - 1, name_card);
91    /// hdu_list.push(img_hdu);
92    /// assert!(hdu_list.get_by_name("hdu_name").is_some());
93    /// ```
94    pub fn get_by_name(&mut self, name: &str) -> Option<&mut Hdu> {
95        let index = self
96            .hdus
97            .iter_mut()
98            .position(|hdu| hdu.get_name() == name)
99            .or_else(|| {
100                let mut index = self.hdus.len();
101                loop {
102                    let mut new_hdu = self.read_hdu()?;
103                    if new_hdu.get_name() == name {
104                        self.hdus.push(new_hdu);
105                        break;
106                    }
107
108                    self.hdus.push(new_hdu);
109                    index += 1;
110                }
111                Some(index)
112            });
113
114        Some(&mut self.hdus[index?])
115    }
116
117    /// Returns a mutable pointer to the first HDU, or `None` if the list is empty.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use astro_rs::fits::*;
123    ///
124    /// let mut hdu_list = HduList::default();
125    /// assert!(hdu_list.first_mut().is_none());
126    ///
127    /// hdu_list.push(primary_hdu::default());
128    /// assert!(hdu_list.first_mut().is_some());
129    /// ```
130    pub fn first_mut(&mut self) -> Option<&mut Hdu> {
131        if self.hdus.is_empty() {
132            let new_hdu = self.read_hdu()?;
133            self.hdus.push(new_hdu);
134        }
135        Some(&mut self.hdus[0])
136    }
137
138    /// Deserializes all HDUs if necessary, then returns a mutable iterator over the HDUs.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use astro_rs::fits::*;
144    ///
145    /// let mut hdu_list = HduList::default();
146    /// hdu_list.push(primary_hdu::default());
147    ///
148    /// // find the primary HDU
149    /// assert!(hdu_list
150    ///     .iter_mut()
151    ///     .find_map(|hdu| if hdu.header.get_card(SIMPLE_KEYWORD).is_some() {
152    ///         Some(hdu)
153    ///     } else {
154    ///         None
155    ///     })
156    ///     .is_some())
157    /// ```
158    pub fn iter_mut(&mut self) -> IterMut<Hdu> {
159        while let Some(new_hdu) = self.read_hdu() {
160            self.hdus.push(new_hdu);
161        }
162        self.hdus.iter_mut()
163    }
164
165    /// Deserializes all HDUs up to `index` if necessary, then inserts the given `hdu`.
166    ///
167    /// # Panics
168    ///
169    /// Panics if `index` is out of bounds.
170    ///
171    /// # Examples
172    ///
173    /// ```should_panic
174    /// use astro_rs::fits::*;
175    ///
176    /// let mut hdu_list = HduList::default();
177    /// // panics, index is out of bounds
178    /// hdu_list.insert(1, image_hdu::default());
179    /// ```
180    ///
181    /// ```
182    /// use astro_rs::fits::*;
183    ///
184    /// let mut hdu_list = HduList::default();
185    /// hdu_list.push(primary_hdu::default());
186    /// hdu_list.insert(1, image_hdu::default());
187    /// assert_eq!(hdu_list.iter_mut().count(), 2);
188    /// ```
189    pub fn insert(&mut self, index: usize, hdu: Hdu) {
190        let mut cur_hdus = self.hdus.len();
191        while cur_hdus < index {
192            if let Some(new_hdu) = self.read_hdu() {
193                self.hdus.push(new_hdu);
194                cur_hdus += 1;
195            } else {
196                panic!("{} is out of bounds (max {})", index, cur_hdus);
197            }
198        }
199        self.hdus.insert(index, hdu);
200    }
201
202    /// Appends `hdu` to the end of the HDU list.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use astro_rs::fits::*;
208    ///
209    /// let mut hdu_list = HduList::default();
210    /// hdu_list.push(primary_hdu::default());
211    /// assert_eq!(hdu_list.iter_mut().count(), 1);
212    /// hdu_list.push(image_hdu::default());
213    /// assert_eq!(hdu_list.iter_mut().count(), 2);
214    /// ```
215    pub fn push(&mut self, hdu: Hdu) {
216        while let Some(new_hdu) = self.read_hdu() {
217            self.hdus.push(new_hdu);
218        }
219        self.hdus.push(hdu);
220    }
221
222    /// Writes the HDU list via the given writer.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use astro_rs::fits::*;
228    /// use std::io::*;
229    ///
230    /// let in_cursor = Cursor::new(SIMPLE_KEYWORD.to_vec());
231    /// let mut hdu_list = HduList::new(BufReader::new(in_cursor));
232    /// let out_cursor = Cursor::new(Vec::new());
233    /// let mut out_writer = BufWriter::new(out_cursor);
234    /// hdu_list.write(&mut out_writer)?;
235    /// assert_eq!(out_writer.get_ref().get_ref(), &SIMPLE_KEYWORD.to_vec());
236    ///
237    /// # Ok::<(), std::io::Error>(())
238    /// ```
239    pub fn write<W: Write>(&mut self, writer: &mut BufWriter<W>) -> Result<(), std::io::Error> {
240        for hdu in &self.hdus {
241            writer.write_all(&hdu.clone().to_bytes())?;
242        }
243        std::io::copy(&mut self.reader, writer)?;
244        writer.flush()?;
245        Ok(())
246    }
247
248    /// Validates the existence and format of the SIMPLE header card.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use astro_rs::fits::*;
254    ///
255    /// let mut hdu_list = HduList::default();
256    /// // empty header
257    /// assert!(!hdu_list.is_header_valid()?);
258    ///
259    /// let mut hdu = Hdu::new();
260    ///
261    /// // non-empty header missing simple card
262    /// let bitpix_card = FitsHeaderCard::from(*b"BITPIX  =                  -32 / FITS BITS/PIXEL                                ");
263    /// hdu.header.cards.insert(0, bitpix_card);
264    /// hdu_list.push(hdu);
265    /// assert!(!hdu_list.is_header_valid()?);
266    ///
267    /// // valid header
268    /// let simple_card = FitsHeaderCard::from(*b"SIMPLE  =                    T / FITS STANDARD                                  ");
269    /// hdu_list.first_mut().unwrap().header.cards.insert(0, simple_card);
270    /// assert!(hdu_list.is_header_valid()?);
271    /// # Ok::<(), astro_rs::fits::FitsHeaderError>(())
272    /// ```
273    pub fn is_header_valid(&mut self) -> Result<bool, FitsHeaderError> {
274        Ok(*self
275            .get_by_index(0)
276            .and_then(|hdu| hdu.header.get_card(SIMPLE_KEYWORD))
277            .and_then(|card| card.get_value::<bool>().ok())
278            .unwrap_or_default())
279    }
280
281    fn read_hdu(&mut self) -> Option<Hdu> {
282        let mut header_raw = Vec::new();
283        let mut new_header_bytes = vec![0; FITS_RECORD_LEN];
284        self.reader.read_exact(&mut new_header_bytes).ok()?;
285        header_raw.append(&mut new_header_bytes);
286
287        // search for the END keyword.
288        // this should be the last keyword in the header, so if something other than ' ' is found, stop searching
289        loop {
290            let mut end_found = false;
291            for card in 1..=FITS_RECORD_LEN / HEADER_CARD_LEN {
292                let card_index = header_raw.len() - card * HEADER_CARD_LEN;
293                match header_raw[card_index..card_index + HEADER_KEYWORD_LEN]
294                    .try_into()
295                    .unwrap()
296                {
297                    END_KEYWORD => {
298                        end_found = true;
299                        break;
300                    }
301                    BLANK_KEYWORD => continue,
302                    _ => {
303                        end_found = false;
304                        break;
305                    }
306                }
307            }
308            if end_found {
309                break;
310            }
311            new_header_bytes = vec![0; FITS_RECORD_LEN];
312            self.reader.read_exact(&mut new_header_bytes).ok()?;
313            header_raw.append(&mut new_header_bytes);
314        }
315
316        let mut header = FitsHeader::from_bytes(header_raw);
317        let mut data_raw = Vec::new();
318
319        let naxis = *header
320            .get_card(NAXIS_KEYWORD)
321            .and_then(|card| card.get_value::<u16>().ok())
322            .unwrap_or_default();
323        if naxis != 0 {
324            if let Some(bitpix) = header
325                .get_card(BITPIX_KEYWORD)
326                .and_then(|card| card.get_value::<Bitpix>().ok())
327            {
328                let mut data_len = 1;
329                let mut naxisx_keyword = FitsHeaderKeyword::from(NAXIS_KEYWORD);
330                for x in 1..=naxis {
331                    naxisx_keyword.append_number(x);
332
333                    let naxisx = *header
334                        .get_card(naxisx_keyword)
335                        .and_then(|card| card.get_value::<u32>().ok())
336                        .unwrap_or_default() as usize;
337                    data_len *= naxisx;
338                }
339                data_len *= bitpix.value() / 8;
340                if data_len % FITS_RECORD_LEN != 0 {
341                    let num_records = (data_len / FITS_RECORD_LEN) + 1;
342                    data_len = num_records * FITS_RECORD_LEN;
343                }
344                data_raw = vec![0; data_len];
345                let _ = self.reader.read_exact(&mut data_raw);
346            }
347        }
348        Some(Hdu { header, data_raw })
349    }
350}
351
352/// A Header Data Unit within a FITS file.
353#[derive(Debug, Default, Clone)]
354pub struct Hdu {
355    /// The header section of the HDU.
356    pub header: FitsHeader,
357    data_raw: Vec<u8>,
358}
359
360impl Hdu {
361    /// Constructs an HDU with the given header and data.
362    pub fn new() -> Self {
363        Self::default()
364    }
365
366    /// Serializes the contents of the HDU to bytes.
367    pub fn to_bytes(mut self) -> Vec<u8> {
368        let mut result = self.header.to_bytes();
369        result.append(&mut self.data_raw);
370        let remainder = result.len() % FITS_RECORD_LEN;
371        if remainder != 0 {
372            let num_cards = (result.len() / FITS_RECORD_LEN) + 1;
373            let new_len = num_cards * FITS_RECORD_LEN;
374            result.resize(new_len, 0);
375        }
376        result
377    }
378
379    /// Gets the name of the HDU, or an empty string if the name cannot be determined.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use astro_rs::fits::*;
385    ///
386    /// let mut img_hdu = image_hdu::default();
387    /// let name_card = FitsHeaderCard::from(*b"EXTNAME = 'hdu_name'                                                            ");
388    /// img_hdu.header.cards.insert(img_hdu.header.cards.len() - 1, name_card);
389    /// assert_eq!(img_hdu.get_name(), String::from("hdu_name"));
390    /// ```
391    pub fn get_name(&mut self) -> String {
392        self.header
393            .get_card(EXTNAME_KEYWORD)
394            .and_then(|card| card.get_value::<String>().ok())
395            .map(|name| name.trim().to_owned())
396            .unwrap_or_default()
397    }
398
399    /// Gets the data section of the HDU.
400    pub fn data_raw(&self) -> &Vec<u8> {
401        &self.data_raw
402    }
403
404    /// Sets the data section of the HDU.
405    pub fn set_data_raw(&mut self, data_raw: Vec<u8>) {
406        self.data_raw = data_raw;
407    }
408
409    /// Attempts to create a new FitsDataCollection from the data section of the HDU.
410    pub fn get_data<T: FitsDataCollection>(&self) -> Result<T, FitsHeaderError> {
411        T::from_bytes(&self.data_raw)
412    }
413
414    /// Sets the data section of the HDU.
415    pub fn set_data<T: FitsDataCollection>(&mut self, data: &T) {
416        self.data_raw = data.to_bytes();
417    }
418
419    /// Creates a Vec containing the dimensions of the data section of the HDU as defined by the NAXIS keywords.
420    pub fn get_dimensions(&mut self) -> Vec<usize> {
421        let naxis = *self
422            .header
423            .get_card(NAXIS_KEYWORD)
424            .and_then(|card| card.get_value::<u16>().ok())
425            .unwrap_or_default();
426        if naxis == 0 {
427            return Vec::new();
428        }
429        let mut result = Vec::with_capacity(naxis as usize);
430        let mut naxisx_keyword = FitsHeaderKeyword::from(NAXIS_KEYWORD);
431        for x in 1..=naxis {
432            naxisx_keyword.append_number(x);
433
434            let naxisx = *self
435                .header
436                .get_card(naxisx_keyword)
437                .and_then(|card| card.get_value::<u32>().ok())
438                .unwrap_or_default() as usize;
439            result.push(naxisx);
440        }
441        result
442    }
443}
444
445/// A trait that allows data to be serialized/deserialized as the data section of an HDU.
446pub trait FitsDataCollection: Debug {
447    /// Attempts to deserialize a data collection from the given bytes.
448    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError>
449    where
450        Self: Sized;
451
452    /// Serializes the data collection to bytes.
453    fn to_bytes(&self) -> Vec<u8>;
454}
455
456impl FitsDataCollection for Vec<u8> {
457    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError> {
458        Ok(raw.to_owned())
459    }
460
461    fn to_bytes(&self) -> Vec<u8> {
462        self.to_owned()
463    }
464}
465
466impl FitsDataCollection for Vec<i16> {
467    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError> {
468        let mut data = Vec::with_capacity(raw.len() / 2);
469        for chunk in raw.chunks_exact(2) {
470            data.push(i16::from_be_bytes(chunk.try_into().unwrap()));
471        }
472        Ok(data)
473    }
474
475    fn to_bytes(&self) -> Vec<u8> {
476        let mut data = Vec::with_capacity(self.len() * 2);
477        for chunk in self {
478            data.extend_from_slice(&chunk.to_be_bytes());
479        }
480        data
481    }
482}
483
484impl FitsDataCollection for Vec<i32> {
485    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError> {
486        let mut data = Vec::with_capacity(raw.len() / 4);
487        for chunk in raw.chunks_exact(4) {
488            data.push(i32::from_be_bytes(chunk.try_into().unwrap()));
489        }
490        Ok(data)
491    }
492
493    fn to_bytes(&self) -> Vec<u8> {
494        let mut data = Vec::with_capacity(self.len() * 4);
495        for chunk in self {
496            data.extend_from_slice(&chunk.to_be_bytes());
497        }
498        data
499    }
500}
501
502impl FitsDataCollection for Vec<f32> {
503    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError> {
504        let mut data = Vec::with_capacity(raw.len() / 4);
505        for chunk in raw.chunks_exact(4) {
506            data.push(f32::from_be_bytes(chunk.try_into().unwrap()));
507        }
508        Ok(data)
509    }
510
511    fn to_bytes(&self) -> Vec<u8> {
512        let mut data = Vec::with_capacity(self.len() * 4);
513        for chunk in self {
514            data.extend_from_slice(&chunk.to_be_bytes());
515        }
516        data
517    }
518}
519
520impl FitsDataCollection for Vec<f64> {
521    fn from_bytes(raw: &[u8]) -> Result<Self, FitsHeaderError> {
522        let mut data = Vec::with_capacity(raw.len() / 8);
523        for chunk in raw.chunks_exact(8) {
524            data.push(f64::from_be_bytes(chunk.try_into().unwrap()));
525        }
526        Ok(data)
527    }
528
529    fn to_bytes(&self) -> Vec<u8> {
530        let mut data = Vec::with_capacity(self.len() * 8);
531        for chunk in self {
532            data.extend_from_slice(&chunk.to_be_bytes());
533        }
534        data
535    }
536}
537
538#[macro_use]
539pub(crate) mod hdu_macros {
540    /// Creates a box of the given value and casts it to an implicit return type.
541    macro_rules! return_box {
542        ($result: expr) => {{
543            let b = Box::new($result);
544            let ptr = Box::into_raw(b);
545            let new_ptr = ptr.cast();
546            Box::from_raw(new_ptr)
547        }};
548    }
549
550    pub(crate) use return_box;
551}