grib2io.codecs

GRIB2 Codec for Zarr / numcodecs

Provides Grib2Codec, a numcodecs.abc.Codec implementation that decodes raw GRIB2 section 7 bytes into NumPy arrays. This codec is registered with the numcodecs registry so that Zarr can transparently decode GRIB2 data chunks when reading through a Kerchunk reference manifest or an Icechunk virtual store.

The codec is decode-only; encoding (writing GRIB2 data) is handled by grib2io.open().

  1"""
  2GRIB2 Codec for Zarr / numcodecs
  3=================================
  4
  5Provides :class:`Grib2Codec`, a ``numcodecs.abc.Codec`` implementation that
  6decodes raw GRIB2 section 7 bytes into NumPy arrays.  This codec is registered
  7with the numcodecs registry so that Zarr can transparently decode GRIB2 data
  8chunks when reading through a Kerchunk reference manifest or an Icechunk
  9virtual store.
 10
 11The codec is **decode-only**; encoding (writing GRIB2 data) is handled by
 12:func:`grib2io.open`.
 13"""
 14
 15from typing import List, Optional
 16
 17import numpy as np
 18
 19from . import g2clib
 20
 21
 22# ---------------------------------------------------------------------------
 23# Lazy import guard
 24# ---------------------------------------------------------------------------
 25
 26
 27def _ensure_numcodecs():
 28    """Raise ``ImportError`` if *numcodecs* is not available."""
 29    try:
 30        import numcodecs  # noqa: F401
 31    except ImportError:
 32        raise ImportError("numcodecs is required for the GRIB2 codec. Install with: pip install grib2io[kerchunk]")
 33
 34
 35# ---------------------------------------------------------------------------
 36# Codec implementation
 37# ---------------------------------------------------------------------------
 38
 39try:
 40    from numcodecs.abc import Codec
 41    from numcodecs.registry import register_codec
 42
 43    _HAS_NUMCODECS = True
 44except ImportError:
 45    _HAS_NUMCODECS = False
 46    Codec = object  # type: ignore[assignment,misc]
 47
 48    def register_codec(cls):  # type: ignore[misc]
 49        pass
 50
 51
 52class Grib2Codec(Codec):
 53    """Zarr codec for decoding raw GRIB2 section 7 bytes.
 54
 55    All constructor parameters are JSON-serializable integers or lists
 56    extracted from the GRIB2 message's section metadata at
 57    reference-generation time.  Together they carry everything needed to
 58    call ``g2clib.unpack7()`` — the same low-level routine used by
 59    ``grib2io._data()``.
 60    """
 61
 62    codec_id = "grib2io"
 63
 64    def __init__(
 65        self,
 66        drtn: int,
 67        drt: List[int],
 68        gdtn: int,
 69        gdt: List[int],
 70        gds: List[int],
 71        nx: int,
 72        ny: int,
 73        bitmap_flag: int,
 74        bitmap_offset: Optional[int] = None,
 75        bitmap_length: Optional[int] = None,
 76        scan_mode_flags: Optional[List[int]] = None,
 77        type_of_values: int = 0,
 78        number_of_data_points: int = 0,
 79        number_of_packed_values: int = 0,
 80    ):
 81        if not _HAS_NUMCODECS:
 82            raise ImportError("numcodecs is required for Grib2Codec. Install with: pip install grib2io[kerchunk]")
 83        self.drtn = drtn
 84        self.drt = list(drt)
 85        self.gdtn = gdtn
 86        self.gdt = list(gdt)
 87        self.gds = list(gds)
 88        self.nx = nx
 89        self.ny = ny
 90        self.bitmap_flag = bitmap_flag
 91        self.bitmap_offset = bitmap_offset
 92        self.bitmap_length = bitmap_length
 93        self.scan_mode_flags = list(scan_mode_flags) if scan_mode_flags is not None else None
 94        self.type_of_values = type_of_values
 95        self.number_of_data_points = number_of_data_points
 96        self.number_of_packed_values = number_of_packed_values
 97
 98    # ---- encode (not supported) ------------------------------------------
 99
