Repository navigation
Implement lazy-loading of data for CompImageHDU - #14353
Conversation
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
|
I think I'll update this PR shortly changing it to be private API, so no need for anyone to review at the moment. |
8e3c6d2 to
9c2d343
Compare
687141a to
dcb595f
Compare
8988661 to
cab925c
Compare
|
I've simplified the implementation to now use the same code regardless of whether we are accessing a single pixel, section, or the whole array, to avoid having three different |
|
@saimn - assuming the CI passes, this is basically ready from my side, so feel free to review whenever you have time. |
There was a problem hiding this comment.
Thanks @astrofrog. This is awesome, important, and non-trivial work!! 👍
Note: we'll want to modify this comment I wrote in the docs about compressed data:
astropy/docs/io/fits/usage/cloud.rst
Lines 81 to 87 in 1bac619
Should we remove this note altogether, or would it be worth to keep a warning that "externally compressed" files do not benefit from lazy loading (e.g., fits.gz)? Are there any other compression mechanisms we should consider as part of this warning?
|
Oh, already phrase as numcodecs! This is not released yet, correct? |
|
I have verified that I can get the codec successfully working with some SDO AIA data: The length of each input block is different; is there a way to pass a concatenated list of N of these byte block and get N uncompressed blocks back? I get |
|
'decompression warning: unused bytes at end of compressed buffer' |
|
@martindurant just a side note, could you keep track of/share examples that trigger segfaults? |
|
I seem to get the segfault if I try to decompress again following the decompression warning above. |
|
@martindurant - thanks, that's useful to know! Unfortunately decode is not able to take a concatenated byte string of multiple tiles, just a single tile. |
|
|
Do you happen to know if cfitsio's Rice is the same as https://sourceforge.net/p/lpcrice/code/HEAD/tree/trunk/rice/ ?(that being extracted from HDF5, and also implemented in imagecodecs) |
I have no idea, but I would probably guess it's subtly different!
correct. Could you open issues for the segfaults and any other things you find? (one tracking issue would be fine). I am happy to run them down next week if it means you can keep working on this. Is it a valid expectation that numcodecs should be able to take more bytes than the codec is expecting to decode? I don't think that would be something we have tested or something the cfitsio layer would be ok with so that's probably the root of the segfault. |
|
Sorry, not really sure what to put in a new issue except to rehash the conversation in this thread already! Please see gist: https://gist.github.com/martindurant/16bee4256595d3b6814be139ab1bd54e
|
|
FWIW, imagecodecs includes a |
|
I took a block from the one RICE-compressed file I have and gave it to Rcomp. It did not crash, but the output values are completely different. |
|
Thanks for trying. How can I access a compressed chunk? |
|
In this gist: https://gist.github.com/martindurant/16bee4256595d3b6814be139ab1bd54e |
|
Thanks. I tried the Rcomp codec with the chunks from https://github.com/astropy/astropy/blob/main/astropy/io/fits/tests/data/compressed_image.fits and it worked. Are there other variations of the rice compression used in FITS? |
|
Aha, I got the dtype endinaness wrong. :| So, can is there a way to pass N blocks to Rcomp, and have it fill in N tiles/rows of output in one go? Just passing more bytes yields only the first output block (but no exception/crash). Interestingly, I can pass an Sorry everyone for keeping this thread going! |
|
@martindurant - if you wanted to be able to pass compressed bytes for multiple tiles to a codec in one go wouldn't you need to also pass information about how long each section of the compressed bytes is? (that is, I don't know if this could be inferred from concatenated compressed bytes) If so, does the numcodecs API allow that? |
I don't think so. Isn't that the job of Zarr to take decoded chunks and arrange them in the output array? I guess you want to avoid the detrimental overhead of Zarr handling many small chunks?
That's a bug. Try |
|
To some extent the real solution is to not have FITS files with tiny chunks, and I know @Cadair has been working on figuring out a better tile shape/size for his data, so maybe we should use an example with more sensible chunks to do any kind of profiling? |
|
Yeah the AIA data made bad choices with respect to chunk sizes 😦 |
Kerchunk's use case is exactly to index over archival data, so we don't get to dream of optimal chunk sizes. There are too many chunks in the AIA data to store them all, so we need to store fewer references, each to multiple compressed blocks. I'll note that most compressors allow you to concatenate blocks
No, the codec should know when it gets to the end of each block (and it apparently does, since the astropy version gives an extra bytes warning and the imagecodecs one just decompresses the first block). All we need is to know how many bytes got used for a tile, as it is consumed.
The output array is used by zarr in zarr.core.Array._process_chunk; so although I can make it work when calling the codec directly, it won't work with zarr unless I subclass. |
Ah ok, this explains what you are trying to achieve a little more. Can you elaborate on how this would work between kerchunk / zarr and the codecs? I am really keen to see this support for RICE compressed FITS land in kerchunk, and we can put some of the Astropy funding towards working on it. What I am struggling to wrap my head around is what the todo list looks like for getting it working. Here are some things I know probably need doing, or questions I have, but I am sure you will tell me there are many others:
|
For the AIA example, let's suppose we wish to view the first two (4096x1) tiles as a (4096x2) chunk, so kerchunk stores the start of the first tile and the end of the second as one reference. Process:
Note that I am seeing the imagecodecs' Rcomp as significantly faster that astropy's RICE, and if safely stops decompression after one tile without segfault. Maybe imagecodecs is a natural place for this to live, not kerchunk or astropy?
You mean that each tile has some different parametrisation? In numcodecs, these are normally saved along with the output chunk as a manner of binary header. We can't do that, so we would need to store the parameters some other way, to be decided. |
I think there's quite a lot of things which we would need to do to get the simpler one tile == one chunk use case working first for any valid RICE file. I think working through that would help me get a better grip on the requirements for the codecs and other components.
Interesting, will have to have a look at that. We tried to not modify the C code from cfitsio to make it easy to take any upstream changes, perhaps that's the root of the problem. The segfault is something we want to fix.
I would be surprised if there is appetite for having astropy depend on imagecodecs to make FITS reading work, but I am not personally against it. |
|
@martindurant - a quick question about the following comment: There is no guarantee that the first two tiles would be contiguous in the heap - the FITS standard doesn't require offsets to be monotonically increasing in the underlying binary table representing the compressed image. Does this cause issues with this approach? |
|
Yes, it would critically kill the idea. However, AIA does seem to always be contiguous. I would be hard pressed to imagine why it ever would not be - isn't compression done from the first pixel to the last? |
|
@martindurant - in practice I think this will probably be a case of the majority of files actually being fine, but one could imagine for instance having some kind of parallel compression where chunks are written to a heap as they are completed and therefore where the chunks end up not appearing sequentially in the heap. I'm not saying such files exist in the wild, but at the very least any kerchunk implementation that groups chunks should check that they are in fact sequential. |
|
I have often fallen foul of over-specifying my code to the first example dataset :). Totally agree that kerchunk should only make multi-tile chunks after checking they are contiguous. Also hoping that future data will have more natural tile sizes to make that unnecessary. With the arrival of parquet storage for kerchunk references, I wonder whether we may be ok anyway (fsspec will combine adjacent reads within a file at access time rather that send off myriad 1500byte calls). |
|
I have collated all the issues and comments that I can find and think of into this issue: #14575. If we can continue the discussion there that would be great. |
Description
The
ImageHDUclass has asectionproperty which can be used to lazy-load data. Until recently, this didn't provide much benefit over memory mapping, but @barentsen revived this as the API to use to access remote data efficiently in #13238.The aim of the present PR is to implement a similar API for
CompImageHDU. Currently, accessingCompImageHDU.datawill load all tiles from the FITS file and decompress them all, then stack them, even if only a small part of the image array is required. By building on the new compression module implemented in #14252 we can now efficiently access sections of the data loading only the required tiles:Performance
Here is a comparison of extracting a single pixel value from a 8k x 8k compressed FITS file with 256 x 256 tile sizes (note that I can't use
%timeitbecause there is caching at various levels):Before
After (via section)
Which is about 60 times faster.
fitsio
For comparison, here are the timings for the fitsio package with the same data:
which is similar to the time for astropy to load the whole array, and:
which is about 3-4 times faster still (based on the 'total' - not 'wall' time) but not too surprising since most of the data access is done in C in fitsio.
Before (with slices):
After (with slices)
Status
This is a work in progress at the moment - I still need to: