Skip to content

#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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

chrsoo
Copy link
Contributor

@chrsoo chrsoo commented Dec 29, 2024

PR that does not (yet) follow the contributing guidelines to facilitate the discussion on #374.

@codecov-commenter
Copy link

codecov-commenter commented Dec 29, 2024

Codecov Report

Attention: Patch coverage is 84.21053% with 15 lines in your changes missing coverage. Please review.

Project coverage is 88.9%. Comparing base (53611ef) to head (c3a83e4).

Files with missing lines Patch % Lines
fitsio/src/headers/card.rs 64.2% 10 Missing ⚠️
fitsio/src/hdu.rs 92.5% 5 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
fitsio/src/headers/mod.rs 82.8% <ø> (ø)
fitsio/src/hdu.rs 97.2% <92.5%> (-0.9%) ⬇️
fitsio/src/headers/card.rs 64.2% <64.2%> (ø)

@simonrw simonrw self-requested a review December 29, 2024 21:55
Copy link
Owner

@simonrw simonrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this proposal. I have a few suggestions that would be good to iron out before I review in full 🎉

@@ -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 {
Copy link
Owner

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_key to support either a name or index, and support returning the Card type so we can return the key as well. Then the iterator would boil down to something like

// TODO: proper types
struct CardIter {
    idx: usize,
    num_cards: usize,
    fits_file: FitsFile,
    hdu: FitsHdu,
}

impl Iterator for CardIter {
    type Item = Card;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.num_cards {
            return None;
        }

        // Generic return type means we can implement `ReadsKey` for `Card` which returns the name, value and (optional) comment
        let card: Card = self.hdu.read_key(&mut self.fits_file, self.idx).unwrap();  // TODO: error handling
        Some(card)
    }
}

Copy link
Contributor Author

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 fitsio API and missed that this was a possibility.

Copy link
Contributor Author

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_keyn nor fits_read_record routine, 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 ffgrec or read all headers using fits_hdr2str as mentioned in your last comment in #374.

Comment on lines +11 to +13
pub(crate) name: [i8;FLEN_KEYWORD as usize],
pub(crate) value: [i8;FLEN_VALUE as usize],
pub(crate) comment: [i8;FLEN_COMMENT as usize],
Copy link
Owner

Choose a reason for hiding this comment

The 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 Card creation) however I prefer doing the work up front:

  • the Card type is really a data object, with no inherent behaviour. As such, it really should just be a wrapper around the state
  • if the user wants the Card then they likely want at least two of the fields (name and value) so most of the conversion work is not wasted
  • the conversion is not exactly intensive so it probably not a performance issue right now.

Copy link
Contributor Author

@chrsoo chrsoo Dec 29, 2024

Choose a reason for hiding this comment

The 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:

  • Using a the i8 array as-is; or
  • Introducing a enum with variants of each type carrying a value.

I am still quite unfamiliar with the details of the fitsio API but I don't think we have that enum and the generic return types of read_key points to the first option with the buffer.

The struct would thus change to

pub(crate) name:       String,
pub(crate) comment:    String,
pub(crate) value:      [i8;FLEN_VALUE as usize],

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

@chrsoo chrsoo Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • the Card type is really a data object, with no inherent behaviour. As such, it really should just be a wrapper around the state

The raw ì8 buffers are still state, just not parsed state... ;-)

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 CardIter<String> for HDU.cards().

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 HDU.cards() signature could take a generic type and we would gracefully handle all failed conversions. This would effectively be filtering by keyword type.

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()?);
Copy link
Owner

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants