Summary
DirectZarrIngestor plugins that declare ZarrArraySpec(fill_value=<x>) produce arrays whose Zarr metadata fill_value is correct but whose user attributes lack the CF-standard _FillValue. xarray's mask_and_scale=True reads _FillValue from user attrs, so fill pixels are not masked to NaN on read and appear as raw sentinel values (65535, iinfo.max, etc.).
Steps to reproduce
# 1. Preallocate + ingest a small window
firecube zarr preallocate mtg_fci_l1c \
--product-name test --target file:///tmp/repro.zarr \
--storage-type local --storage-driver fsspec --write-mode direct \
--input-data /path/to/fci-zips \
--option product_type=FDHSI --option time_epoch=2024-10-01 --option time_slots=1
firecube ingest mtg_fci_l1c \
--input-data /path/to/fci-zips --target file:///tmp/repro.zarr \
--storage-type local --storage-driver fsspec --write-mode direct \
--option product_type=FDHSI --option time_epoch=2024-10-01 --option time_slots=1 \
--slot-start 0 --slot-end 1
# 2. Inspect the array
python -c "
import zarr
arr = zarr.open_array('/tmp/repro.zarr/data_1km/counts', mode='r')
print('metadata fill_value:', arr.fill_value) # 65535
print('_FillValue attr:', arr.attrs.get('_FillValue', 'NOT SET')) # NOT SET
"
# 3. Try to open with xarray + mask_and_scale
python -c "
import xarray as xr
ds = xr.open_dataset('/tmp/repro.zarr/data_1km', engine='zarr', mask_and_scale=True, consolidated=False)
print('counts dtype:', ds.counts.dtype) # uint16 (not promoted to float)
print('counts encoding _FillValue:', ds.counts.encoding.get('_FillValue', 'NOT SET')) # NOT SET
# Fill pixels appear as 65535, not NaN
"
Expected behavior
When ZarrArraySpec.fill_value is not None, DirectZarrIngestor should emit a corresponding _FillValue user attribute on the created array. Xarray tooling (mask_and_scale=True, rioxarray, satpy) would then mask fill pixels to NaN on read without any per-plugin workaround.
Actual behavior
`_FillValue` attribute is absent from `arr.attrs` for every array created via `DirectZarrIngestor`. The Zarr metadata `fill_value` is set correctly, but the CF-standard user attribute that xarray reads for masking is never stamped.
Consequence: xarray with `mask_and_scale=True` returns raw sentinel values instead of NaN. For int arrays with sentinel fills (e.g. int32 pixel_time = 2147483647), reading the fill sentinel as a real value can cause secondary failures downstream (e.g. cftime OverflowError when the sentinel is decoded as `seconds since 2000-01-01`).
Firecube version
0.1.4
Install source
PyPI
Environment
Linux
Firecube 0.1.4
Plugin involved (if any)
firecube-mtg-fci-l1c==-0.1.4
Data / state impact
No response
Before submitting
Summary
DirectZarrIngestor plugins that declare
ZarrArraySpec(fill_value=<x>)produce arrays whose Zarr metadatafill_valueis correct but whose user attributes lack the CF-standard_FillValue. xarray'smask_and_scale=Truereads_FillValuefrom user attrs, so fill pixels are not masked to NaN on read and appear as raw sentinel values (65535, iinfo.max, etc.).Steps to reproduce
Expected behavior
When
ZarrArraySpec.fill_value is not None,DirectZarrIngestorshould emit a corresponding_FillValueuser attribute on the created array. Xarray tooling (mask_and_scale=True, rioxarray, satpy) would then mask fill pixels to NaN on read without any per-plugin workaround.Actual behavior
Firecube version
0.1.4
Install source
PyPI
Environment
Linux
Firecube 0.1.4
Plugin involved (if any)
firecube-mtg-fci-l1c==-0.1.4
Data / state impact
No response
Before submitting