astro_rs/fits/header_value/
tform.rs

1//! Defines the TFORM header value.
2
3use crate::fits::hdu_macros::return_box;
4use crate::fits::FitsHeaderError;
5
6use super::FitsHeaderValue;
7
8/// An enumeration of valid types corresponding to the TFORM keyword.
9#[allow(missing_docs)]
10#[derive(Debug, Clone, Copy)]
11pub enum TFormType {
12    Logical,
13    Bit,
14    UnsignedByte,
15    I16,
16    I32,
17    Character,
18    F32,
19    F64,
20    C64,
21    C128,
22    ArrayDescriptor,
23}
24
25impl TryFrom<char> for TFormType {
26    type Error = FitsHeaderError;
27
28    fn try_from(value: char) -> Result<Self, Self::Error> {
29        match value {
30            'L' => Ok(TFormType::Logical),
31            'X' => Ok(TFormType::Bit),
32            'B' => Ok(TFormType::UnsignedByte),
33            'I' => Ok(TFormType::I16),
34            'J' => Ok(TFormType::I32),
35            'A' => Ok(TFormType::Character),
36            'E' => Ok(TFormType::F32),
37            'D' => Ok(TFormType::F64),
38            'C' => Ok(TFormType::C64),
39            'M' => Ok(TFormType::C128),
40            'P' => Ok(TFormType::ArrayDescriptor),
41            _ => Err(FitsHeaderError::DeserializationError {
42                found: vec![value as u8],
43                intent: String::from("header card TFORM type value"),
44            }),
45        }
46    }
47}
48
49/// A value corresponding to the TFORM keyword.
50#[derive(Debug, Clone)]
51pub struct TForm {
52    /// The repeat count
53    pub r: usize,
54    /// The field type
55    pub t: TFormType,
56    /// Undefined additional characters
57    pub a: String,
58}
59
60impl TForm {
61    /// Gets the number of bytes required by the column.
62    pub fn value(&self) -> usize {
63        let type_bytes = match self.t {
64            TFormType::Logical => 1,
65            TFormType::Bit => todo!(),
66            TFormType::UnsignedByte => 1,
67            TFormType::I16 => 2,
68            TFormType::I32 => 4,
69            TFormType::Character => 1,
70            TFormType::F32 => 4,
71            TFormType::F64 => 8,
72            TFormType::C64 => 8,
73            TFormType::C128 => 16,
74            TFormType::ArrayDescriptor => 8,
75        };
76        self.r * type_bytes
77    }
78
79    /// Creates a column of values from the given data.
80    pub fn create_column<T>(
81        &self,
82        data: &[u8],
83        column_start: usize,
84        row_len: usize,
85        num_rows: usize,
86    ) -> Box<Vec<T>> {
87        let column_len = self.value();
88        unsafe {
89            match self.t {
90                TFormType::Logical => {
91                    let mut result = Vec::with_capacity(num_rows * self.r);
92                    for i in 0..num_rows {
93                        let start = row_len * i + column_start;
94                        let column = data[start..start + column_len].to_vec();
95                        for value in column.iter().take(self.r) {
96                            result.push(*value != 0);
97                        }
98                    }
99
100                    return_box!(result)
101                }
102                TFormType::Bit => todo!(),
103                TFormType::UnsignedByte => {
104                    let mut result = Vec::with_capacity(num_rows * self.r);
105                    for i in 0..num_rows {
106                        let start = row_len * i + column_start;
107                        let mut column = data[start..start + column_len].to_vec();
108                        result.append(&mut column);
109                    }
110
111                    return_box!(result)
112                }
113                TFormType::I16 => {
114                    tform_macros::deserialize_column!(
115                        i16,
116                        num_rows,
117                        row_len,
118                        column_start,
119                        column_len,
120                        self.r,
121                        data,
122                    )
123                }
124                TFormType::I32 => {
125                    tform_macros::deserialize_column!(
126                        i32,
127                        num_rows,
128                        row_len,
129                        column_start,
130                        column_len,
131                        self.r,
132                        data,
133                    )
134                }
135                TFormType::Character => {
136                    unsafe fn deserialize_char(value: [u8; 4]) -> char {
137                        char::from_u32_unchecked(u32::from_be_bytes(value))
138                    }
139                    tform_macros::deserialize_column!(
140                        char,
141                        num_rows,
142                        row_len,
143                        column_start,
144                        column_len,
145                        self.r,
146                        data,
147                        deserialize_char,
148                    )
149                }
150                TFormType::F32 => {
151                    tform_macros::deserialize_column!(
152                        f32,
153                        num_rows,
154                        row_len,
155                        column_start,
156                        column_len,
157                        self.r,
158                        data,
159                    )
160                }
161                TFormType::F64 => {
162                    tform_macros::deserialize_column!(
163                        f64,
164                        num_rows,
165                        row_len,
166                        column_start,
167                        column_len,
168                        self.r,
169                        data,
170                    )
171                }
172                TFormType::C64 => todo!(),
173                TFormType::C128 => todo!(),
174                TFormType::ArrayDescriptor => todo!(),
175            }
176        }
177    }
178}
179
180#[macro_use]
181mod tform_macros {
182    /// Creates a boxed vector deserialized with the given function, or a default function if one is not given.
183    macro_rules! deserialize_column {
184        (@dfn $value_type: ty) => {{
185            <$value_type>::from_be_bytes
186        }};
187        ($value_type: ty, $num_rows: expr, $row_len: expr, $column_start: expr, $column_len: expr, $repeats: expr, $data: expr,) => {{
188            let deserialize_fn = $crate::fits::header_value::tform::tform_macros::deserialize_column!(@dfn $value_type);
189            $crate::fits::header_value::tform::tform_macros::deserialize_column!(
190                $value_type,
191                $num_rows,
192                $row_len,
193                $column_start,
194                $column_len,
195                $repeats,
196                $data,
197                deserialize_fn,
198            )
199        }};
200        ($value_type: ty, $num_rows: expr, $row_len: expr, $column_start: expr, $column_len: expr, $repeats: expr, $data: expr, $deserialize_fn: tt,) => {{
201            let mut result = Vec::with_capacity($num_rows * $repeats);
202            for i in 0..$num_rows {
203                let start = $row_len * i + $column_start;
204                let column = $data[start..start + $column_len].to_vec();
205                let value_size = std::mem::size_of::<$value_type>();
206                for repeat in 0..$repeats {
207                    let value_start = repeat * value_size;
208                    let raw_value = column[value_start..value_start + value_size]
209                        .try_into()
210                        .unwrap();
211                    result.push($deserialize_fn(raw_value));
212                }
213            }
214
215            $crate::fits::hdu_macros::return_box!(result)
216        }};
217    }
218
219    pub(crate) use deserialize_column;
220}
221
222/// ```
223/// use astro_rs::fits::*;
224///
225/// // successful deserialization
226/// let double_value: TForm = FitsHeaderValue::from_bytes(b"'1D      '".to_vec())?;
227/// assert_eq!(double_value.value(), 8);
228/// let repeat_int_value: TForm = FitsHeaderValue::from_bytes(b"'2I      '".to_vec())?;
229/// assert_eq!(repeat_int_value.value(), 4);
230/// let comment_char_value: TForm = FitsHeaderValue::from_bytes(b"'1A comment'".to_vec())?;
231/// assert_eq!(comment_char_value.value(), 1);
232/// let short_complex_value: TForm = FitsHeaderValue::from_bytes(b"'M       '".to_vec())?;
233/// assert_eq!(short_complex_value.value(), 16);
234///
235/// // failed deserialization
236/// let result: Result<TForm, FitsHeaderError> = FitsHeaderValue::from_bytes(b"U".to_vec());
237/// assert!(result.is_err());
238/// let result: Result<TForm, FitsHeaderError> = FitsHeaderValue::from_bytes(b"nonsense".to_vec());
239/// assert!(result.is_err());
240///
241/// // serialization
242/// assert_eq!(double_value.to_bytes(), *b"'1D      '                                                            ");
243/// assert_eq!(repeat_int_value.to_bytes(), *b"'2I      '                                                            ");
244/// assert_eq!(comment_char_value.to_bytes(), *b"'1A comment'                                                          ");
245/// assert_eq!(short_complex_value.to_bytes(), *b"'1M      '                                                            ");
246/// # Ok::<(), astro_rs::fits::FitsHeaderError>(())
247/// ```
248impl FitsHeaderValue for TForm {
249    fn from_bytes(raw: Vec<u8>) -> Result<Self, FitsHeaderError> {
250        let mut repeats = String::new();
251        let mut ttype = None;
252        let mut i = 1;
253        while i < raw.len() - 1 {
254            let ch = raw[i] as char;
255            if ch.is_ascii_digit() {
256                repeats.push(ch);
257                i += 1;
258            } else {
259                ttype = Some(TFormType::try_from(ch)?);
260                i += 1;
261                break;
262            }
263        }
264        let r = repeats.parse::<u32>().unwrap_or(1) as usize;
265        if let Some(t) = ttype {
266            if let Ok(a) = String::from_utf8(raw[i..raw.len() - 1].to_vec()) {
267                return Ok(TForm {
268                    r,
269                    t,
270                    a: a.trim_end().to_owned(),
271                });
272            }
273        }
274        Err(FitsHeaderError::DeserializationError {
275            found: raw,
276            intent: String::from("header card TFORM value"),
277        })
278    }
279
280    fn to_bytes(&self) -> [u8; 70] {
281        let mut result = [b' '; 70];
282        let mut i = 0;
283        result[i] = b'\'';
284        i += 1;
285        let repeats = self.r.to_string();
286        for b in repeats.bytes() {
287            result[i] = b;
288            i += 1;
289        }
290        match self.t {
291            TFormType::Logical => result[i] = b'L',
292            TFormType::Bit => result[i] = b'X',
293            TFormType::UnsignedByte => result[i] = b'B',
294            TFormType::I16 => result[i] = b'I',
295            TFormType::I32 => result[i] = b'J',
296            TFormType::Character => result[i] = b'A',
297            TFormType::F32 => result[i] = b'E',
298            TFormType::F64 => result[i] = b'D',
299            TFormType::C64 => result[i] = b'C',
300            TFormType::C128 => result[i] = b'M',
301            TFormType::ArrayDescriptor => result[i] = b'P',
302        }
303        i += 1;
304        for b in self.a.bytes() {
305            result[i] = b;
306            i += 1;
307        }
308        if i < 9 {
309            i = 9;
310        }
311        result[i] = b'\'';
312        result
313    }
314}