From 33b1857abaa7c82ba4f69b145f0ecd6bfaebc4f5 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 14:14:38 -0400 Subject: [PATCH 01/14] Re-whitespace abstract and fix extraneous word. --- README.rst | 5 ++++- index.rst | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8dffd60..03a9bbd 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,10 @@ File Formats and Layouts for Cell-based Coadds DMTN-294 ======== -Rubin's deep coadds will be built in on a grid of small cells, in which each cell has an approximately constant PSF. Cells will have "inner regions" that can be stitched together to form the full coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. This technical note will summarize various possibilities and their advantages and disadvantages. +Rubin's deep coadds will be built on a grid of small cells, in which each cell has an approximately constant PSF. +Cells will have "inner regions" that can be stitched together to form the full coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. +This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. +This technical note will summarize various possibilities and their advantages and disadvantages. **Links:** diff --git a/index.rst b/index.rst index 7e29948..1e13dd7 100644 --- a/index.rst +++ b/index.rst @@ -4,7 +4,10 @@ File Formats and Layouts for Cell-based Coadds .. abstract:: - Rubin's deep coadds will be built in on a grid of small cells, in which each cell has an approximately constant PSF. Cells will have "inner regions" that can be stitched together to form the full coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. This technical note will summarize various possibilities and their advantages and disadvantages. + Rubin's deep coadds will be built on a grid of small cells, in which each cell has an approximately constant PSF. + Cells will have "inner regions" that can be stitched together to form the full coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. + This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. + This technical note will summarize various possibilities and their advantages and disadvantages. Add content here ================ From 288f6dad76138a9568a1a4e32ccad5eb5d31115b Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 14:15:15 -0400 Subject: [PATCH 02/14] Add intros, outline, and metadata sections. --- index.rst | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/index.rst b/index.rst index 1e13dd7..a4ef586 100644 --- a/index.rst +++ b/index.rst @@ -9,7 +9,109 @@ File Formats and Layouts for Cell-based Coadds This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. This technical note will summarize various possibilities and their advantages and disadvantages. -Add content here +Goals and Requirements +====================== + +Rubin's cell-based coadds will need to store five or more image planes that share a single coordinate system and pixel grid: + +- a floating point main data image; +- an integer bitmask; +- a floating point variance plane, with per-pixel variance estimates that include photon noise from sources; +- a floating point "interpolation fraction" image that records fractional missing data in each pixel; +- at least one floating point Monte Carlo noise realization. + +It will need to store at least one PSF model image for each cell; these will be smaller than the other per-cell images but the grid will have the same shape. +To account for chromatic PSF effects we may also store images that correspond to the derivatives of the PSF with respect to some proxy for object SED, in at least some bands. + +In addition, we'll need to store considerable structured metadata, including a WCS, information about the grid structure, coadded aperture corrections and wavelength-dependent throughput, the visit and detector IDs of the epochs that contributed to each cell, and additional information about the pixel uncertainty (some measure of typical covariance, and an approximately constant variance that either averages or does not include photon noise from sources). + +We will assume throughout this note that we're writing FITS files: regardless of any other format Rubin might support, we have to support FITS as well, so it's just more work to do anything else. + +These files will be one per "patch", which we assume to be an approximately 4k x 4k image, divided into cells with an inner region that is approximately 150x150 and 50 pixel padding on all sides. +We will only consider layouts that save the entire coadd, including the outer cell regions. + +We need these files to be readable over both POSIX filesystems and S3 and webdav object stores, and we need to be able to read subimages efficiently over all of these storage systems (i.e. we cannot afford to read the entire file just to read a subimage). +Writing to just POSIX filesystems is adequate, as there's no problem with writing to local temporary storage and then uploading separately in our pipeline architecture. +We do not expect to need the ability to extend existing files. + +We expect to compress all image planes with at least lossless compression, and would like to have the capability to perform lossy compression on some planes as well. +If we do lossy compression, we would quite likely want to do our own quantization (our `afw` library already has code for this that we prefer over CFITSIO's). +DESC has expressed an interest in applying lossy compression to any coadd files they transfer to NERSC, if we have not done so already. + +Constraints from FITS +===================== + +Because we need to be able to read subimages efficiently, file-level compression is not an option: our only options are FITS image "tile compression" and perhaps lesser-known FITS 4.0's binary table compression. +Combined with our need to subimage reads over object stores, this already puts us beyond what third-party FITS libraries can do: + +- CFITSIO can do image tile compression, including efficient subimage reads that take advantage of the tile structure, but only on POSIX filesystems. + It can also do subset reads of FITS binary tables, again only on POSIX filesystems. + This imposes the same limitation on its Python bindings in the ``fitsio`` package. + +- Astropy can do image tile compression with efficient subimage reads on POSIX filesystems, and it can do uncompressed reads (including efficient subimage reads) against both POSIX and object stores via `fsspec`, but it delegates to vendored CFITSIO code for its tile compression code and does not support the combination of these two. + Astropy cannot do subset reads of FITS binary tables, even on POSIX filesystems. + +- I am not aware of any library that can do FITS binary table compression at all. + +As a result, I expect us to need to write some low-level code of our own to do subimage reads over object stores, though this is not specific to cell-based coadds: we need this for all of our image data products. +Whether to contribute more general code upstream (probably to Astropy) or focus only on reading the specific files we write ourselves is an important open question; the general solution would be very useful the community, but its scope is unquestionably larger. + +Finally, the WCS of any single cell (or the inner-cell stitched image) will be simple and can be exactly represented in FITS headers (unlike the WCSs of our single-epoch images). +An additional FITS WCS could be used to describe the position of a single-cell image within a larger grid. +It is highly desirable that any images we store in our FITS files have both to allow third-party FITS readers to fully interpret them. + +Image Data Layouts +================== + +This section will cover potential ways to lay out the image data - the 5+ planes that share a common grid, as well as the PSF images - across multiple FITS HDUs. + +Binary Table With Per-Cell Rows +------------------------------- + +TODO + +Per-HDU Cells +------------- + +TODO + +Data Cubes +---------- + +TODO + +Exploded Images +--------------- + +TODO + +Stitched Images +--------------- + +TODO + +Hybrid Options +-------------- + +TODO + + +Metadata Layouts ================ +Cell coadd metadata falls into three categories: + +- global information common to all cells (identifiers, WCS, grid structure); +- per-cell information with a fixed schema (including coadded aperture corrections and wavelength-dependent throughput); +- visit, detector and other IDs for the observations that contribute to each cell. + +While some global information will go into FITS headers (certainly the WCS and some identifiers), we do not want to assume that all global metadata can be neatly represented in the limited confines of a FITS header. +A single-row binary table is another option, but we will likely instead adopt the approach recently proposed for other Rubin image data products on RFC-1030: embedding a JSON document as a byte array in a FITS extension HDU. + +A binary table with per-cell rows is a natural fit for the fixed-schema per-cell information, especially if the image data layout already involves a binary table with per-cell rows. +But if we're embedding a JSON document in the FITS file anyway, it might make more sense to store this information in JSON as well; this will let us share code, documentation, and serialization for more complex objects with other Rubin image data products, and that includes sharing the machinery for managing schema changes schema documentation. + +The tables of observations that contribute to each cell is also a natural binary table, but not one with per-cell rows (it's more natural as a cell-visit-detector join table), but once again embedded JSON is an equally viable option. + + See the `Documenteer documentation `_ for tips on how to write and configure your new technote. From 19be450cc32d4d57ff93aeaae7e3dae8d6314a6a Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 15:11:46 -0400 Subject: [PATCH 03/14] Add sections for binary tables and per-cell HDUs. --- index.rst | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/index.rst b/index.rst index a4ef586..0330ff6 100644 --- a/index.rst +++ b/index.rst @@ -44,7 +44,7 @@ Constraints from FITS Because we need to be able to read subimages efficiently, file-level compression is not an option: our only options are FITS image "tile compression" and perhaps lesser-known FITS 4.0's binary table compression. Combined with our need to subimage reads over object stores, this already puts us beyond what third-party FITS libraries can do: -- CFITSIO can do image tile compression, including efficient subimage reads that take advantage of the tile structure, but only on POSIX filesystems. +- CFITSIO can do image tile compression, including efficient subimage reads that take advantage of the tile structure, but only on POSIX filesystems (or blocks of contiguous memory that correspond to the on-disk layout). It can also do subset reads of FITS binary tables, again only on POSIX filesystems. This imposes the same limitation on its Python bindings in the ``fitsio`` package. @@ -68,12 +68,47 @@ This section will cover potential ways to lay out the image data - the 5+ planes Binary Table With Per-Cell Rows ------------------------------- -TODO +Since FITS binary tables can have array columns with arbitrary shapes, the most intuitive layout for cell-based image data is probably a single binary table HDU that has each image plane as a separate 2-d array column. +A FITS WCS can be stored with each row, using "Green Bank" convention to translate header keys to table columns; this may be sufficient for third-party readers to fully interpret our files and even stitch together cells on the fly (in the case of image viewers). + +It's not clear whether any extant FITS viewers could *already* do this in an intuitive way, but by relying on an existing convention it'd be reasonable for them to add it, even if they were otherwise disinclined to add support for observatory-specific file formats. + +The Green Bank convention makes no mention of having multiple images as different columns in the same table, however, so we may want to consider putting different image planes in different binary table HDUs, each with corresponding rows, and probably some columns - like the WCS ones - duplicated. +The extra storage cost of another header and some duplicated columns is insignificant, and it may be desirable to put multiple cells from the same plane closer together on disk than different planes of the same cell, especially since we expect that subimage reads of some planes (i.e. the image data image) will be much more popular than others (e.g. noise realizations). + +PSF images fit naturally in this layout, as there's nothing wrong with having some array columns having a different shape. +There might be some confusion with a Green Bank convention WCS, though, if we go with a single table with different columns for different image planes as well as the PSF; there's no way to mark which image columns the WCS applies to. + +The main problem with binary table layouts is compression support. +The latest FITS standard does include (in section 10.3) a discussion of "tiled table compression", which seems like it'd be sufficient, as long as compressing one cell at a time is enough to get a good compression ratio (this is unclear). +Unlike image tile compression, binary table tile compression doesn't support lossy compression algorithms or dithered quantization, but it would still be possible to do our own non-dithered quantization and use ``BZERO`` and ``BSCALE`` to record the transformation from integer back to floating-point. +The bigger problem is that there does not appear to be any implementations of it: there is no mention of it in either the CFITSIO or Astropy documentation (and even if an implementation does exist in, say, the Java ecosystem, we wouldn't be in a position to use it). +While we've already discussed the fact that we'll probably need to implement some low-level FITS image tile compression code in order to do decompressed subimage reads with object stores anymore, the binary table compression situation is much more problematic: + +- tables are much more complicated than images; +- we would have to implement writes ourselves, not just reads; +- we would not have a reference implementation we could use for testing; +- if the standard has not seen real use, we stand a good chance of discovering uncovered edge cases or other defects; +- third-party FITS readers would definitely not be able to read our files, at least not without significant work. + +In fact, even without compression, the binary table layout would require writing our code just to solve the problem of subimage reads over object stores, since Astropy cannot do efficient table subset reads and CFITSIO cannot do object store reads. Per-HDU Cells ------------- -TODO +Another simple file layout is to put each image plane for each cell in a completely separate FITS image HDU. +This is entirely compatible with FITS tile compression (though we'd almost certainly compress the entire HDU as one tile) and our goals for using FITS WCS. +Stitching images from different HDUs into a coherent whole is probably a bit more likely for a third-party FITS viewer to support than images from different binary tables, but a flat list of HDUs for all cells and image planes provides a lot less organizational structure than a binary table (especially a single binary table) for third-party tools to interpret. + +Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250x250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. +The overheads would be significant for the PSF images, which we expect to be 25-40 pixels on a side (2.5-6 KB uncompressed). + +Subimage reads would be similarly non-ideal but perhaps tolerable. +Because each HDU is so small, it'd be plenty efficient to read full HDUs, but only those for the cells that overlap the region of interest. +Seeking to the right HDUs (or requesting the appropriate byte ranges, in the object store case) is easily solved by putting a table of byte offsets in the primary HDU header, though this isn't something third-party FITS readers could leverage. +That would make for a simple solution to the problem of doing subimage reads over object stores (including compression): we could use the address table to read the HDUs we are interested in in their entirety into a client-side memory location that looks like a full in-memory FITS file holding just those HDUs, and then delegate to CFITSIO's "memory file" interfaces to let it do the decompression. + +As in the binary table case, it's an open question whether we could get sufficiently good compression ratios if we are limited to compressing one cell at a time. Data Cubes ---------- From c7df87e8a84119c396599ee4d0bff9983f3b7bba Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 15:42:13 -0400 Subject: [PATCH 04/14] Add sections on data cubes and exploded images. --- index.rst | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/index.rst b/index.rst index 0330ff6..012730e 100644 --- a/index.rst +++ b/index.rst @@ -27,7 +27,7 @@ In addition, we'll need to store considerable structured metadata, including a W We will assume throughout this note that we're writing FITS files: regardless of any other format Rubin might support, we have to support FITS as well, so it's just more work to do anything else. -These files will be one per "patch", which we assume to be an approximately 4k x 4k image, divided into cells with an inner region that is approximately 150x150 and 50 pixel padding on all sides. +These files will be one per "patch", which we assume to be an approximately 4k × 4k image, divided into cells with an inner region that is approximately 150×150 and 50 pixel padding on all sides. We will only consider layouts that save the entire coadd, including the outer cell regions. We need these files to be readable over both POSIX filesystems and S3 and webdav object stores, and we need to be able to read subimages efficiently over all of these storage systems (i.e. we cannot afford to read the entire file just to read a subimage). @@ -100,7 +100,7 @@ Another simple file layout is to put each image plane for each cell in a complet This is entirely compatible with FITS tile compression (though we'd almost certainly compress the entire HDU as one tile) and our goals for using FITS WCS. Stitching images from different HDUs into a coherent whole is probably a bit more likely for a third-party FITS viewer to support than images from different binary tables, but a flat list of HDUs for all cells and image planes provides a lot less organizational structure than a binary table (especially a single binary table) for third-party tools to interpret. -Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250x250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. +Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250×250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. The overheads would be significant for the PSF images, which we expect to be 25-40 pixels on a side (2.5-6 KB uncompressed). Subimage reads would be similarly non-ideal but perhaps tolerable. @@ -113,12 +113,23 @@ As in the binary table case, it's an open question whether we could get sufficie Data Cubes ---------- -TODO +In this layout, we'd have one 3-d or 4-d image extension HDU for each plane, with each cell's image a 2-d slice of that higher-dimensional array, and the other dimensions corresponding to a 1-d or 2-d index of that cell in its grid. + +This avoids the problem with per-HDU overheads, and it makes an address table much less important, as there are many fewer HDUs. +It also allows compression tiles that comprise multiple cells. + +It does not allow us to represent the on-sky locations of cells using FITS WCS, however, and this is probably enough to rule it out. + +This approach is neutral w.r.t. the problem of compressed subimage reads against stores: any solution that worked for a regular, non-cell image would work for this one. Exploded Images --------------- -TODO +This approach is similar to the data cube layout, with one HDU for each image plane, but instead of using additional dimensions to represent the grid, we just stitch all cells into a single larger 2-d image. +This doesn't put the cells onto a consistent meaningful coordinate system, however, because we'd be stitching the outer regions together, not the inner regions, and that means all of the logical pixels in the overlap regions appear more than once (albeit with subtly different PSFs, noise, etc, due to different input epochs, in most cases). + +That makes the full image *somewhat* interpretable by humans, though far from ideal - it's a bit similar to the common approach of displaying "untrimmed" raw images with the overscan regions of amplifiers in between the data sections. +This is a slight advantage over the data cube layout, but it seems to be the only one: it suffers from the same incompatibility with FITS WCS, and is similarly neutral for the compressed subimage reads problem. Stitched Images --------------- From 815f86a2570d5eed7d2d6caf98fb20805475de26 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 16:24:25 -0400 Subject: [PATCH 05/14] Add section on stitched images and hybrid approaches. --- _static/cell-stitching.png | Bin 0 -> 36771 bytes index.rst | 31 +++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 _static/cell-stitching.png diff --git a/_static/cell-stitching.png b/_static/cell-stitching.png new file mode 100644 index 0000000000000000000000000000000000000000..f2716caa73410e5241e801f1164e390f432d28fb GIT binary patch literal 36771 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 t_~oL=coM3OCk4XOQ7Q$O`a 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 Date: Mon, 22 Jul 2024 16:48:19 -0400 Subject: [PATCH 06/14] Proofreading edits and PR comment clarifications. --- index.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/index.rst b/index.rst index 36d12cc..b5a9328 100644 --- a/index.rst +++ b/index.rst @@ -27,7 +27,7 @@ In addition, we'll need to store considerable structured metadata, including a W We will assume throughout this note that we're writing FITS files: regardless of any other format Rubin might support, we have to support FITS as well, so it's just more work to do anything else. -These files will be one per "patch", which we assume to be an approximately 4k × 4k image, divided into cells with an inner region that is approximately 150×150 and 50 pixel padding on all sides. +These files will be one per "patch", which we assume to be an approximately 4k × 4k image, divided into cells with an inner region that is approximately 150×150 with 50 pixel padding on all sides. We will only consider layouts that save the entire coadd, including the outer cell regions. We need these files to be readable over both POSIX filesystems and S3 and webdav object stores, and we need to be able to read subimages efficiently over all of these storage systems (i.e. we cannot afford to read the entire file just to read a subimage). @@ -56,8 +56,8 @@ Combined with our need to subimage reads over object stores, this already puts u As a result, I expect us to need to write some low-level code of our own to do subimage reads over object stores, though this is not specific to cell-based coadds: we need this for all of our image data products. Whether to contribute more general code upstream (probably to Astropy) or focus only on reading the specific files we write ourselves is an important open question; the general solution would be very useful the community, but its scope is unquestionably larger. -Finally, the WCS of any single cell (or the inner-cell stitched image) will be simple and can be exactly represented in FITS headers (unlike the WCSs of our single-epoch images). -An additional FITS WCS could be used to describe the position of a single-cell image within a larger grid. +Finally, the WCS of any single cell (or the inner-cell stitched image; they differ only in an integer offset) will be simple and can be exactly represented in FITS headers (unlike the WCSs of our single-epoch images). +An additional FITS WCS could be used to describe the position of a single-cell image within a larger grid (this is just an integer offset). It is highly desirable that any images we store in our FITS files have both to allow third-party FITS readers to fully interpret them. Image Data Layouts @@ -100,7 +100,7 @@ Another simple file layout is to put each image plane for each cell in a complet This is entirely compatible with FITS tile compression (though we'd almost certainly compress the entire HDU as one tile) and our goals for using FITS WCS. Stitching images from different HDUs into a coherent whole is probably a bit more likely for a third-party FITS viewer to support than images from different binary tables, but a flat list of HDUs for all cells and image planes provides a lot less organizational structure than a binary table (especially a single binary table) for third-party tools to interpret. -Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250×250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. +Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880 bytes) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250×250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. The overheads would be significant for the PSF images, which we expect to be 25-40 pixels on a side (2.5-6 KB uncompressed). Subimage reads would be similarly non-ideal but perhaps tolerable. @@ -120,7 +120,7 @@ It also allows compression tiles that comprise multiple cells. It does not allow us to represent the on-sky locations of cells using FITS WCS, however, and this is probably enough to rule it out. -This approach is neutral w.r.t. the problem of compressed subimage reads against stores: any solution that worked for a regular, non-cell image would work for this one. +This approach is neutral w.r.t. the problem of compressed subimage reads against object stores: any solution that worked for a regular, non-cell image would work for this one. Exploded Images --------------- @@ -139,7 +139,7 @@ This suggests that we might want to store that stitched inner-cell image for eac For the inner-cell image, this is ideal: FITS WCS can be used exactly the way it was intended, and third-party FITS viewers will be completely usable without any extra effort. -For the overlap regions, we'd end up with a repeat of our original problem, but with lower stakes: for each original cell, we'd have 4 overlap-region images (top, bottom, left, right) that need to be packed into a binary table, data cube, or stitched image of their own (which this stitching being analogous to the exploded coadd case, since there'd be no meaningful overall coordinate system). +For the overlap regions, we'd end up with a repeat of our original problem, but with lower stakes: for each original cell, we'd have 4 overlap-region images (top, bottom, left, right) that need to be packed into a binary table, data cube, or stitched image of their own (with this stitching being analogous to the exploded coadd case, since there'd be no meaningful overall coordinate system). .. figure:: /_static/cell-stitching.png :name: cell-stitching @@ -150,16 +150,16 @@ For the overlap regions, we'd end up with a repeat of our original problem, but Right: a stitched inner-cell image and packings of the overlap regions. Each color represents a different FITS HDU; the green inner region would be a single 2-d image, while the blue and purple overlap regions could 2-d images or 3- or 4-d data cubes. -Assuming we don't care about FITS WCS support for the overlap regions, the main complication here is complexity in two places: +Assuming we don't care about FITS WCS support for the overlap regions, the main problem with this approach is complexity in two places: - When writing, we'd need to quantize the outer cell image first, and then slice the image into its inner-cell and overlap-region sections, and only then compress the quantized values. - This isn't something third-party FITS compress-while-writing libraries can do, but it if we're doing our own quantization anyway, it'd be straightforward to include. - It would not be compatible with storing the overlap regions in a binary table, but we would have the freedom to compress more than one cell at a time (and even compress more cells at a time in the overlap regions than in the inner regions). + This isn't something third-party FITS compress-while-writing libraries can do, but if we're doing our own quantization anyway, it'd be straightforward to include. + Compression would not be compatible with storing the overlap regions in a binary table, but we would have the freedom to compress more than one cell at a time (and even compress more cells at a time in the overlap regions than in the inner regions). - Access to the outer cells on read would require more complex code and a few more seeks: first read the inner region, then read the four overlap regions (though these can almost certainly be read in pairs), and then put them all together. This is not something we'd expect third-party general-purpose readers to ever do, but it's not a terribly complicated specification or a huge burden for, say, someone who wanted to write a Rubin-specific reader in a language other than Python. -This option is also neutral to the problem of compressed subimage reads against object store. +This option is also neutral to the problem of compressed subimage reads against object stores. Hybrid Options -------------- @@ -167,7 +167,7 @@ Hybrid Options We can in theory use a different approach for each image plane, though for the most part the arguments are the same for all image planes. The PSF images are the big exception: they are already intrinsically in their own coordinate system that doesn't have a meaningful WCS, and they are unlikely to ever be lossy compressed (I actually don't have any intuition for whether they'd have good lossless compression ratios). -This makes data cube or exploded storage of PSFs quite attractive (binary table too, at least if if we determine that we don't need to compress them at all), even if other image planes are stored in other ways. +This makes data cube or exploded storage of PSFs quite attractive (binary tables too, at least if if we determine that we don't need to compress them at all), even if other image planes are stored in other ways. Metadata Layouts ================ From 54821c41adc576307c11aaf41dbbecbeeb66469f Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 22 Jul 2024 16:54:50 -0400 Subject: [PATCH 07/14] Delete template text. --- index.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/index.rst b/index.rst index b5a9328..3e69c74 100644 --- a/index.rst +++ b/index.rst @@ -185,6 +185,3 @@ A binary table with per-cell rows is a natural fit for the fixed-schema per-cell But if we're embedding a JSON document in the FITS file anyway, it might make more sense to store this information in JSON as well; this will let us share code, documentation, and serialization for more complex objects with other Rubin image data products, and that includes sharing the machinery for managing schema changes schema documentation. The tables of observations that contribute to each cell is also a natural binary table, but not one with per-cell rows (it's more natural as a cell-visit-detector join table), but once again embedded JSON is an equally viable option. - - -See the `Documenteer documentation `_ for tips on how to write and configure your new technote. From a38b189a2c25f22240aa1e453be7dfa3586e61a5 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Tue, 23 Jul 2024 10:47:51 -0400 Subject: [PATCH 08/14] PR review edits from Arun Co-authored-by: Arun Kannawadi --- index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.rst b/index.rst index 3e69c74..5a0ae87 100644 --- a/index.rst +++ b/index.rst @@ -5,7 +5,7 @@ File Formats and Layouts for Cell-based Coadds .. abstract:: Rubin's deep coadds will be built on a grid of small cells, in which each cell has an approximately constant PSF. - Cells will have "inner regions" that can be stitched together to form the full coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. + Cells will have "inner regions" that can be stitched together to form the full (patch-sized) coadd, but they will also have outer regions that overlap (neighboring cells will have their own versions of some of the same pixels), in order to allow convolutions and other operations that require padding to be performed rigorously cell by cell. This creates a problem for how to store a coadd in an on-disk FITS file: we want a layout that can be easily interpreted by third-party readers, but we also need to support compression and efficient subimage reads of at least the inner cell region. This technical note will summarize various possibilities and their advantages and disadvantages. @@ -182,6 +182,6 @@ While some global information will go into FITS headers (certainly the WCS and s A single-row binary table is another option, but we will likely instead adopt the approach recently proposed for other Rubin image data products on RFC-1030: embedding a JSON document as a byte array in a FITS extension HDU. A binary table with per-cell rows is a natural fit for the fixed-schema per-cell information, especially if the image data layout already involves a binary table with per-cell rows. -But if we're embedding a JSON document in the FITS file anyway, it might make more sense to store this information in JSON as well; this will let us share code, documentation, and serialization for more complex objects with other Rubin image data products, and that includes sharing the machinery for managing schema changes schema documentation. +But if we're embedding a JSON document in the FITS file anyway, it might make more sense to store this information in JSON as well; this will let us share code, documentation, and serialization for more complex objects with other Rubin image data products, and that includes sharing the machinery for managing schema changes and schema documentation. The tables of observations that contribute to each cell is also a natural binary table, but not one with per-cell rows (it's more natural as a cell-visit-detector join table), but once again embedded JSON is an equally viable option. From aebd0d7afa22fd5149bcaa1178ad617c5ba8432d Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Wed, 11 Sep 2024 15:25:39 -0400 Subject: [PATCH 09/14] Fix discussion of compression. - tiled table compression is supported by CFITSIO and other readers; - we do need lossy compression and subtractive dithering for at least some image planes. The overall result is that the big picture is not changed much: binary tables are still not really viable, because they can't do the kind of compression we need. --- index.rst | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/index.rst b/index.rst index 5a0ae87..7222b37 100644 --- a/index.rst +++ b/index.rst @@ -20,7 +20,7 @@ Rubin's cell-based coadds will need to store five or more image planes that shar - a floating point "interpolation fraction" image that records fractional missing data in each pixel; - at least one floating point Monte Carlo noise realization. -It will need to store at least one PSF model image for each cell; these will be smaller than the other per-cell images but the grid will have the same shape. +We will also need to store at least one PSF model image for each cell; these will be smaller than the other per-cell images but the grid will have the same shape. To account for chromatic PSF effects we may also store images that correspond to the derivatives of the PSF with respect to some proxy for object SED, in at least some bands. In addition, we'll need to store considerable structured metadata, including a WCS, information about the grid structure, coadded aperture corrections and wavelength-dependent throughput, the visit and detector IDs of the epochs that contributed to each cell, and additional information about the pixel uncertainty (some measure of typical covariance, and an approximately constant variance that either averages or does not include photon noise from sources). @@ -34,8 +34,9 @@ We need these files to be readable over both POSIX filesystems and S3 and webdav Writing to just POSIX filesystems is adequate, as there's no problem with writing to local temporary storage and then uploading separately in our pipeline architecture. We do not expect to need the ability to extend existing files. -We expect to compress all image planes with at least lossless compression, and would like to have the capability to perform lossy compression on some planes as well. -If we do lossy compression, we would quite likely want to do our own quantization (our `afw` library already has code for this that we prefer over CFITSIO's). +We expect to compress all image planes with at least lossless compression, and need like to have the capability to perform lossy compression on some planes as well. +For lossy compression, we would quite likely want to do our own quantization (our `afw` library already has code for this that we prefer over CFITSIO's), +and would want to use subtractive dithering (adding and subtracting deterministic pseudorandom numbers to avoid quantization biases). DESC has expressed an interest in applying lossy compression to any coadd files they transfer to NERSC, if we have not done so already. Constraints from FITS @@ -44,19 +45,20 @@ Constraints from FITS Because we need to be able to read subimages efficiently, file-level compression is not an option: our only options are FITS image "tile compression" and perhaps lesser-known FITS 4.0's binary table compression. Combined with our need to subimage reads over object stores, this already puts us beyond what third-party FITS libraries can do: -- CFITSIO can do image tile compression, including efficient subimage reads that take advantage of the tile structure, but only on POSIX filesystems (or blocks of contiguous memory that correspond to the on-disk layout). +- CFITSIO can do image tile compression and binary table compression, including efficient subimage reads that take advantage of the tile structure, but only on POSIX filesystems (or blocks of contiguous memory that correspond to the on-disk layout). It can also do subset reads of FITS binary tables, again only on POSIX filesystems. + CFITSIO can only permits customization of the quantization used in image tile compression via undocumented internal interfaces (which is what we use in ``afw``). This imposes the same limitation on its Python bindings in the ``fitsio`` package. - Astropy can do image tile compression with efficient subimage reads on POSIX filesystems, and it can do uncompressed reads (including efficient subimage reads) against both POSIX and object stores via `fsspec`, but it delegates to vendored CFITSIO code for its tile compression code and does not support the combination of these two. Astropy cannot do subset reads of FITS binary tables, even on POSIX filesystems. - -- I am not aware of any library that can do FITS binary table compression at all. + Astropy also does not provide a way to customize the quantization during lossy compression. As a result, I expect us to need to write some low-level code of our own to do subimage reads over object stores, though this is not specific to cell-based coadds: we need this for all of our image data products. +We also may need new low-level code to write tile-compressed images with custom quantization, since the code we have in ``afw`` for this is not general enough to work with all of the file layouts described here. Whether to contribute more general code upstream (probably to Astropy) or focus only on reading the specific files we write ourselves is an important open question; the general solution would be very useful the community, but its scope is unquestionably larger. -Finally, the WCS of any single cell (or the inner-cell stitched image; they differ only in an integer offset) will be simple and can be exactly represented in FITS headers (unlike the WCSs of our single-epoch images). +Finally, the WCS of any single cell (or the inner-cell stitched image; they differ only by an integer offset) will be simple and can be exactly represented in FITS headers (unlike the WCSs of our single-epoch images). An additional FITS WCS could be used to describe the position of a single-cell image within a larger grid (this is just an integer offset). It is highly desirable that any images we store in our FITS files have both to allow third-party FITS readers to fully interpret them. @@ -80,18 +82,12 @@ PSF images fit naturally in this layout, as there's nothing wrong with having so There might be some confusion with a Green Bank convention WCS, though, if we go with a single table with different columns for different image planes as well as the PSF; there's no way to mark which image columns the WCS applies to. The main problem with binary table layouts is compression support. -The latest FITS standard does include (in section 10.3) a discussion of "tiled table compression", which seems like it'd be sufficient, as long as compressing one cell at a time is enough to get a good compression ratio (this is unclear). -Unlike image tile compression, binary table tile compression doesn't support lossy compression algorithms or dithered quantization, but it would still be possible to do our own non-dithered quantization and use ``BZERO`` and ``BSCALE`` to record the transformation from integer back to floating-point. -The bigger problem is that there does not appear to be any implementations of it: there is no mention of it in either the CFITSIO or Astropy documentation (and even if an implementation does exist in, say, the Java ecosystem, we wouldn't be in a position to use it). -While we've already discussed the fact that we'll probably need to implement some low-level FITS image tile compression code in order to do decompressed subimage reads with object stores anymore, the binary table compression situation is much more problematic: - -- tables are much more complicated than images; -- we would have to implement writes ourselves, not just reads; -- we would not have a reference implementation we could use for testing; -- if the standard has not seen real use, we stand a good chance of discovering uncovered edge cases or other defects; -- third-party FITS readers would definitely not be able to read our files, at least not without significant work. +The latest FITS standard does include (in section 10.3) a discussion of "tiled table compression", which is limited to lossless compression algorithms, though the standard endorses the idea of extending this in the future. +It might still be possible to do our own non-dithered quantization and use ``BZERO`` and ``BSCALE`` to record the transformation from integer back to floating-point, and external FITS readers that understand both the Green Bank convention and tiled table compression could reasonably be expected to interpret this as intended. +But this does not provide a way to do subtractive dithering (which we consider necessary for lossy compression) or a way to quantize more than one floating-point image-like column in a single table, unless they had the same quantization parameters (and our images and variances, at least, would not). -In fact, even without compression, the binary table layout would require writing our code just to solve the problem of subimage reads over object stores, since Astropy cannot do efficient table subset reads and CFITSIO cannot do object store reads. +Adopting a binary table layout would thus effectively mean any lossy-compressed columns we write would be unavailable to third-party FITS readers until and unless we could standardize some approach to quantization and get it implemented in other libraries. +This seems doable given the positive language in the standard about that possibility, but not on a short timescale. Per-HDU Cells ------------- @@ -108,8 +104,6 @@ Because each HDU is so small, it'd be plenty efficient to read full HDUs, but on Seeking to the right HDUs (or requesting the appropriate byte ranges, in the object store case) is easily solved by putting a table of byte offsets in the primary HDU header, though this isn't something third-party FITS readers could leverage. That would make for a simple solution to the problem of doing subimage reads over object stores (including compression): we could use the address table to read the HDUs we are interested in in their entirety into a client-side memory location that looks like a full in-memory FITS file holding just those HDUs, and then delegate to CFITSIO's "memory file" interfaces to let it do the decompression. -As in the binary table case, it's an open question whether we could get sufficiently good compression ratios if we are limited to compressing one cell at a time. - Data Cubes ---------- @@ -167,7 +161,7 @@ Hybrid Options We can in theory use a different approach for each image plane, though for the most part the arguments are the same for all image planes. The PSF images are the big exception: they are already intrinsically in their own coordinate system that doesn't have a meaningful WCS, and they are unlikely to ever be lossy compressed (I actually don't have any intuition for whether they'd have good lossless compression ratios). -This makes data cube or exploded storage of PSFs quite attractive (binary tables too, at least if if we determine that we don't need to compress them at all), even if other image planes are stored in other ways. +This makes binary table, exploded image, or data cube storage of PSFs quite attractive, even if other image planes are stored in other ways. Metadata Layouts ================ From 3d666b2c5c362e151f21cc6d65f45efeffb804c9 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 12 Sep 2024 11:56:50 -0400 Subject: [PATCH 10/14] Include GPDF's lossy-compressed visualization-only image idea. --- index.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.rst b/index.rst index 7222b37..6e2649f 100644 --- a/index.rst +++ b/index.rst @@ -163,6 +163,13 @@ The PSF images are the big exception: they are already intrinsically in their ow This makes binary table, exploded image, or data cube storage of PSFs quite attractive, even if other image planes are stored in other ways. +Another attractive hybrid concept is storing the inner cell regions of one or more planes two times, in different ways: + +- as a stitched image with aggressively lossy compression, for visualization only; +- along with the outer regions with less aggressively lossy or lossless compression in some format that is natural for third-party readers but not visualization (e.g. binary tables or exploded images). + +With sufficiently aggressive quantization, the visualization copy's storage cost may be negligible relative to the full copy. + Metadata Layouts ================ From 2ce0df7d22399c26ce16046b5f38be5c86653b2d Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 12 Sep 2024 11:58:10 -0400 Subject: [PATCH 11/14] Add section on dual-interpretation HDUs. --- index.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/index.rst b/index.rst index 6e2649f..89d61e0 100644 --- a/index.rst +++ b/index.rst @@ -155,6 +155,19 @@ Assuming we don't care about FITS WCS support for the overlap regions, the main This option is also neutral to the problem of compressed subimage reads against object stores. +Dual-interpretation HDUs +------------------------ + +Tile-compressed FITS image HDUs are actually implemented as binary table HDUs with special columns and headers, and FITS libraries typically allow them to be read directly as binary tables as well as decompressed into images, even if the latter is generally the default. +The FITS standard explicitly permits these binary tables to have additional columns (to be ignored when reading the HDU as a compressed image), which means we can actually combine the exploded image (or data cube) and binary table representations in a single HDU, with a couple of caveats: + +- we have to compress each cell independently (this is the most way to compress anyway), in order for the image tile-compression binary table form to have one row for each cell; +- each image plane can only have one image HDU. + +This lets us associate WCS information and other metadata with each cell by adding table columns that correspond to standard FITS header values, at the cost of duplicating it for every plane (very little of the per-cell information we'd put in these columns would change from plane to plane). +It's unlikely third-party FITS readers would fully interpret these columns without additional effort - they would most likely be ignored when treating the HDU as an image to be decompressed, while the ``COMPRESSED_DATA`` column would not be recognized as the image-like column expected by the Green Bank convention in the binary table form. +Nevertheless, mixing two well-established interpretations of binary tables to together fully represent our data model is arguably better than inventing a new one. + Hybrid Options -------------- From c73cf717550c8469fd67c47f9b68ea7a1483002e Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 12 Sep 2024 11:59:57 -0400 Subject: [PATCH 12/14] Add note on third-party image support from GPDF. --- index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.rst b/index.rst index 89d61e0..9b9ac12 100644 --- a/index.rst +++ b/index.rst @@ -25,6 +25,8 @@ To account for chromatic PSF effects we may also store images that correspond to In addition, we'll need to store considerable structured metadata, including a WCS, information about the grid structure, coadded aperture corrections and wavelength-dependent throughput, the visit and detector IDs of the epochs that contributed to each cell, and additional information about the pixel uncertainty (some measure of typical covariance, and an approximately constant variance that either averages or does not include photon noise from sources). +It is highly desirable that non-Rubin-specific third-party image viewers be able to understand the WCS, display coordinates for pixels indicated by users, and accurately overlay marks on the image at the locations of detected objects or other targets. + We will assume throughout this note that we're writing FITS files: regardless of any other format Rubin might support, we have to support FITS as well, so it's just more work to do anything else. These files will be one per "patch", which we assume to be an approximately 4k × 4k image, divided into cells with an inner region that is approximately 150×150 with 50 pixel padding on all sides. From 4e773b5a5197a401dafa5b956ced40ab069dbfc4 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 12 Sep 2024 12:09:18 -0400 Subject: [PATCH 13/14] Clarify situation on third-party viewers and per-HDU cells. --- index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.rst b/index.rst index 9b9ac12..17c77fd 100644 --- a/index.rst +++ b/index.rst @@ -96,7 +96,8 @@ Per-HDU Cells Another simple file layout is to put each image plane for each cell in a completely separate FITS image HDU. This is entirely compatible with FITS tile compression (though we'd almost certainly compress the entire HDU as one tile) and our goals for using FITS WCS. -Stitching images from different HDUs into a coherent whole is probably a bit more likely for a third-party FITS viewer to support than images from different binary tables, but a flat list of HDUs for all cells and image planes provides a lot less organizational structure than a binary table (especially a single binary table) for third-party tools to interpret. +Stitching images from different HDUs into a coherent whole is probably a bit more likely for a third-party FITS viewer to support than images from different binary tables; it definitely is supported by some viewers in the absence of overlaps, if certain header cards are included. +But a flat list of HDUs for all cells and image planes provides a lot less organizational structure than a binary table (especially a single binary table) for third-party tools to interpret. Each HDU comes with an extra 3-9 KB of overhead (1-2 header blocks, and padding out the full HDU size to a multiple of 2880 bytes) that cannot be compressed, which is not ideal, but probably not intolerable unless we get unexpectedly good compression ratios or shrink the cell size: an uncompressed 250×250 single-precision floating point image is 250KB, so those overheads should be at most 4% or so. The overheads would be significant for the PSF images, which we expect to be 25-40 pixels on a side (2.5-6 KB uncompressed). From 96e31a5734d08b147927ce15c7069102c7055b5f Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 12 Sep 2024 12:13:10 -0400 Subject: [PATCH 14/14] Clarify when overlap regions are and are not needed. --- index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.rst b/index.rst index 17c77fd..980e2c4 100644 --- a/index.rst +++ b/index.rst @@ -131,8 +131,9 @@ This is a slight advantage over the data cube layout, but it seems to be the onl Stitched Images --------------- -We expect most accesses to our coadd files to be uninterested in the redundant overlap pixel values - instead, most science users will be interested in stitching together the inner cells to form a mostly-seamless patch-level image. +We expect many accesses to our coadd files to be uninterested in the redundant overlap pixel values - instead, many science users will be interested in stitching together the inner cells to form a mostly-seamless patch-level image. This suggests that we might want to store that stitched inner-cell image for each plane directly as a single HDU, and shunt the overlap pixel values to a different HDU. +Aside from being actively problematic for visualization, the overlap regions are not needed for science analyses that either ignore the PSF or use it via forward modeling (they are important for methods that involve convolution or deconvolutions of the data). For the inner-cell image, this is ideal: FITS WCS can be used exactly the way it was intended, and third-party FITS viewers will be completely usable without any extra effort.