-
Notifications
You must be signed in to change notification settings - Fork 57
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
Use new non-standard FITS extension for ASDF-in-FITS #412
Changes from 9 commits
55e2bf5
91476b1
221dde8
092c78e
0147779
ed19e6f
57fa800
a29dbd0
0022c16
24b0865
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 |
---|---|---|
|
@@ -20,15 +20,87 @@ | |
|
||
try: | ||
from astropy.io import fits | ||
from astropy.io.fits.file import _File | ||
from astropy.io.fits.header import Header, _pad_length | ||
except ImportError: | ||
raise ImportError("AsdfInFits requires astropy") | ||
|
||
|
||
ASDF_EXTENSION_NAME = 'ASDF' | ||
|
||
FITS_SOURCE_PREFIX = 'fits:' | ||
|
||
|
||
__all__ = ['AsdfInFits'] | ||
|
||
|
||
class _AsdfHDU(fits.hdu.base.NonstandardExtHDU): | ||
""" | ||
A non-standard extension HDU for encapsulating an entire ASDF file within a | ||
single HDU of a container FITS file. These HDUs have an extension (that is | ||
an XTENSION keyword) of ASDF. | ||
""" | ||
|
||
_extension = ASDF_EXTENSION_NAME | ||
|
||
@classmethod | ||
def from_buff(cls, buff, compress=False, **kwargs): | ||
""" | ||
Creates a new _AsdfHDU from a given AsdfFile object. | ||
|
||
Parameters | ||
---------- | ||
buff : io.BytesIO | ||
A buffer containing an ASDF metadata tree | ||
compress : bool, optional | ||
Gzip compress the contents of the ASDF HDU | ||
""" | ||
|
||
if compress: | ||
buff = gzip.GzipFile(fileobj=buff, mode='wb') | ||
|
||
# A proper HDU should still be padded out to a multiple of 2880 | ||
# technically speaking | ||
data_length = buff.tell() | ||
padding = (_pad_length(data_length) * cls._padding_byte).encode('ascii') | ||
buff.write(padding) | ||
|
||
buff.seek(0) | ||
|
||
cards = [ | ||
('XTENSION', cls._extension, 'ASDF extension'), | ||
('BITPIX', 8, 'array data type'), | ||
('NAXIS', 1, 'number of array dimensions'), | ||
('NAXIS1', len(buff.getvalue()), 'Axis length'), | ||
('PCOUNT', 0, 'number of parameters'), | ||
('GCOUNT', 1, 'number of groups'), | ||
('COMPRESS', compress, 'Uses gzip compression'), | ||
('EXTNAME', cls._extension, 'Name of ASDF extension'), | ||
] | ||
|
||
header = Header(cards) | ||
return cls._readfrom_internal(_File(buff), header=header) | ||
|
||
|
||
@classmethod | ||
def match_header(cls, header): | ||
card = header.cards[0] | ||
if card.keyword != 'XTENSION': | ||
return False | ||
xtension = card.value | ||
if isinstance(xtension, str): | ||
xtension = xtension.rstrip() | ||
return xtension == cls._extension | ||
|
||
# TODO: Add header verification | ||
|
||
def _summary(self): | ||
# TODO: Perhaps make this more descriptive... | ||
return (self.name, self.ver, self.__class__.__name__, len(self._header)) | ||
|
||
|
||
fits.register_hdu(_AsdfHDU) | ||
|
||
|
||
class _FitsBlock(object): | ||
def __init__(self, hdu): | ||
self._hdu = hdu | ||
|
@@ -222,9 +294,17 @@ def open(cls, fd, uri=None, validate_checksums=False, extensions=None, | |
return cls._open_asdf(self, buff, uri=uri, mode='r', | ||
validate_checksums=validate_checksums) | ||
|
||
def _create_hdu(self, buff, use_image_hdu): | ||
# Allow writing to old-style ImageHDU for backwards compatibility | ||
if use_image_hdu: | ||
array = np.frombuffer(buff.getvalue(), np.uint8) | ||
return fits.ImageHDU(array, name=ASDF_EXTENSION_NAME) | ||
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. Is the use of the ASDF_EXTENSION_NAME value here going to cause the XTENSION keyword to still have a value of "ASDF", even though in this case we want it to be "IMAGE"? Or does "name" in this case set the value of the EXTNAME keyword? 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. It's the latter: the However, this is the backwards compatible case. In the new case, both 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. 👍 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. Howard, here's a comparison of a current DMS NIRISS rate file read in and written out under the new asdf code. Compare the last HDU of the DMS version and the version with the new code:
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 assume the lack of Dimensions and Format info for the new ASDF extension is due to the fact that fits.info no longer knows how to handle this type of extension? |
||
else: | ||
return _AsdfHDU.from_buff(buff) | ||
|
||
def _update_asdf_extension(self, all_array_storage=None, | ||
all_array_compression=None, | ||
auto_inline=None, pad_blocks=False): | ||
all_array_compression=None, auto_inline=None, | ||
pad_blocks=False, use_image_hdu=False): | ||
if self.blocks.streamed_block is not None: | ||
raise ValueError( | ||
"Can not save streamed data to ASDF-in-FITS file.") | ||
|
@@ -235,27 +315,27 @@ def _update_asdf_extension(self, all_array_storage=None, | |
all_array_compression=all_array_compression, | ||
auto_inline=auto_inline, pad_blocks=pad_blocks, | ||
include_block_index=False) | ||
array = np.frombuffer(buff.getvalue(), np.uint8) | ||
|
||
try: | ||
asdf_extension = self._hdulist[ASDF_EXTENSION_NAME] | ||
except (KeyError, IndexError, AttributeError): | ||
self._hdulist.append(fits.ImageHDU(array, name=ASDF_EXTENSION_NAME)) | ||
else: | ||
asdf_extension.data = array | ||
if ASDF_EXTENSION_NAME in self._hdulist: | ||
del self._hdulist[ASDF_EXTENSION_NAME] | ||
self._hdulist.append(self._create_hdu(buff, use_image_hdu)) | ||
|
||
def write_to(self, filename, all_array_storage=None, | ||
all_array_compression=None, auto_inline=None, | ||
pad_blocks=False, *args, **kwargs): | ||
pad_blocks=False, use_image_hdu=False, *args, **kwargs): | ||
self._update_asdf_extension( | ||
all_array_storage=all_array_storage, | ||
all_array_compression=all_array_compression, | ||
auto_inline=auto_inline, pad_blocks=pad_blocks) | ||
auto_inline=auto_inline, pad_blocks=pad_blocks, | ||
use_image_hdu=use_image_hdu) | ||
|
||
self._hdulist.writeto(filename, *args, **kwargs) | ||
|
||
def update(self, all_array_storage=None, all_array_compression=None, | ||
auto_inline=None, pad_blocks=False): | ||
raise NotImplementedError( | ||
"In-place update is not currently implemented for ASDF-in-FITS") | ||
|
||
self._update_asdf_extension( | ||
all_array_storage=all_array_storage, | ||
all_array_compression=all_array_compression, | ||
|
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.
If I read this right, the name is still hard-coded as
ASDF
. Is that desirable? I would have thought this should allow multiple ASDF-type extensions, with arbitrary names. Or is this necessary for backwards compatibility?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.
Admittedly this is not the most generic design. However, since the
AsdfInFits
class is expected to be the only user of this HDU class, and sinceastropy.io.fits
locates HDUs in anHDUList
based onEXTNAME
, it just made sense to setEXTNAME
toASDF
here. In other words, this is a matter of convenience and an implementation detail ofAsdfInFits
, but not necessarily part of the abstract ASDF HDU convention itself.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.
Multiple ASDF extensions could be distinguished via unique EXTVER values, assuming that keyword makes sense in an ASDF extension?
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.
It might be possible, but I would need to investigate more. Currently
AsdfInFits
expects only a single ASDF extension to be present in a FITS file. Until the JWST pipeline has a use case for multiple ASDF extensions in the same file, I don't think it makes sense to support this.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.
Ah, well, I'm not necessarily attached to this specific line as the spot to change it. Are you thinking that if I am a user and want to have an ASDF-in-FITS HDU with the name "WCS_MODEL", I'd be expected to do something like
? I'm really just saying I think it would be useful to have the option to do something like
fits_embed.AsdfInFits(hdulist, tree, name='WCS_MODEL')
, as the typical user is likely to see that as the main place where they set information about the hdu.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.
Oh, I think I just realized my mis-conception. I thought it was possible to have multiple asdf trees in one file (I see @drdavella pointed this out above, but I didn't see it until now...). I see now that the "reader" mode only supports a single one. I guess that makes this use case less important. Probably I should make a separate issue about that, but this concern about the naming could wait until then...
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.
Just to be clear: the ASDF HDU is used solely for storing a serialized version of the ASDF metadata tree.
AsdfInFits
currently expects a one-to-one mapping between FITS files and ASDF trees. The actual data arrays, including any that are used for WCS models or any other purposes, are actually stored in conventional FITS HDUs. The details of this, including the assignment of anyEXTNAME
are controlled completely by the user. This array data is accessible by any FITS reader.So it doesn't really make sense to use anything other than
ASDF
as theEXTNAME
for this HDU because the only thing that will ever actually process it is ourAsdfInFits
code. Its just an implementation detail and anyone who reads the pure FITS file does not need to be concerned with it. Unless there becomes a compelling reason to have more than one ASDF metadata tree in the same FITS file, I don't think it makes sense to change anything.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.
Gotcha, that clears things up. I withdraw my request 😉