Skip to main content

fitsio/headers/
mod.rs

1//! Header-related code
2use crate::errors::{check_status, Result};
3use crate::fitsfile::FitsFile;
4use crate::longnam::*;
5use crate::types::HasFitsDataType;
6use std::ffi;
7use std::ptr;
8
9mod constants;
10mod header_value;
11
12use constants::{MAX_COMMENT_LENGTH, MAX_VALUE_LENGTH};
13pub use header_value::HeaderValue;
14
15/**
16Trait applied to types which can be read from a FITS header
17
18This is currently:
19
20* i32
21* i64
22* f32
23* f64
24* String
25* */
26pub trait ReadsKey {
27    #[doc(hidden)]
28    fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
29    where
30        Self: Sized;
31}
32
33macro_rules! reads_key_impl {
34    ($t:ty) => {
35        impl ReadsKey for $t {
36            fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
37                let hv: HeaderValue<$t> = ReadsKey::read_key(f, name)?;
38                Ok(hv.value)
39            }
40        }
41        impl ReadsKey for HeaderValue<$t>
42        where
43            $t: Default,
44        {
45            fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
46                let c_name = ffi::CString::new(name)?;
47                let mut status = 0;
48                let mut value: Self = Default::default();
49                let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
50
51                unsafe {
52                    fits_read_key(
53                        f.fptr.as_mut() as *mut _,
54                        <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
55                        c_name.as_ptr(),
56                        &mut value.value as *mut $t as *mut c_void,
57                        comment.as_mut_ptr(),
58                        &mut status,
59                    );
60                }
61
62                check_status(status).map(|_| {
63                    let comment = {
64                        let comment: Vec<u8> = comment
65                            .iter()
66                            .map(|&x| x as u8)
67                            .filter(|&x| x != 0)
68                            .collect();
69                        if comment.is_empty() {
70                            None
71                        } else {
72                            String::from_utf8(comment).ok()
73                        }
74                    };
75
76                    value.comment = comment;
77
78                    value
79                })
80            }
81        }
82    };
83}
84
85reads_key_impl!(i32);
86reads_key_impl!(i64);
87reads_key_impl!(f32);
88reads_key_impl!(f64);
89
90impl ReadsKey for bool {
91    fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
92    where
93        Self: Sized,
94    {
95        i32::read_key(f, name).map(|v| v > 0)
96    }
97}
98
99impl ReadsKey for HeaderValue<bool> {
100    fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
101    where
102        Self: Sized,
103    {
104        let hv: HeaderValue<i32> = ReadsKey::read_key(f, name)?;
105        Ok(hv.map(|v| v > 0))
106    }
107}
108
109impl ReadsKey for String {
110    fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
111        let hv: HeaderValue<String> = ReadsKey::read_key(f, name)?;
112        Ok(hv.value)
113    }
114}
115
116impl ReadsKey for HeaderValue<String> {
117    fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
118        let c_name = ffi::CString::new(name)?;
119        let mut status = 0;
120        let mut value: Vec<c_char> = vec![0; MAX_VALUE_LENGTH];
121        let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
122
123        unsafe {
124            fits_read_key_str(
125                f.fptr.as_mut() as *mut _,
126                c_name.as_ptr(),
127                value.as_mut_ptr(),
128                comment.as_mut_ptr(),
129                &mut status,
130            );
131        }
132
133        check_status(status).and_then(|_| {
134            let value: Vec<u8> = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect();
135            String::from_utf8(value)
136                .map(|value| {
137                    let comment = {
138                        let comment: Vec<u8> = comment
139                            .iter()
140                            .map(|&x| x as u8)
141                            .filter(|&x| x != 0)
142                            .collect();
143                        if comment.is_empty() {
144                            None
145                        } else {
146                            String::from_utf8(comment).ok()
147                        }
148                    };
149                    HeaderValue { value, comment }
150                })
151                .map_err(From::from)
152        })
153    }
154}
155
156/// Writing a fits keyword
157pub trait WritesKey {
158    #[doc(hidden)]
159    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()>;
160}
161
162macro_rules! writes_key_impl {
163    ($t:ty) => {
164        impl WritesKey for $t {
165            fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
166                let c_name = ffi::CString::new(name)?;
167                let mut status = 0;
168
169                unsafe {
170                    fits_write_key(
171                        f.fptr.as_mut() as *mut _,
172                        <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
173                        c_name.as_ptr(),
174                        &value as *const $t as *mut c_void,
175                        ptr::null_mut(),
176                        &mut status,
177                    );
178                }
179                check_status(status)
180            }
181        }
182
183        impl WritesKey for ($t, &str) {
184            fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
185                let (value, comment) = value;
186                let c_name = ffi::CString::new(name)?;
187                let c_comment = ffi::CString::new(comment)?;
188                let mut status = 0;
189
190                unsafe {
191                    fits_write_key(
192                        f.fptr.as_mut() as *mut _,
193                        <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
194                        c_name.as_ptr(),
195                        &value as *const $t as *mut c_void,
196                        c_comment.as_ptr(),
197                        &mut status,
198                    );
199                }
200                check_status(status)
201            }
202        }
203
204        impl WritesKey for ($t, String) {
205            #[inline(always)]
206            fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
207                let (value, comment) = value;
208                WritesKey::write_key(f, name, (value, comment.as_str()))
209            }
210        }
211    };
212}
213
214writes_key_impl!(i8);
215writes_key_impl!(i16);
216writes_key_impl!(i32);
217writes_key_impl!(i64);
218writes_key_impl!(u8);
219writes_key_impl!(u16);
220writes_key_impl!(u32);
221writes_key_impl!(u64);
222writes_key_impl!(f32);
223writes_key_impl!(f64);
224
225impl WritesKey for String {
226    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
227        WritesKey::write_key(f, name, value.as_str())
228    }
229}
230
231impl WritesKey for &'_ str {
232    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
233        let c_name = ffi::CString::new(name)?;
234        let c_value = ffi::CString::new(value)?;
235        let mut status = 0;
236
237        unsafe {
238            fits_write_key_str(
239                f.fptr.as_mut() as *mut _,
240                c_name.as_ptr(),
241                c_value.as_ptr(),
242                ptr::null_mut(),
243                &mut status,
244            );
245        }
246
247        check_status(status)
248    }
249}
250
251impl WritesKey for (String, &str) {
252    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
253        let (value, comment) = value;
254        WritesKey::write_key(f, name, (value.as_str(), comment))
255    }
256}
257
258impl WritesKey for (String, String) {
259    #[inline(always)]
260    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
261        let (value, comment) = value;
262        WritesKey::write_key(f, name, (value.as_str(), comment.as_str()))
263    }
264}
265
266impl<'a> WritesKey for (&'a str, &'a str) {
267    fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
268        let (value, comment) = value;
269        let c_name = ffi::CString::new(name)?;
270        let c_value = ffi::CString::new(value)?;
271        let c_comment = ffi::CString::new(comment)?;
272        let mut status = 0;
273
274        unsafe {
275            fits_write_key_str(
276                f.fptr.as_mut() as *mut _,
277                c_name.as_ptr(),
278                c_value.as_ptr(),
279                c_comment.as_ptr(),
280                &mut status,
281            );
282        }
283
284        check_status(status)
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::testhelpers::{duplicate_test_file, floats_close_f64, with_temp_file};
292
293    #[test]
294    fn test_reading_header_keys() {
295        let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
296        let hdu = f.hdu(0).unwrap();
297        match hdu.read_key::<i64>(&mut f, "INTTEST") {
298            Ok(value) => assert_eq!(value, 42),
299            Err(e) => panic!("Error reading key: {:?}", e),
300        }
301
302        match hdu.read_key::<f64>(&mut f, "DBLTEST") {
303            Ok(value) => assert!(
304                floats_close_f64(value, 0.09375),
305                "{:?} != {:?}",
306                value,
307                0.09375
308            ),
309            Err(e) => panic!("Error reading key: {:?}", e),
310        }
311
312        match hdu.read_key::<String>(&mut f, "TEST") {
313            Ok(value) => assert_eq!(value, "value"),
314            Err(e) => panic!("Error reading key: {:?}", e),
315        }
316    }
317
318    #[test]
319    fn test_writing_header_keywords() {
320        with_temp_file(|filename| {
321            // Scope ensures file is closed properly
322            {
323                let mut f = FitsFile::create(filename).open().unwrap();
324                f.hdu(0).unwrap().write_key(&mut f, "FOO", 1i64).unwrap();
325                f.hdu(0)
326                    .unwrap()
327                    .write_key(&mut f, "BAR", "baz".to_string())
328                    .unwrap();
329            }
330
331            FitsFile::open(filename)
332                .map(|mut f| {
333                    assert_eq!(f.hdu(0).unwrap().read_key::<i64>(&mut f, "foo").unwrap(), 1);
334                    assert_eq!(
335                        f.hdu(0).unwrap().read_key::<String>(&mut f, "bar").unwrap(),
336                        "baz".to_string()
337                    );
338                })
339                .unwrap();
340        });
341    }
342
343    #[test]
344    fn test_writing_with_comments() {
345        with_temp_file(|filename| {
346            // Scope ensures file is closed properly
347            {
348                let mut f = FitsFile::create(filename).open().unwrap();
349                f.hdu(0)
350                    .unwrap()
351                    .write_key(&mut f, "FOO", (1i64, "Foo value"))
352                    .unwrap();
353                f.hdu(0)
354                    .unwrap()
355                    .write_key(&mut f, "BAR", ("baz".to_string(), "baz value"))
356                    .unwrap();
357            }
358
359            FitsFile::open(filename)
360                .map(|mut f| {
361                    let foo_header_value = f
362                        .hdu(0)
363                        .unwrap()
364                        .read_key::<HeaderValue<i64>>(&mut f, "foo")
365                        .unwrap();
366                    assert_eq!(foo_header_value.value, 1);
367                    assert_eq!(foo_header_value.comment, Some("Foo value".to_string()));
368                })
369                .unwrap();
370        });
371    }
372
373    #[test]
374    fn test_writing_reading_empty_comment() {
375        with_temp_file(|filename| {
376            // Scope ensures file is closed properly
377            {
378                let mut f = FitsFile::create(filename).open().unwrap();
379                f.hdu(0)
380                    .unwrap()
381                    .write_key(&mut f, "FOO", (1i64, ""))
382                    .unwrap();
383            }
384
385            FitsFile::open(filename)
386                .map(|mut f| {
387                    let foo_header_value = f
388                        .hdu(0)
389                        .unwrap()
390                        .read_key::<HeaderValue<i64>>(&mut f, "foo")
391                        .unwrap();
392                    assert_eq!(foo_header_value.value, 1);
393                    assert!(foo_header_value.comment.is_none());
394                })
395                .unwrap();
396        });
397    }
398
399    #[test]
400    fn test_writing_integers() {
401        duplicate_test_file(|filename| {
402            let mut f = FitsFile::edit(filename).unwrap();
403            let hdu = f.hdu(0).unwrap();
404            hdu.write_key(&mut f, "ONE", -1i8).unwrap();
405            hdu.write_key(&mut f, "TWO", -500i16).unwrap();
406            hdu.write_key(&mut f, "THREE", -1_000_000i32).unwrap();
407            hdu.write_key(&mut f, "FOUR", -99_000_000_000i64).unwrap();
408            hdu.write_key(&mut f, "UONE", 1u8).unwrap();
409            hdu.write_key(&mut f, "UTWO", 500u16).unwrap();
410            hdu.write_key(&mut f, "UTHREE", 1_000_000u32).unwrap();
411            hdu.write_key(&mut f, "UFOUR", 3_000_000_000u32).unwrap();
412            hdu.write_key(&mut f, "UFIVE", 99_000_000_000u64).unwrap();
413
414            // make sure we round-trip:
415
416            assert_matches1!(hdu.read_key(&mut f, "ONE"), Ok(-1i32));
417            assert_matches1!(hdu.read_key(&mut f, "ONE"), Ok(-1i64));
418
419            assert_matches1!(hdu.read_key(&mut f, "TWO"), Ok(-500i32));
420            assert_matches1!(hdu.read_key(&mut f, "TWO"), Ok(-500i64));
421
422            assert_matches1!(hdu.read_key(&mut f, "THREE"), Ok(-1_000_000i32));
423            assert_matches1!(hdu.read_key(&mut f, "THREE"), Ok(-1_000_000i64));
424
425            assert_matches1!(hdu.read_key::<i32>(&mut f, "FOUR"), Err(_));
426            assert_matches1!(hdu.read_key(&mut f, "FOUR"), Ok(-99_000_000_000i64));
427
428            assert_matches1!(hdu.read_key(&mut f, "UONE"), Ok(1i32));
429            assert_matches1!(hdu.read_key(&mut f, "UONE"), Ok(1i64));
430
431            assert_matches1!(hdu.read_key(&mut f, "UTWO"), Ok(500i32));
432            assert_matches1!(hdu.read_key(&mut f, "UTWO"), Ok(500i64));
433
434            assert_matches1!(hdu.read_key(&mut f, "UTHREE"), Ok(1_000_000i32));
435            assert_matches1!(hdu.read_key(&mut f, "UTHREE"), Ok(1_000_000i64));
436
437            assert_matches1!(hdu.read_key::<i32>(&mut f, "UFOUR"), Err(_));
438            assert_matches1!(hdu.read_key(&mut f, "UFOUR"), Ok(3_000_000_000i64));
439
440            assert_matches1!(hdu.read_key::<i32>(&mut f, "UFIVE"), Err(_));
441            assert_matches1!(hdu.read_key(&mut f, "UFIVE"), Ok(99_000_000_000i64));
442        });
443    }
444
445    #[test]
446    fn boolean_header_values() {
447        let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
448        let hdu = f.primary_hdu().unwrap();
449
450        let res = hdu.read_key::<bool>(&mut f, "SIMPLE").unwrap();
451        assert!(res);
452    }
453}
454
455#[cfg(test)]
456mod headervalue_tests {
457    use super::HeaderValue;
458
459    #[test]
460    fn equate_different_types() {
461        let v = HeaderValue {
462            value: 1i64,
463            comment: Some("".to_string()),
464        };
465
466        assert_eq!(v, 1i64);
467    }
468}