100    def encode(self, buf):
101        """Not supported — GRIB2 encoding is handled by ``grib2io.open()``."""
102        raise NotImplementedError("Grib2Codec is decode-only; GRIB2 encoding is handled by grib2io.open()")
103
104    # ---- decode ----------------------------------------------------------
105
106    def decode(self, buf, out=None):
107        """Decode raw GRIB2 section 7 bytes into a NumPy array."""
108        if not isinstance(buf, bytes):
109            buf = bytes(buf)
110
111        fld = _decode_grib2_bytes(
112            buf,
113            drtn=self.drtn,
114            drt=list(self.drt) if not isinstance(self.drt, list) else self.drt,
115            gdtn=self.gdtn,
116            gdt=list(self.gdt) if not isinstance(self.gdt, list) else self.gdt,
117            nx=self.nx,
118            ny=self.ny,
119            bitmap_flag=self.bitmap_flag,
120            bitmap_length=self.bitmap_length,
121            scan_mode_flags=list(self.scan_mode_flags) if self.scan_mode_flags is not None else None,
122            type_of_values=self.type_of_values,
123            number_of_data_points=self.number_of_data_points,
124            number_of_packed_values=self.number_of_packed_values,
125        )
126
127        if out is not None:
128            np.copyto(out, fld)
129            return out
130
131        return fld
132
133    # ---- config serialization --------------------------------------------
134
135    def get_config(self):
136        """Return a JSON-serializable dict of all codec parameters."""
137        return {
138            "id": self.codec_id,
139            "drtn": self.drtn,
140            "drt": self.drt,
141            "gdtn": self.gdtn,
142            "gdt": self.gdt,
143            "gds": self.gds,
144            "nx": self.nx,
145            "ny": self.ny,
146            "bitmap_flag": self.bitmap_flag,
147            "bitmap_offset": self.bitmap_offset,
148            "bitmap_length": self.bitmap_length,
149            "scan_mode_flags": self.scan_mode_flags,
150            "type_of_values": self.type_of_values,
151            "number_of_data_points": self.number_of_data_points,
152            "number_of_packed_values": self.number_of_packed_values,
153        }
154
155    @classmethod
156    def from_config(cls, config):
157        """Reconstruct the codec from a configuration dict.
158
159        Parameters
160        ----------
161        config : dict
162            Dictionary as returned by :meth:`get_config`.
163
164        Returns
165        -------
166        Grib2Codec
167        """
168        # Remove the 'id' key which is not a constructor parameter
169        cfg = {k: v for k, v in config.items() if k != "id"}
170        return cls(**cfg)
171
172
173register_codec(Grib2Codec)
174
175
176# ---------------------------------------------------------------------------
177# Zarr v3 ArrayBytesCodec implementation
178# ---------------------------------------------------------------------------
179
180try:
181    from dataclasses import dataclass
182    from zarr.abc.codec import ArrayBytesCodec
183    from zarr.core.array_spec import ArraySpec
184    from zarr.core.buffer import Buffer, NDBuffer
185    import zarr.registry as _zarr_registry
186
187    _HAS_ZARR_V3 = True
188except ImportError:
189    _HAS_ZARR_V3 = False
190
191
192if _HAS_ZARR_V3:
193
194    @dataclass(frozen=True)
195    class Grib2SerializerCodec(ArrayBytesCodec):
196        """Zarr v3 ``ArrayBytesCodec`` (serializer) for GRIB2 virtual chunks.
197
198        Receives the raw GRIB2 section 7 bytes fetched from a virtual chunk
199        reference and decodes them into a NumPy array using
200        ``g2clib.unpack7()``.  This is the zarr v3 counterpart of
201        :class:`Grib2Codec` (which targets numcodecs / zarr v2).
202
203        All constructor parameters mirror those of :class:`Grib2Codec` and
204        are stored in ``zarr.json`` so they travel with the array metadata.
205        """
206
207        is_fixed_size: bool = False  # GRIB2 compressed size varies
208
209        # ---- GRIB2 decode parameters (mirror Grib2Codec) ----
210        drtn: int = 0
211        drt: tuple = ()
212        gdtn: int = 0
213        gdt: tuple = ()
214        gds: tuple = ()
215        nx: int = 0
216        ny: int = 0
217        bitmap_flag: int = 255
218        bitmap_offset: Optional[int] = None
219        bitmap_length: Optional[int] = None
220        scan_mode_flags: Optional[tuple] = None
221        type_of_values: int = 0
222        number_of_data_points: int = 0
223        number_of_packed_values: int = 0
224
225        # dataclass frozen=True uses __init__ auto-generated; we need a
226        # custom __init__ to accept list args and coerce to tuples.
227        def __new__(cls, **kwargs):
228            # coerce list args to tuples so the frozen dataclass is hashable
229            for f in ("drt", "gdt", "gds", "scan_mode_flags"):
230                if f in kwargs and isinstance(kwargs[f], list):
231                    kwargs[f] = tuple(kwargs[f])
232            obj = object.__new__(cls)
233            return obj
234
235        def __init__(
236            self,
237            *,
238            drtn: int = 0,
239            drt=(),
240            gdtn: int = 0,
241            gdt=(),
242            gds=(),
243            nx: int = 0,
244            ny: int = 0,
245            bitmap_flag: int = 255,
246            bitmap_offset: Optional[int] = None,
247            bitmap_length: Optional[int] = None,
248            scan_mode_flags=None,
249            type_of_values: int = 0,
250            number_of_data_points: int = 0,
251            number_of_packed_values: int = 0,
252        ):
253            object.__setattr__(self, "drtn", int(drtn))
254            object.__setattr__(self, "drt", tuple(drt) if drt is not None else ())
255            object.__setattr__(self, "gdtn", int(gdtn))
256            object.__setattr__(self, "gdt", tuple(gdt) if gdt is not None else ())
257            object.__setattr__(self, "gds", tuple(gds) if gds is not None else ())
258            object.__setattr__(self, "nx", int(nx))
259            object.__setattr__(self, "ny", int(ny))
260            object.__setattr__(self, "bitmap_flag", int(bitmap_flag))
261            object.__setattr__(self, "bitmap_offset", bitmap_offset)
262            object.__setattr__(self, "bitmap_length", bitmap_length)
263            object.__setattr__(self, "scan_mode_flags", tuple(scan_mode_flags) if scan_mode_flags is not None else None)
264            object.__setattr__(self, "type_of_values", int(type_of_values))
265            object.__setattr__(self, "number_of_data_points", int(number_of_data_points))
266            object.__setattr__(self, "number_of_packed_values", int(number_of_packed_values))
267
268        # ---- zarr v3 codec interface --------------------------------
269
270        @classmethod
271        def from_dict(cls, data: dict) -> "Grib2SerializerCodec":
272            cfg = data.get("configuration", {})
273            return cls(**cfg)
274
275        def to_dict(self) -> dict:
276            return {
277                "name": "grib2io",
278                "configuration": {
279                    "drtn": self.drtn,
280                    "drt": list(self.drt),
281                    "gdtn": self.gdtn,
282                    "gdt": list(self.gdt),
283                    "gds": list(self.gds),
284                    "nx": self.nx,
285                    "ny": self.ny,
286                    "bitmap_flag": self.bitmap_flag,
287                    "bitmap_offset": self.bitmap_offset,
288                    "bitmap_length": self.bitmap_length,
289                    "scan_mode_flags": list(self.scan_mode_flags) if self.scan_mode_flags else None,
290                    "type_of_values": self.type_of_values,
291                    "number_of_data_points": self.number_of_data_points,
292                    "number_of_packed_values": self.number_of_packed_values,
293                },
294            }
295
296        def compute_encoded_size(self, input_byte_length: int, chunk_spec: "ArraySpec") -> int:
297            raise NotImplementedError("Grib2SerializerCodec has variable encoded size")
298
299        async def _decode_single(
300            self,
301            chunk_bytes: "Buffer",
302            chunk_spec: "ArraySpec",
303        ) -> "NDBuffer":
304            raw = bytes(chunk_bytes.as_array_like())
305            fld = _decode_grib2_bytes(
306                raw,
307                drtn=self.drtn,
308                drt=list(self.drt),
309                gdtn=self.gdtn,
310                gdt=list(self.gdt),
311                nx=self.nx,
312                ny=self.ny,
313                bitmap_flag=self.bitmap_flag,
314                bitmap_length=self.bitmap_length,
315                scan_mode_flags=list(self.scan_mode_flags) if self.scan_mode_flags else None,
316                type_of_values=self.type_of_values,
317                number_of_data_points=self.number_of_data_points,
318                number_of_packed_values=self.number_of_packed_values,
319            )
320            # Reshape to the full chunk shape zarr expects (e.g. [1,1,1,1,ny,nx])
321            if fld.shape != chunk_spec.shape:
322                fld = fld.reshape(chunk_spec.shape)
323            return chunk_spec.prototype.nd_buffer.from_ndarray_like(fld)
324
325        async def _encode_single(
326            self,
327            chunk_array: "NDBuffer",
328            chunk_spec: "ArraySpec",
329        ) -> Optional["Buffer"]:
330            raise NotImplementedError("Grib2SerializerCodec is decode-only")
331
332    _zarr_registry.register_codec("grib2io", Grib2SerializerCodec)
333    # VirtualiZarr converts numcodecs {"id": "grib2io"} → zarr v3 {"name": "numcodecs.grib2io"}
334    # so we must also register under the prefixed name.
335    _zarr_registry.register_codec("numcodecs.grib2io", Grib2SerializerCodec)
336
337
338def _decode_grib2_bytes(
339    raw: bytes,
340    *,
341    drtn: int,
342    drt: List[int],
343    gdtn: int,
344    gdt: List[int],
345    nx: int,
346    ny: int,
347    bitmap_flag: int,
348    bitmap_length: Optional[int],
349    scan_mode_flags: Optional[List[int]],
350    type_of_values: int,
351    number_of_data_points: int,
352    number_of_packed_values: int,
353) -> np.ndarray:
354    """Shared GRIB2 decode logic used by both :class:`Grib2Codec` (numcodecs)
355    and :class:`Grib2SerializerCodec` (zarr v3).
356    """
357    gdt_arr = np.array(gdt, dtype=np.int64)
358    drt_arr = np.array(drt, dtype=np.int64)
359
360    storageorder = "C"
361    if scan_mode_flags is not None and len(scan_mode_flags) > 2:
362        storageorder = "F" if scan_mode_flags[2] else "C"
363
364    # --- Dynamic per-chunk parsing -------------------------------------------
365    # If the raw bytes start with GRIB2 Section 5 (byte[4] == 5), parse the
366    # data representation template and bitmap from the chunk bytes rather than
367    # using the static codec-config values.  This allows arrays whose messages
368    # have different DRT values (reference value, scale factors, etc.) to all
369    # decode correctly from a single shared .zarray codec config.
370    if len(raw) > 11 and raw[4] == 5:
371        # Parse section 5
372        sec5_len = int.from_bytes(raw[0:4], "big")
373        _drtn, drt_arr, _npts, _ipos = g2clib.unpack5(bytes(raw[:sec5_len]))
374        drtn = int(_drtn)
375        number_of_packed_values = int(_npts)
376        raw = raw[sec5_len:]
377
378    # Now raw starts with section 6 (bitmap indicator section).
379    # Always present (6 bytes minimum with indicator=255 when no bitmap).
380    if len(raw) > 5 and raw[4] == 6:
381        sec6_len = int.from_bytes(raw[0:4], "big")
382        bitmap_indicator = raw[5]
383        if bitmap_indicator in {0, 254}:
384            # Bitmap is present; unpack6 reads it from the sec6 bytes
385            bmap_bytes = bytes(raw[:sec6_len])
386            raw = raw[sec6_len:]
387            _bmapflag, bmap, _bpos = g2clib.unpack6(bmap_bytes, number_of_data_points)
388        else:
389            bmap = None
390            raw = raw[sec6_len:]
391
392        fld1, _ipos = g2clib.unpack7(
393            raw,
394            gdtn,
395            gdt_arr,
396            drtn,
397            drt_arr,
398            number_of_packed_values,
399            storageorder=storageorder,
400        )
401
402        if bitmap_indicator in {0, 254}:
403            if bmap is not None:
404                fld = np.full(number_of_data_points, np.nan, dtype=np.float32)
405                np.put(fld, np.nonzero(bmap), fld1)
406            else:
407                fld = fld1
408        else:
409            fld = fld1
410
411    else:
412        # Legacy path: raw already starts at sec6 or sec7 (old manifest format)
413        bmap = None
414        sec7_buf = raw
415        if bitmap_flag in {0, 254} and bitmap_length is not None and bitmap_length > 0:
416            bmap_bytes = raw[:bitmap_length]
417            sec7_buf = raw[bitmap_length:]
418            _bmapflag, bmap, _bpos = g2clib.unpack6(bmap_bytes, number_of_data_points)
419
420        fld1, _ipos = g2clib.unpack7(
421            sec7_buf,
422            gdtn,
423            gdt_arr,
424            drtn,
425            drt_arr,
426            number_of_packed_values,
427            storageorder=storageorder,
428        )
429
430        if bitmap_flag in {0, 254}:
431            if bmap is not None:
432                fld = np.full(number_of_data_points, np.nan, dtype=np.float32)
433                np.put(fld, np.nonzero(bmap), fld1)
434            else:
435                fld = fld1
436        else:
437            fld = fld1
438    # -------------------------------------------------------------------------
439
440    fld = np.reshape(fld, (ny, nx))
441
442    if scan_mode_flags is not None and len(scan_mode_flags) > 3:
443        if scan_mode_flags[3]:
444            fldsave = fld.astype(np.float32)
445            fld[1::2, :] = fldsave[1::2, ::-1]
446
447    if type_of_values == 1:
448        fld = fld.astype(np.int32)
449
450    return fld
class Grib2Codec(numcodecs.abc.Codec):
 53class Grib2Codec(Codec):
 54    """Zarr codec for decoding raw GRIB2 section 7 bytes.
 55
 56    All constructor parameters are JSON-serializable integers or lists
 57    extracted from the GRIB2 message's section metadata at
 58    reference-generation time.  Together they carry everything needed to
 59    call ``g2clib.unpack7()`` — the same low-level routine used by
 60    ``grib2io._data()``.
 61    """
 62
 63    codec_id = "grib2io"
 64
 65    def __init__(
 66        self,
 67        drtn: int,
 68        drt: List[int],
 69        gdtn: int,
 70        gdt: List[int],
 71        gds: List[int],
 72        nx: int,
 73        ny: int,
 74        bitmap_flag: int,
 75        bitmap_offset: Optional[int] = None,
 76        bitmap_length: Optional[int] = None,
 77        scan_mode_flags: Optional[List[int]] = None,
 78        type_of_values: int = 0,
 79        number_of_data_points: int = 0,
 80        number_of_packed_values: int = 0,
 81    ):
 82        if not _HAS_NUMCODECS:
 83            raise ImportError("numcodecs is required for Grib2Codec. Install with: pip install grib2io[kerchunk]")
 84        self.drtn = drtn
 85        self.drt = list(drt)
 86        self.gdtn = gdtn
 87        self.gdt = list(gdt)
 88        self.gds = list(gds)
 89        self.nx = nx
 90        self.ny = ny
 91        self.bitmap_flag = bitmap_flag
 92        self.bitmap_offset = bitmap_offset
 93        self.bitmap_length = bitmap_length
 94        self.scan_mode_flags = list(scan_mode_flags) if scan_mode_flags is not None else None
 95        self.type_of_values = type_of_values
 96        self.number_of_data_points = number_of_data_points
 97        self.number_of_packed_values = number_of_packed_values
 98
 99    # ---- encode (not supported) ------------------------------------------
