Skip to content

[DirectZarrIngestor] Direct-region Zarr writes do not emit CF _FillValue user attribute (breaks xarray mask_and_scale) #49

Description

@armagankaratosun

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

  • I searched existing issues and this is not a duplicate.
  • I removed all credentials, tokens, and private URIs from this report.

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions