Skip to content

Rain grid

dpc_radar.grid

One radar frame as an array, and what to trust in it.

A :class:RainGrid is a DPC frame read from its GeoTIFF: rain in mm (CUM, SRT1) or mm/h (SRI), NaN where the radar has no data. Two grids exist and must not be mixed: SRI and SRT1 are 1 km in a Transverse Mercator projection, the CUM products 0.01° in EPSG:4326. Every method takes lon/lat in degrees and converts them to the grid's own projection.

Read a place with :meth:RainGrid.sample: the median of the 3x3 pixels around it, never one pixel. Check a frame against the one before it with :meth:RainGrid.check before trusting it: on 9-11 October 2026 and again from 21 October to 3 November the DPC published every CUM24 frame at :30 with a bell of up to 20,584 mm over Abruzzo, while the :00 frames were right.

BBox module-attribute

BBox = tuple[float, float, float, float]

west, south, east, north, in degrees (EPSG:4326).

RainGrid dataclass

One frame: data in mm or mm/h (float32, as published), NaN where there is no data.

from_geotiff classmethod

from_geotiff(raw: bytes, name: str | Product, time: datetime) -> RainGrid

A frame from the bytes of its GeoTIFF; -9999 becomes NaN.

sample

sample(points: list[tuple[float, float]]) -> list[float | None]

The value at each lon/lat point: the median of the valid 3x3 pixels.

The median and not the pixel, so that one wrong pixel never becomes a flood on one cell. Outside the frame, or where the 3x3 window holds no data, the answer is None: not measured, which is not the same as dry.

peak

peak(bbox: BBox, *, threshold: float) -> tuple[float, int]

The highest value inside bbox, and how many pixels reach threshold.

(0.0, 0) outside the frame or where there is no data: Limen's radar trigger asks "is it raining hard enough, on enough km², here?".

crop

crop(bbox: BBox) -> RainGrid

The part of the frame inside bbox (plus the pixel on each edge).

check

check(previous: RainGrid, *, max_step: float, min_pixels: int = 5) -> FrameCheck

Whether to trust this frame, given the one before it.

Two accumulations of the same length, one period apart, differ by the rain that entered the window minus the rain that left it: never more than one period of rain. Where they differ by more than max_step, one of the two is wrong, and the sign says which: every corruption seen so far adds rain.

  • no pixel jumps: consistent, the frame as it is;
  • fewer than min_pixels jump: pixels_masked, those pixels set to NaN;
  • more jump, and this frame is the higher: rejected, use the previous frame instead;
  • more jump, and this frame is the lower: previous_wrong, this frame as it is (it is the previous one that was corrupted);
  • different grids: not_comparable, the frame as it is.

max_step is the caller's choice (Limen uses 150 mm for CUM frames 30 minutes apart). Measured on the benchmark of known cases, this rule passes 17 of 18 checks and, on 96 pairs of whole frames, rejects the 44 corrupted ones and no clean one. It cannot see an artefact that does not move between frames.

discard_inconsistent

discard_inconsistent(previous: RainGrid, *, max_step: float) -> tuple[RainGrid, int]

This frame without the pixels that moved more than max_step.

Limen's rule from October 2026, kept for comparison: it cannot tell which of the two frames is wrong, so it masks the pixels in both cases, and it lets through the edges of a corrupted bell (jumps under max_step). Prefer :meth:check.

FrameCheck dataclass

The answer of :meth:RainGrid.check.

grid is the frame to use (with masked pixels for pixels_masked), or None when the frame is rejected. jumps is how many pixels moved more than max_step.