100
101    def encode(self, buf):
102        """Not supported — GRIB2 encoding is handled by ``grib2io.open()``."""
103        raise NotImplementedError("Grib2Codec is decode-only; GRIB2 encoding is handled by grib2io.open()")
104
105    # ---- decode ----------------------------------------------------------
106
107    def decode(self, buf, out=None):
108        """Decode raw GRIB2 section 7 bytes into a NumPy array."""
109        if not isinstance(buf, bytes):
110            buf = bytes(buf)
111
112        fld = _decode_grib2_bytes(
113            buf,
114            drtn=self.drtn,
115            drt=list(self.drt) if not isinstance(self.drt, list) else self.drt,
116            gdtn=self.gdtn,
117            gdt=list(self.gdt) if not isinstance(self.gdt, list) else self.gdt,
118            nx=self.nx,
119            ny=self.ny,
120            bitmap_flag=self.bitmap_flag,
121            bitmap_length=self.bitmap_length,
122            scan_mode_flags=list(self.scan_mode_flags) if self.scan_mode_flags is not None else None,
123            type_of_values=self.type_of_values,
124            number_of_data_points=self.number_of_data_points,
125            number_of_packed_values=self.number_of_packed_values,
126        )
127
128        if out is not None:
129            np.copyto(out, fld)
130            return out
131
132        return fld
133
134    # ---- config serialization --------------------------------------------
135
136    def get_config(self):
137        """Return a JSON-serializable dict of all codec parameters."""
138        return {
139            "id": self.codec_id,
140            "drtn": self.drtn,
141            "drt": self.drt,
142            "gdtn": self.gdtn,
143            "gdt": self.gdt,
144            "gds": self.gds,
145            "nx": self.nx,
146            "ny": self.ny,
147            "bitmap_flag": self.bitmap_flag,
148            "bitmap_offset": self.bitmap_offset,
149            "bitmap_length": self.bitmap_length,
150            "scan_mode_flags": self.scan_mode_flags,
151            "type_of_values": self.type_of_values,
152            "number_of_data_points": self.number_of_data_points,
153            "number_of_packed_values": self.number_of_packed_values,
154        }
155
156    @classmethod
157    def from_config(cls, config):
158        """Reconstruct the codec from a configuration dict.
159
160        Parameters
161        ----------
162        config : dict
163            Dictionary as returned by :meth:`get_config`.
164
165        Returns
166        -------
167        Grib2Codec
168        """
169        # Remove the 'id' key which is not a constructor parameter
170        cfg = {k: v for k, v in config.items() if k != "id"}
171        return cls(**cfg)

