-
Notifications
You must be signed in to change notification settings - Fork 20
#374 added Card structure and the cards() method on HDU #375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8ceb6fe
af6e89e
283eeec
c3a83e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,19 @@ | ||
| //! Fits HDU related code | ||
|
|
||
| use crate::errors::{check_status, Result}; | ||
| use fitsio_sys::{ffghsp, ffgkyn}; | ||
|
|
||
| use crate::errors::{check_status, Error, Result}; | ||
| use crate::fitsfile::CaseSensitivity; | ||
| use crate::fitsfile::FitsFile; | ||
| use crate::headers::card::Card; | ||
| use crate::headers::{ReadsKey, WritesKey}; | ||
| use crate::images::{ImageType, ReadImage, WriteImage}; | ||
| use crate::longnam::*; | ||
| use crate::tables::{ | ||
| ColumnIterator, ConcreteColumnDescription, DescribesColumnLocation, FitsRow, ReadsCol, | ||
| WritesCol, | ||
| }; | ||
| use std::ffi; | ||
| use std::{ffi, ptr}; | ||
| use std::ops::Range; | ||
|
|
||
| /// Struct representing a FITS HDU | ||
|
|
@@ -66,6 +69,11 @@ impl FitsHdu { | |
| T::read_key(fits_file, name) | ||
| } | ||
|
|
||
| /// Return cards available in the HDU header. | ||
| pub fn cards(&self, fits_file: &mut FitsFile) -> Result<CardIter> { | ||
| CardIter::new(fits_file, self) | ||
| } | ||
|
|
||
| /** | ||
| Write a fits key to the current header | ||
|
|
||
|
|
@@ -1062,11 +1070,99 @@ hduinfo_into_impl!(i8); | |
| hduinfo_into_impl!(i32); | ||
| hduinfo_into_impl!(i64); | ||
|
|
||
| /// Iterator for the keys of an HDU header. | ||
| pub struct CardIter { | ||
| fptr: *mut fitsfile, | ||
| total: c_int, | ||
| current: c_int, | ||
| // name: [i8;FLEN_KEYWORD as usize], | ||
| // value: [i8;FLEN_VALUE as usize], | ||
| // comment: [i8;FLEN_COMMENT as usize], | ||
| status: c_int, | ||
| } | ||
|
|
||
| impl CardIter { | ||
| fn new(file: &mut FitsFile, hdu: &FitsHdu) -> Result<CardIter> { | ||
| file.make_current(hdu)?; | ||
|
|
||
| let mut iter = CardIter { | ||
| fptr: unsafe { file.as_raw() }, | ||
| total: 0, | ||
| current: 1, | ||
| // name: [0i8;FLEN_KEYWORD as usize], | ||
| // value: [0i8;FLEN_VALUE as usize], | ||
| // comment: [0i8;FLEN_COMMENT as usize], | ||
| status: 0, | ||
| }; | ||
|
|
||
| let mut nmore: c_int = 0; | ||
| unsafe { | ||
| ffghsp( | ||
| iter.fptr, | ||
| ptr::addr_of_mut!(iter.total), | ||
| ptr::addr_of_mut!(nmore), | ||
| ptr::addr_of_mut!(iter.status) | ||
| ) | ||
| }; | ||
| if iter.total == 0 { | ||
| Err(Error::Message("no headers available".to_owned())) | ||
| } else { | ||
| iter.next_header()?; // reset the header count by using index 0 | ||
| Ok(iter) // iterator now points at first header card | ||
| } | ||
| } | ||
| fn next_header(&mut self) -> Result<Card> { | ||
| self.status = 0; // reset the status before calling | ||
| let mut card = Card::default(); | ||
| unsafe { | ||
| ffgkyn( | ||
| self.fptr, | ||
| self.current, | ||
| // self.name.as_mut_ptr(), | ||
| // self.value.as_mut_ptr(), | ||
| // self.comment.as_mut_ptr(), | ||
| card.name.as_mut_ptr() as *mut c_char, | ||
| card.value.as_mut_ptr() as *mut c_char, | ||
| card.comment.as_mut_ptr() as *mut c_char, | ||
| ptr::addr_of_mut!(self.status) | ||
| ) | ||
| }; | ||
| self.current += 1; | ||
| check_status(self.status)?; | ||
| Ok(card) | ||
| } | ||
| } | ||
|
|
||
| impl Iterator for CardIter { | ||
| type Item = Card; | ||
|
|
||
| fn next(&mut self) -> Option<Self::Item> { | ||
| if self.current > self.total { | ||
| return None | ||
| } | ||
| match self.next_header() { | ||
| Ok(card) => Some(card), | ||
| Err(e) => { | ||
| let mut card = Card::default(); | ||
| card.set_comment(format!("{e}")); | ||
| Some(card) | ||
| }, | ||
| } | ||
|
|
||
| // if let Err(e) = self.next_header() { | ||
| // Some(format!("{e}")) // return the error as the keyword | ||
| // } else { | ||
| // buf_to_string(self.name.as_slice()).ok() | ||
| // } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::FitsFile; | ||
| use crate::hdu::{FitsHdu, HduInfo}; | ||
| use crate::testhelpers::duplicate_test_file; | ||
| use crate::errors::Result; | ||
|
|
||
| #[test] | ||
| fn test_manually_creating_a_fits_hdu() { | ||
|
|
@@ -1098,6 +1194,16 @@ mod tests { | |
| assert_eq!(intcol_data[49], 12); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_cards() -> Result<()> { | ||
| let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); | ||
| let primary_hdu = f.hdu(0).unwrap(); | ||
| for card in primary_hdu.cards(&mut f).unwrap() { | ||
| println!("{:8} = {:10} - {}", card.name()?, card.str_value()?, card.comment()?); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course this test doesn't actually test anything, let's revisit when we have agreement on the overall design.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, indeed. Just wanted proof that the code could run without hitting a lifetime snag or similar. |
||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_fetch_hdu_name() { | ||
| duplicate_test_file(|filename| { | ||
|
|
@@ -1106,6 +1212,7 @@ mod tests { | |
| assert_eq!(hdu.name(&mut f).unwrap(), "TESTEXT".to_string()); | ||
| }); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_delete_hdu() { | ||
| duplicate_test_file(|filename| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| //! Fits HDU related code | ||
|
|
||
| use fitsio_sys::{FLEN_COMMENT, FLEN_KEYWORD, FLEN_VALUE}; | ||
|
|
||
| use crate::errors::Result; | ||
| use std::ffi::{c_char, CStr}; | ||
|
|
||
| /// Wraps a single header card | ||
| #[derive(Debug)] | ||
| pub struct Card { | ||
| pub(crate) name: [i8;FLEN_KEYWORD as usize], | ||
| pub(crate) value: [i8;FLEN_VALUE as usize], | ||
| pub(crate) comment: [i8;FLEN_COMMENT as usize], | ||
|
Comment on lines
+11
to
+13
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see why these methods are here in this implementation (lazily converting the types if required, rather than eagerly converting the types at
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name and comment will always be strings, but given that we don't know what type the value is we cannot do the conversion upfront and the data structure must support all possible value types. This leaves us with two options:
I am still quite unfamiliar with the details of the The struct would thus change to pub(crate) name: String,
pub(crate) comment: String,
pub(crate) value: [i8;FLEN_VALUE as usize],Thoughts?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or better yet: pub(crate) name: String,
pub(crate) comment: Option<String>,
pub(crate) value: [i8;FLEN_VALUE as usize],... given that comments are optional.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The raw Regarding doing conversion work up front, using an enum for values is only an option if we accept that we cannot distinguish between integers and float. Which one to go for, must be a choice of the code consuming the value. Presumably based on prior knowledge of the semantics of the header. What we can do though, is to parse the raw data structures up front into safe code. Given that the iterator can only use one (concrete) type parameter we could have the following definition: pub struct Card<T> {
pub(crate) name: String,
pub(crate) comment: Option<String>,
pub(crate) value: T,
}... and use a generic I think this should work fine as, iterating over the cards is something I would expect we do only when we do not need a specific keyword. Any type conversions can be done in a next step. If we do need a specific keyword, we should know the type and can retrieve the converted keyword by name . @simonrw unless you see any issues with this approach, this is what I will go for. If we want to be a bit fancy, the |
||
| } | ||
|
|
||
| impl Card { | ||
| /// Header keyword. | ||
| pub fn name(&self) -> Result<&str> { | ||
| Ok(unsafe { CStr::from_ptr(self.name.as_ptr() as *mut c_char) }.to_str()?) | ||
| } | ||
|
|
||
| /// Header comment. | ||
| pub fn comment(&self) -> Result<&str> { | ||
| Ok(unsafe { CStr::from_ptr(self.comment.as_ptr() as *mut c_char) }.to_str()?) | ||
| } | ||
|
|
||
| /// Header value as a &str without enclosing quotes. | ||
| pub fn str_value(&self) -> Result<&str> { | ||
| let cstr = unsafe { CStr::from_ptr(self.value.as_ptr() as *mut c_char) }; | ||
| let str = cstr.to_str()?.trim_matches('\''); | ||
| Ok(str) | ||
| } | ||
|
|
||
| pub(crate) fn set_comment(&mut self, comment: String) { | ||
| self.comment.fill(0); // clear the buffer before using it, ensure null termination | ||
| let mut i = 0; | ||
| for b in comment.into_bytes() { | ||
| self.comment[i] = b as i8; | ||
| i += 1; | ||
| if i >= self.comment.len() - 1 { // C string must be null terminated | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Default for Card { | ||
| fn default() -> Self { | ||
| Card { | ||
| name: [0i8;FLEN_KEYWORD as usize], | ||
| value: [0i8;FLEN_VALUE as usize], | ||
| comment: [0i8;FLEN_COMMENT as usize], | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be better to update
read_keyto support either a name or index, and support returning theCardtype so we can return the key as well. Then the iterator would boil down to something likeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds right, I am new to the both FITS and the
fitsioAPI and missed that this was a possibility.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just read the section 4 in the FITS standard related to headers and realised that long strings are not supported neither by
fits_read_keynnorfits_read_recordroutine, but I am guessing that the latter would return CONTINUE records that allows the piecing together of long strings.If we want support for long strings from the beginning we must use either
ffgrecor read all headers usingfits_hdr2stras mentioned in your last comment in #374.