Zarr codec for decoding raw GRIB2 section 7 bytes.

All constructor parameters are JSON-serializable integers or lists extracted from the GRIB2 message's section metadata at reference-generation time. Together they carry everything needed to call g2clib.unpack7() — the same low-level routine used by grib2io._data().

Grib2Codec( drtn: int, drt: List[int], gdtn: int, gdt: List[int], gds: List[int], nx: int, ny: int, bitmap_flag: int, bitmap_offset: Optional[int] = None, bitmap_length: Optional[int] = None, scan_mode_flags: Optional[List[int]] = None, type_of_values: int = 0, number_of_data_points: int = 0, number_of_packed_values: int = 0)
65    def __init__(
66        self,
67        drtn: int,
68        drt: List[int],
69        gdtn: int,
70        gdt: List[int],
71        gds: List[int],
72        nx: int,
73        ny: int,
74        bitmap_flag: int,
75        bitmap_offset: Optional[int] = None,
76        bitmap_length: Optional[int] = None,
77        scan_mode_flags: Optional[List[int]] = None,
78        type_of_values: int = 0,
79        number_of_data_points: int = 0,
80        number_of_packed_values: int = 0,
81    ):
82        if not _HAS_NUMCODECS:
83            raise ImportError("numcodecs is required for Grib2Codec. Install with: pip install grib2io[kerchunk]")
84        self.drtn = drtn
85        self.drt = list(drt)
86        self.gdtn = gdtn
87        self.gdt = list(gdt)
88        self.gds = list(gds)
89        self.nx = nx
90        self.ny = ny
91        self.bitmap_flag = bitmap_flag
92        self.bitmap_offset = bitmap_offset
93        self.bitmap_length = bitmap_length
94        self.scan_mode_flags = list(scan_mode_flags) if scan_mode_flags is not None else None
95        self.type_of_values = type_of_values
96        self.number_of_data_points = number_of_data_points
97        self.number_of_packed_values = number_of_packed_values
codec_id = 'grib2io'

Codec identifier.

drtn
drt
gdtn
gdt
gds
nx
ny
bitmap_flag
bitmap_offset
bitmap_length
scan_mode_flags
type_of_values
number_of_data_points
number_of_packed_values
def encode(self, buf):
101    def encode(self, buf):
102        """Not supported — GRIB2 encoding is handled by ``grib2io.open()``."""
103        raise NotImplementedError("Grib2Codec is decode-only; GRIB2 encoding is handled by grib2io.open()")

Not supported — GRIB2 encoding is handled by grib2io.open().

def decode(self, buf, out=None):
107    def decode(self, buf, out=None):
108        """Decode raw GRIB2 section 7 bytes into a NumPy array."""
109        if not isinstance(buf, bytes):
110            buf = bytes(buf)
111
112        fld = _decode_grib2_bytes(
113            buf,
114            drtn=self.drtn,
115            drt=list(self.drt) if not isinstance(self.drt, list) else self.drt,
116            gdtn=self.gdtn,
117            gdt=list(self.gdt) if not isinstance(self.gdt, list) else self.gdt,
118            nx=self.nx,
119            ny=self.ny,
120            bitmap_flag=self.bitmap_flag,
121            bitmap_length=self.bitmap_length,
122            scan_mode_flags=list(self.scan_mode_flags) if self.scan_mode_flags is not None else None,
123            type_of_values=self.type_of_values,
124            number_of_data_points=self.number_of_data_points,
125            number_of_packed_values=self.number_of_packed_values,
126        )
127
128        if out is not None:
129            np.copyto(out, fld)
130            return out
131
132        return fld

Decode raw GRIB2 section 7 bytes into a NumPy array.

def get_config(self):
136    def get_config(self):
137        """Return a JSON-serializable dict of all codec parameters."""
138        return {
139            "id": self.codec_id,
140            "drtn": self.drtn,
141            "drt": self.drt,
142            "gdtn": self.gdtn,
143            "gdt": self.gdt,
144            "gds": self.gds,
145            "nx": self.nx,
146            "ny": self.ny,
147            "bitmap_flag": self.bitmap_flag,
148            "bitmap_offset": self.bitmap_offset,
149            "bitmap_length": self.bitmap_length,
150            "scan_mode_flags": self.scan_mode_flags,
151            "type_of_values": self.type_of_values,
152            "number_of_data_points": self.number_of_data_points,
153            "number_of_packed_values": self.number_of_packed_values,
154        }

Return a JSON-serializable dict of all codec parameters.

@classmethod
def from_config(cls, config):
156    @classmethod
157    def from_config(cls, config):
158        """Reconstruct the codec from a configuration dict.
159
160        Parameters
161        ----------
162        config : dict
163            Dictionary as returned by :meth:`get_config`.
164
165        Returns
166        -------
167        Grib2Codec
168        """
169        # Remove the 'id' key which is not a constructor parameter
170        cfg = {k: v for k, v in config.items() if k != "id"}
171        return cls(**cfg)

Reconstruct the codec from a configuration dict.

Parameters
Returns
  • Grib2Codec
@dataclass(frozen=True)
class Grib2SerializerCodec(zarr.abc.codec.BaseCodec[zarr.core.buffer.core.NDBuffer, zarr.core.buffer.core.Buffer]):
195    @dataclass(frozen=True)
196    class Grib2SerializerCodec(ArrayBytesCodec):
197        """Zarr v3 ``ArrayBytesCodec`` (serializer) for GRIB2 virtual chunks.
198
199        Receives the raw GRIB2 section 7 bytes fetched from a virtual chunk
200        reference and decodes them into a NumPy array using
201        ``g2clib.unpack7()``.  This is the zarr v3 counterpart of
202        :class:`Grib2Codec` (which targets numcodecs / zarr v2).
203
204        All constructor parameters mirror those of :class:`Grib2Codec` and
205        are stored in ``zarr.json`` so they travel with the array metadata.
206        """
207
208        is_fixed_size: bool = False  # GRIB2 compressed size varies
209
210        # ---- GRIB2 decode parameters (mirror Grib2Codec) ----
211        drtn: int = 0
212        drt: tuple = ()
213        gdtn: int = 0
214        gdt: tuple = ()
215        gds: tuple = ()
216        nx: int = 0
217        ny: int = 0
218        bitmap_flag: int = 255
219        bitmap_offset: Optional[int] = None
220        bitmap_length: Optional[int] = None
221        scan_mode_flags: Optional[tuple] = None
222        type_of_values: int = 0
223        number_of_data_points: int = 0
224        number_of_packed_values: int = 0
225
226        # dataclass frozen=True uses __init__ auto-generated; we need a
227        # custom __init__ to accept list args and coerce to tuples.
228        def __new__(cls, **kwargs):
229            # coerce list args to tuples so the frozen dataclass is hashable
230            for f in ("drt", "gdt", "gds", "scan_mode_flags"):
231                if f in kwargs and isinstance(kwargs[f], list):
232                    kwargs[f] = tuple(kwargs[f])
233            obj = object.__new__(cls)
234            return obj
235
236        def __init__(
237            self,
238            *,
239            drtn: int = 0,
240            drt=(),
241            gdtn: int = 0,
242            gdt=(),
243            gds=(),
244            nx: int = 0,
245            ny: int = 0,
246            bitmap_flag: int = 255,
247            bitmap_offset: Optional[int] = None,
248            bitmap_length: Optional[int] = None,
249            scan_mode_flags=None,
250            type_of_values: int = 0,
251            number_of_data_points: int = 0,
252            number_of_packed_values: int = 0,
253        ):
254            object.__setattr__(self, "drtn", int(drtn))
255            object.__setattr__(self, "drt", tuple(drt) if drt is not None else ())
256            object.__setattr__(self, "gdtn", int(gdtn))
257            object.__setattr__(self, "gdt", tuple(gdt) if gdt is not None else ())
258            object.__setattr__(self, "gds", tuple(gds) if gds is not None else ())
259            object.__setattr__(self, "nx", int(nx))
260            object.__setattr__(self, "ny", int(ny))
261            object.__setattr__(self, "bitmap_flag", int(bitmap_flag))
262            object.__setattr__(self, "bitmap_offset", bitmap_offset)
263            object.__setattr__(self, "bitmap_length", bitmap_length)
264            object.__setattr__(self, "scan_mode_flags", tuple(scan_mode_flags) if scan_mode_flags is not None else None)
265            object.__setattr__(self, "type_of_values", int(type_of_values))
266            object.__setattr__(self, "number_of_data_points", int(number_of_data_points))
267            object.__setattr__(self, "number_of_packed_values", int(number_of_packed_values))
268
269        # ---- zarr v3 codec interface --------------------------------
270
271        @classmethod
272        def from_dict(cls, data: dict) -> "Grib2SerializerCodec":
273            cfg = data.get("configuration", {})
274            return cls(**cfg)
275
276        def to_dict(self) -> dict:
277            return {
278                "name": "grib2io",
279                "configuration": {
280                    "drtn": self.drtn,
281                    "drt": list(self.drt),
282                    "gdtn": self.gdtn,
283                    "gdt": list(self.gdt),
284                    "gds": list(self.gds),
285                    "nx": self.nx,
286                    "ny": self.ny,
287                    "bitmap_flag": self.bitmap_flag,
288                    "bitmap_offset": self.bitmap_offset,
289                    "bitmap_length": self.bitmap_length,
290                    "scan_mode_flags": list(self.scan_mode_flags) if self.scan_mode_flags else None,
291                    "type_of_values": self.type_of_values,
292                    "number_of_data_points": self.number_of_data_points,
293                    "number_of_packed_values": self.number_of_packed_values,
294                },
295            }
296
297        def compute_encoded_size(self, input_byte_length: int, chunk_spec: "ArraySpec") -> int:
298            raise NotImplementedError("Grib2SerializerCodec has variable encoded size")
299
300        async def _decode_single(
301            self,
302            chunk_bytes: "Buffer",
303            chunk_spec: "ArraySpec",
304        ) -> "NDBuffer":
305            raw = bytes(chunk_bytes.as_array_like())
306            fld = _decode_grib2_bytes(
307                raw,
308                drtn=self.drtn,
309                drt=list(self.drt),
310                gdtn=self.gdtn,
311                gdt=list(self.gdt),
312                nx=self.nx,
313                ny=self.ny,
314                bitmap_flag=self.bitmap_flag,
315                bitmap_length=self.bitmap_length,
316                scan_mode_flags=list(self.scan_mode_flags) if self.scan_mode_flags else None,
317                type_of_values=self.type_of_values,
318                number_of_data_points=self.number_of_data_points,
319                number_of_packed_values=self.number_of_packed_values,
320            )
321            # Reshape to the full chunk shape zarr expects (e.g. [1,1,1,1,ny,nx])
322            if fld.shape != chunk_spec.shape:
323                fld = fld.reshape(chunk_spec.shape)
324            return chunk_spec.prototype.nd_buffer.from_ndarray_like(fld)
325
326        async def _encode_single(
327            self,
328            chunk_array: "NDBuffer",
329            chunk_spec: "ArraySpec",
330        ) -> Optional["Buffer"]:
331            raise NotImplementedError("Grib2SerializerCodec is decode-only")

Zarr v3 ArrayBytesCodec (serializer) for GRIB2 virtual chunks.

Receives the raw GRIB2 section 7 bytes fetched from a virtual chunk reference and decodes them into a NumPy array using g2clib.unpack7(). This is the zarr v3 counterpart of Grib2Codec (which targets numcodecs / zarr v2).

All constructor parameters mirror those of Grib2Codec and are stored in zarr.json so they travel with the array metadata.

Grib2SerializerCodec( *, drtn: int = 0, drt=(), gdtn: int = 0, gdt=(), gds=(), nx: int = 0, ny: int = 0, bitmap_flag: int = 255, bitmap_offset: Optional[int] = None, bitmap_length: Optional[int] = None, scan_mode_flags=None, type_of_values: int = 0, number_of_data_points: int = 0, number_of_packed_values: int = 0)
236        def __init__(
237            self,
238            *,
239            drtn: int = 0,
240            drt=(),
241            gdtn: int = 0,
242            gdt=(),
243            gds=(),
244            nx: int = 0,
245            ny: int = 0,
246            bitmap_flag: int = 255,
247            bitmap_offset: Optional[int] = None,
248            bitmap_length: Optional[int] = None,
249            scan_mode_flags=None,
250            type_of_values: int = 0,
251            number_of_data_points: int = 0,
252            number_of_packed_values: int = 0,
253        ):
254            object.__setattr__(self, "drtn", int(drtn))
255            object.__setattr__(self, "drt", tuple(drt) if drt is not None else ())
256            object.__setattr__(self, "gdtn", int(gdtn))
257            object.__setattr__(self, "gdt", tuple(gdt) if gdt is not None else ())
258            object.__setattr__(self, "gds", tuple(gds) if gds is not None else ())
259            object.__setattr__(self, "nx", int(nx))
260            object.__setattr__(self, "ny", int(ny))
261            object.__setattr__(self, "bitmap_flag", int(bitmap_flag))
262            object.__setattr__(self, "bitmap_offset", bitmap_offset)
263            object.__setattr__(self, "bitmap_length", bitmap_length)
264            object.__setattr__(self, "scan_mode_flags", tuple(scan_mode_flags) if scan_mode_flags is not None else None)
265            object.__setattr__(self, "type_of_values", int(type_of_values))
266            object.__setattr__(self, "number_of_data_points", int(number_of_data_points))
267            object.__setattr__(self, "number_of_packed_values", int(number_of_packed_values))
is_fixed_size: bool = False
drtn: int = 0
drt: tuple = ()
gdtn: int = 0
gdt: tuple = ()
gds: tuple = ()
nx: int = 0
ny: int = 0
bitmap_flag: int = 255
bitmap_offset: Optional[int] = None
bitmap_length: Optional[int] = None
scan_mode_flags: Optional[tuple] = None
type_of_values: int = 0
number_of_data_points: int = 0
number_of_packed_values: int = 0
@classmethod
def from_dict(cls, data: dict) -> Grib2SerializerCodec:
271        @classmethod
272        def from_dict(cls, data: dict) -> "Grib2SerializerCodec":
273            cfg = data.get("configuration", {})
274            return cls(**cfg)

Create an instance of the model from a dictionary

def to_dict(self) -> dict:
276        def to_dict(self) -> dict:
277            return {
278                "name": "grib2io",
279                "configuration": {
280                    "drtn": self.drtn,
281                    "drt": list(self.drt),
282                    "gdtn": self.gdtn,
283                    "gdt": list(self.gdt),
284                    "gds": list(self.gds),
285                    "nx": self.nx,
286                    "ny": self.ny,
287                    "bitmap_flag": self.bitmap_flag,
288                    "bitmap_offset": self.bitmap_offset,
289                    "bitmap_length": self.bitmap_length,
290                    "scan_mode_flags": list(self.scan_mode_flags) if self.scan_mode_flags else None,
291                    "type_of_values": self.type_of_values,
292                    "number_of_data_points": self.number_of_data_points,
293                    "number_of_packed_values": self.number_of_packed_values,
294                },
295            }

Recursively serialize this model to a dictionary. This method inspects the fields of self and calls x.to_dict() for any fields that are instances of Metadata. Sequences of Metadata are similarly recursed into, and the output of that recursion is collected in a list.

def compute_encoded_size( self, input_byte_length: int, chunk_spec: zarr.core.array_spec.ArraySpec) -> int:
297        def compute_encoded_size(self, input_byte_length: int, chunk_spec: "ArraySpec") -> int:
298            raise NotImplementedError("Grib2SerializerCodec has variable encoded size")

Given an input byte length, this method returns the output byte length. Raises a NotImplementedError for codecs with variable-sized outputs (e.g. compressors).

Parameters
  • input_byte_length (int):

  • chunk_spec (ArraySpec):

Returns
  • int