FireDataForge

Bridge between FireDataForge event outputs and PyTorchFire. FireDataForge turns a single MTBS fire id into a directory of harmonized raster layers (terrain, fuels, weather, observed perimeters, ...); this module maps those layers onto the inputs WildfireModel expects.

The reader only depends on numpy (already a PyTorchFire dependency), so the heavyweight firedataforge package is not required to consume its outputs. To also install the producer:

pip install 'pytorchfire[firedataforge]'
from pytorchfire import load_event

event = load_event('output/CA3432611848120191010')
model = event.build_model()      # a WildfireModel seeded with the real fire
for _ in range(100):
    model.compute()

Bridge between FireDataForge outputs and PyTorchFire.

FireDataForge turns a single MTBS fire ID into a directory of harmonized, analysis-ready raster layers (terrain, fuels, weather, observed fire perimeters, ...), all sampled on one common grid and saved as <layer>.npy files. This module reads such an event directory and maps those layers onto the inputs WildfireModel expects, so a real fire becomes a ready-to-run (or ready-to-calibrate) simulation in a couple of lines:

from pytorchfire import WildfireModel
from pytorchfire.firedataforge import load_event

event = load_event('output/CA3432611848120191010')
model = event.build_model()  # a WildfireModel seeded with the real terrain/fuel/wind
for _ in range(100):
    model.compute()

The reader depends only on numpy (already a PyTorchFire dependency), so the heavyweight geospatial firedataforge package is not required to consume its outputs. If firedataforge happens to be installed, its typed firedataforge.load_numpy loader is used; otherwise the same .npy files are read directly with numpy. Install the producer alongside PyTorchFire with:

pip install "pytorchfire[firedataforge]"

Layer-to-input mapping

WildfireModel input FireDataForge layer(s) How it is derived
slope elevation calculate_slope at the grid resolution
p_veg canopy_cover (%) mean-centered, then piecewise-rescaled so [min, mean, max] -> [p_veg_low, 0, p_veg_high] (defaults [-0.4, 0, 0.35])
p_den canopy_bulk_density mean-centered, then piecewise-rescaled so [min, mean, max] -> [p_den_low, 0, p_den_high] (defaults [-0.2, 0, 0.4])
wind_velocity / wind_towards_direction u10, v10 (HRRR, coarse) resampled to the grid, then convert_wind_components_to_velocity_and_direction
initial_ignition burn_perimeter (first frame) earliest observed perimeter as the ignition source
calibration target burn_perimeter (last / all frames) cumulative burned area, see target / target_series

Every mapping is overridable, and any layer can be read directly with frame / frames to build your own.

Advancing time

Time-varying layers (the hourly HRRR wind, the observed perimeters) are sampled irregularly and may have gaps. Rather than assume a frame per simulation step, drive the simulation with a datetime cursor: an EventClock (from event.clock(dt)) advances a wall-clock counter by dt per step and swaps in a layer's next frame only once the counter reaches that frame's timestamp -- so a gap simply holds the last valid frame until the next real observation's time arrives:

from datetime import timedelta

clock = event.clock(timedelta(hours=1))   # one model step == one real hour
for _ in range(steps):
    clock.apply_wind(model)               # no-op until a new wind frame is due
    model.compute()
    clock.tick()

EventClock(event, dt, start=None, layers=None)

A datetime cursor that walks a simulation through an event's real time.

Created with event.clock(dt). Each tick advances now by dt (the real-world duration of one simulation step) and recomputes, per tracked time-varying layer, the frame that is currently valid -- the most recent frame whose timestamp now has reached (via frame_index_at).

A frame is replaced only once a new one is actually due, so irregular sampling and outright gaps in a layer's series need no interpolation: the last valid frame simply persists until the next real observation's time arrives. This is what the FireDataForge data contract calls exposing each source "at its most recent valid observation".

Attributes:
  • event (FireDataForgeEvent) –

    The event being clocked.

  • dt

    Real-world duration of one simulation step (datetime.timedelta).

  • now

    The current wall-clock datetime of the cursor.

  • layers (list) –

    Time-varying layers being tracked.

Examples:

from datetime import timedelta

clock = event.clock(timedelta(hours=1))   # one model step == one hour
for _ in range(steps):
    clock.apply_wind(model)               # no-op until a new frame is due
    model.compute()
    clock.tick()
Source code in pytorchfire/firedataforge.py
def __init__(self, event: FireDataForgeEvent, dt: timedelta,
             start: Optional[datetime] = None,
             layers: Optional[list] = None):
    self.event = event
    self.dt = dt
    if layers is None:
        layers = [name for name in self.WIND_LAYERS if event.has(name)]
    self.layers = list(layers)
    self.now = start if start is not None else self._default_start()
    # Current valid frame index per tracked layer.
    self._index = {name: event.frame_index_at(name, self.now)
                   for name in self.layers}
    # Layers whose frame changed on the latest tick. Seeded with every
    # tracked layer so the first apply_*() call actually pushes a frame.
    self._changed = set(self.layers)

apply_wind(model, force=False)

Push the wind frame valid at now into model when one is due.

Skips the resample/convert work on every step that stays inside the current wind frame, updating the model only when a new wind frame has been reached since the last apply (or when force is set, e.g. to seed the very first frame).

Returns:
  • bool

    True if the model's wind was updated, False if nothing was

  • bool

    due. Does nothing (returns False) when the event has no wind.

Source code in pytorchfire/firedataforge.py
def apply_wind(self, model: WildfireModel, force: bool = False) -> bool:
    """Push the wind frame valid at ``now`` into ``model`` when one is due.

    Skips the resample/convert work on every step that stays inside the
    current wind frame, updating the model only when a new wind frame has
    been reached since the last apply (or when ``force`` is set, e.g. to
    seed the very first frame).

    Returns:
        ``True`` if the model's wind was updated, ``False`` if nothing was
        due. Does nothing (returns ``False``) when the event has no wind.
    """
    wind = [name for name in self.WIND_LAYERS if name in self._index]
    if not wind:
        return False
    if not (force or any(name in self._changed for name in wind)):
        return False
    self.event.apply_wind(model, self._index[wind[0]])
    self._changed -= set(wind)  # consumed; don't re-apply until next change
    return True

changed(layer)

Whether layer's valid frame changed on the latest tick.

Source code in pytorchfire/firedataforge.py
def changed(self, layer: str) -> bool:
    """Whether ``layer``'s valid frame changed on the latest [`tick`][pytorchfire.firedataforge.EventClock.tick]."""
    return layer in self._changed

index(layer)

Current valid frame index for layer (tracking it if new).

Source code in pytorchfire/firedataforge.py
def index(self, layer: str) -> int:
    """Current valid frame index for ``layer`` (tracking it if new)."""
    if layer not in self._index:
        self._index[layer] = self.event.frame_index_at(layer, self.now)
        self.layers.append(layer)
    return self._index[layer]

tick(dt=None)

Advance the counter and re-evaluate every tracked layer's frame.

Parameters:
  • dt (Optional[timedelta], default: None ) –

    Step to advance by; defaults to the clock's dt.

Returns:
  • set

    The set of tracked layer names whose valid frame changed this tick

  • set

    (empty when the step stayed inside every layer's current frame --

  • set

    the common case for sub-hourly steps or during a data gap).

Source code in pytorchfire/firedataforge.py
def tick(self, dt: Optional[timedelta] = None) -> set:
    """Advance the counter and re-evaluate every tracked layer's frame.

    Parameters:
        dt: Step to advance by; defaults to the clock's ``dt``.

    Returns:
        The set of tracked layer names whose valid frame changed this tick
        (empty when the step stayed inside every layer's current frame --
        the common case for sub-hourly steps or during a data gap).
    """
    self.now = self.now + (self.dt if dt is None else dt)
    self._changed = set()
    for name in self.layers:
        new = self.event.frame_index_at(name, self.now)
        if new != self._index[name]:
            self._index[name] = new
            self._changed.add(name)
    return self._changed

FireDataForgeEvent(event_dir)

A loaded FireDataForge event directory, adapted for WildfireModel.

Wraps the output/<event_id>/ directory produced by FireDataForge and exposes its layers both raw (via frame / frames) and pre-mapped onto the tensors WildfireModel consumes (via to_env_data / build_model).

Attributes:
  • event_dir (str) –

    Path to the event directory.

  • event_id (str) –

    MTBS event id (e.g. "CA3432611848120191010").

  • name (str) –

    Human-readable incident name (e.g. "SADDLERIDGE").

  • year (int) –

    Year the fire occurred.

  • resolution (float) –

    Grid resolution in CRS units (meters).

  • bounds (tuple) –

    (minx, miny, maxx, maxy) in crs units.

  • crs (str) –

    Coordinate reference system identifier (e.g. "EPSG:5070").

  • shape (tuple) –

    Grid shape as (height, width).

  • t_start

    Start datetime of the event window.

  • t_end

    End datetime of the event window.

Examples:

from pytorchfire.firedataforge import load_event

event = load_event('output/CA3432611848120191010')
print(event)                       # FireDataForgeEvent(SADDLERIDGE, ...)
model = event.build_model()        # ready-to-run WildfireModel
target = event.target()            # observed final burned area [H, W] bool

Open a FireDataForge event directory.

Parameters:
  • event_dir (str) –

    Path to an event output directory containing the <layer>.npy files (e.g. "output/CA3432611848120191010").

Raises:
  • FileNotFoundError

    If event_dir does not exist or has no task_info.npy describing the grid.

Source code in pytorchfire/firedataforge.py
def __init__(self, event_dir: str):
    """Open a FireDataForge event directory.

    Parameters:
        event_dir (str): Path to an event output directory containing the
            ``<layer>.npy`` files (e.g. ``"output/CA3432611848120191010"``).

    Raises:
        FileNotFoundError: If ``event_dir`` does not exist or has no
            ``task_info.npy`` describing the grid.
    """
    if not os.path.isdir(event_dir):
        raise FileNotFoundError(f"Not a FireDataForge event directory: {event_dir}")
    self.event_dir = event_dir
    self._cache: dict = {}

    info_path = os.path.join(event_dir, "task_info.npy")
    if not os.path.exists(info_path):
        raise FileNotFoundError(
            f"{event_dir} is missing task_info.npy; is it a FireDataForge event?")
    meta = dict(_load_layer(info_path).data[0])

    self.event_id = meta.get("event_id", os.path.basename(event_dir.rstrip("/")))
    self.name = meta.get("name")
    self.year = meta.get("year")
    self.resolution = float(meta.get("resolution", 30))
    self.bounds = tuple(meta["bounds"]) if meta.get("bounds") is not None else None
    self.crs = meta.get("crs")
    self.shape = tuple(meta["shape"])
    self.t_start = meta.get("t_start")
    self.t_end = meta.get("t_end")

n_wind_frames property

Number of hourly weather frames (0 if no wind layer).

apply_wind(model, frame)

Update an existing model's wind fields to a different weather frame.

Mirrors how the real-fire examples advance wind mid-simulation:

for f in range(event.n_wind_frames):
    event.apply_wind(model, f)
    model.compute()

For a wall-clock-driven, gap-aware schedule use clock instead of indexing frames by hand.

Source code in pytorchfire/firedataforge.py
def apply_wind(self, model: WildfireModel, frame: int) -> None:
    """Update an existing model's wind fields to a different weather frame.

    Mirrors how the real-fire examples advance wind mid-simulation:

    ```python
    for f in range(event.n_wind_frames):
        event.apply_wind(model, f)
        model.compute()
    ```

    For a wall-clock-driven, gap-aware schedule use
    [`clock`][pytorchfire.firedataforge.FireDataForgeEvent.clock] instead of
    indexing frames by hand.
    """
    wind = self.wind(frame=frame, dtype=model.wind_velocity.dtype,
                     device=model.wind_velocity.device)
    model.wind_velocity = wind["wind_velocity"]
    model.wind_towards_direction = wind["wind_towards_direction"]

available_layers()

Return the sorted names of every .npy layer in the directory.

Source code in pytorchfire/firedataforge.py
def available_layers(self) -> list:
    """Return the sorted names of every ``.npy`` layer in the directory."""
    return sorted(
        f[:-4] for f in os.listdir(self.event_dir) if f.endswith(".npy"))

build_model(params=None, wind_frame=0, keep_acc_mask=False, device=None, **env_kwargs)

Construct a WildfireModel seeded with this event's data.

The model is assembled on CPU and then moved to device (if given) so the environment buffers and the default scalar parameters end up on the same device, satisfying the model's internal sanity checks.

Parameters:
  • params (dict, default: None ) –

    Optional override of the model's scalar parameters (a, p_h, c_1, c_2, p_continue).

  • wind_frame (int, default: 0 ) –

    Weather frame to initialise the wind fields with.

  • keep_acc_mask (bool, default: False ) –

    Forwarded to WildfireModel.

  • device (Any, default: None ) –

    Device to move the finished model to.

  • **env_kwargs (Any, default: {} ) –

    Forwarded to to_env_data (e.g. p_veg_layer, p_den_layer).

Returns:
Source code in pytorchfire/firedataforge.py
def build_model(self, params: Optional[dict] = None, wind_frame: int = 0,
                keep_acc_mask: bool = False, device: Any = None,
                **env_kwargs: Any) -> WildfireModel:
    """Construct a [`WildfireModel`][pytorchfire.WildfireModel] seeded with this event's data.

    The model is assembled on CPU and then moved to ``device`` (if given) so
    the environment buffers and the default scalar parameters end up on the
    same device, satisfying the model's internal sanity checks.

    Parameters:
        params (dict): Optional override of the model's scalar parameters
            (``a``, ``p_h``, ``c_1``, ``c_2``, ``p_continue``).
        wind_frame (int): Weather frame to initialise the wind fields with.
        keep_acc_mask (bool): Forwarded to [`WildfireModel`][pytorchfire.WildfireModel].
        device: Device to move the finished model to.
        **env_kwargs: Forwarded to [`to_env_data`][pytorchfire.firedataforge.FireDataForgeEvent.to_env_data]
            (e.g. ``p_veg_layer``, ``p_den_layer``).

    Returns:
        A ready-to-run [`WildfireModel`][pytorchfire.WildfireModel].
    """
    env = self.to_env_data(wind_frame=wind_frame, device=None, **env_kwargs)
    model = WildfireModel(env_data=env, params=params, keep_acc_mask=keep_acc_mask)
    if device is not None:
        model = model.to(device)
    return model

clock(dt, start=None, layers=None)

Create an EventClock driving this event's time-varying layers.

Parameters:
  • dt (timedelta) –

    Real-world duration of one simulation step, as a datetime.timedelta. Each tick advances the counter by this much.

  • start (Optional[datetime], default: None ) –

    Datetime the counter starts at. Defaults to the first wind timestamp (else t_start), so the cursor begins on the first real observation.

  • layers (list, default: None ) –

    Time-varying layers to track. Defaults to whichever of the wind layers (u10/v10) the event produced.

Returns:

Examples:

from datetime import timedelta

clock = event.clock(timedelta(hours=1))
for _ in range(steps):
    clock.apply_wind(model)   # swaps frames only when one is due
    model.compute()
    clock.tick()
Source code in pytorchfire/firedataforge.py
def clock(self, dt: timedelta, start: Optional[datetime] = None,
          layers: Optional[list] = None) -> "EventClock":
    """Create an [`EventClock`][pytorchfire.firedataforge.EventClock] driving this event's time-varying layers.

    Parameters:
        dt: Real-world duration of one simulation step, as a
            ``datetime.timedelta``. Each [`tick`][pytorchfire.firedataforge.EventClock.tick]
            advances the counter by this much.
        start: Datetime the counter starts at. Defaults to the first wind
            timestamp (else ``t_start``), so the cursor begins on the first
            real observation.
        layers (list): Time-varying layers to track. Defaults to whichever
            of the wind layers (``u10``/``v10``) the event produced.

    Returns:
        An [`EventClock`][pytorchfire.firedataforge.EventClock].

    Examples:
        ```python
        from datetime import timedelta

        clock = event.clock(timedelta(hours=1))
        for _ in range(steps):
            clock.apply_wind(model)   # swaps frames only when one is due
            model.compute()
            clock.tick()
        ```
    """
    return EventClock(self, dt, start=start, layers=layers)

frame(name, index=0)

Return a single frame of layer name as a numpy array.

Parameters:
  • name (str) –

    Layer name (file stem).

  • index (int, default: 0 ) –

    Frame index; -1 gives the most recent frame.

Source code in pytorchfire/firedataforge.py
def frame(self, name: str, index: int = 0) -> np.ndarray:
    """Return a single frame of layer ``name`` as a `numpy` array.

    Parameters:
        name (str): Layer name (file stem).
        index (int): Frame index; ``-1`` gives the most recent frame.
    """
    return _as_numpy(self.layer(name).data[index])

frame_index_at(layer, when, default=0)

Index of the frame of layer that is valid at datetime when.

Returns the latest frame whose timestamp is at or before when -- the most recent observation the simulation clock has actually reached. Between two timestamps the earlier frame stays valid, so this is the forward-marching, gap-aware counterpart of wind_frame_index (which snaps to the nearest frame in either direction): as a datetime counter advances, the next frame is selected only once when reaches its timestamp, so a gap in an irregularly sampled layer simply holds the last frame until the next real observation's time arrives. This is the primitive EventClock steps on.

Layers with no timestamps (static fuels/terrain) always return default. when earlier than the first timestamp also returns default (the first frame).

Source code in pytorchfire/firedataforge.py
def frame_index_at(self, layer: str, when, default: int = 0) -> int:
    """Index of the frame of ``layer`` that is *valid* at datetime ``when``.

    Returns the latest frame whose timestamp is at or before ``when`` -- the
    most recent observation the simulation clock has actually reached.
    Between two timestamps the earlier frame stays valid, so this is the
    forward-marching, gap-aware counterpart of
    [`wind_frame_index`][pytorchfire.firedataforge.FireDataForgeEvent.wind_frame_index]
    (which snaps to the nearest frame in *either* direction): as a datetime
    counter advances, the next frame is selected only once ``when`` reaches
    its timestamp, so a gap in an irregularly sampled layer simply holds the
    last frame until the next real observation's time arrives. This is the
    primitive [`EventClock`][pytorchfire.firedataforge.EventClock] steps on.

    Layers with no timestamps (static fuels/terrain) always return
    ``default``. ``when`` earlier than the first timestamp also returns
    ``default`` (the first frame).
    """
    stamps = self.timestamps(layer)
    if not stamps:
        return default
    idx = default
    for i, stamp in enumerate(stamps):
        if stamp <= when:
            idx = i
        else:
            break  # timestamps are ascending; nothing later can qualify
    return idx

frames(name)

Return every frame of layer name as a list of numpy arrays.

Source code in pytorchfire/firedataforge.py
def frames(self, name: str) -> list:
    """Return every frame of layer ``name`` as a list of `numpy` arrays."""
    return [_as_numpy(frame) for frame in self.layer(name).data]

has(name)

Return True if the event produced a <name>.npy layer.

Source code in pytorchfire/firedataforge.py
def has(self, name: str) -> bool:
    """Return ``True`` if the event produced a ``<name>.npy`` layer."""
    return os.path.exists(os.path.join(self.event_dir, f"{name}.npy"))

initial_ignition(layer='burn_perimeter', index=0, device=None)

Boolean ignition mask [H, W] from the first observed perimeter.

Uses frame index of burn_perimeter by default (the earliest observed fire footprint). Falls back to fireline if present, and finally to a small central seed so a simulation can still run.

Source code in pytorchfire/firedataforge.py
def initial_ignition(self, layer: str = "burn_perimeter", index: int = 0,
                     device=None) -> torch.Tensor:
    """Boolean ignition mask ``[H, W]`` from the first observed perimeter.

    Uses frame ``index`` of ``burn_perimeter`` by default (the earliest
    observed fire footprint). Falls back to ``fireline`` if present, and
    finally to a small central seed so a simulation can still run.
    """
    source = layer if self.has(layer) else ("fireline" if self.has("fireline") else None)
    if source is None:
        mask = np.zeros(self.shape, dtype=bool)
        h, w = self.shape
        mask[h // 2 - 1:h // 2 + 1, w // 2 - 1:w // 2 + 1] = True
    else:
        mask = _as_numpy(self.frame(source, index)).astype(bool)
    return torch.as_tensor(mask, dtype=torch.bool, device=device)

layer(name)

Return the raw layer object for name (a DataLayer-like view).

The returned object exposes .data (list of frames), .timestamps, .unit, .categories, etc. Results are cached per event.

Raises:
  • FileNotFoundError

    If the layer was not produced for this event.

Source code in pytorchfire/firedataforge.py
def layer(self, name: str):
    """Return the raw layer object for ``name`` (a ``DataLayer``-like view).

    The returned object exposes ``.data`` (list of frames), ``.timestamps``,
    ``.unit``, ``.categories``, etc. Results are cached per event.

    Raises:
        FileNotFoundError: If the layer was not produced for this event.
    """
    if name not in self._cache:
        path = os.path.join(self.event_dir, f"{name}.npy")
        if not os.path.exists(path):
            raise FileNotFoundError(
                f"Layer '{name}' not found in {self.event_dir}")
        self._cache[name] = _load_layer(path)
    return self._cache[name]

p_den(layer='canopy_bulk_density', low=-0.2, high=0.4, dtype=torch.float32, device=None)

Vegetation-density factor [H, W] from a continuous fuel layer.

The chosen layer (canopy_bulk_density by default) is mean-centered and then piecewise-rescaled so the grid mean maps to 0, the densest cell to high and the sparsest to low -- the same scheme as p_veg (see _center_scale). Other fuel layers such as lai or canopy_cover can be substituted via layer.

Parameters:
  • layer (str, default: 'canopy_bulk_density' ) –

    Continuous fuel layer to derive p_den from.

  • low (float, default: -0.2 ) –

    Value the sparsest cell maps to.

  • high (float, default: 0.4 ) –

    Value the densest cell maps to.

Source code in pytorchfire/firedataforge.py
def p_den(self, layer: str = "canopy_bulk_density", low: float = -0.2,
          high: float = 0.4, dtype: torch.dtype = torch.float32,
          device=None) -> torch.Tensor:
    """Vegetation-density factor ``[H, W]`` from a continuous fuel layer.

    The chosen ``layer`` (``canopy_bulk_density`` by default) is mean-centered
    and then piecewise-rescaled so the grid **mean** maps to ``0``, the
    densest cell to ``high`` and the sparsest to ``low`` -- the same scheme
    as [`p_veg`][pytorchfire.firedataforge.FireDataForgeEvent.p_veg] (see
    ``_center_scale``). Other fuel
    layers such as ``lai`` or ``canopy_cover`` can be substituted via
    ``layer``.

    Parameters:
        layer (str): Continuous fuel layer to derive ``p_den`` from.
        low (float): Value the sparsest cell maps to.
        high (float): Value the densest cell maps to.
    """
    if not self.has(layer):
        return torch.zeros(self.shape, dtype=dtype, device=device)
    out = _center_scale(self.frame(layer), low, high)
    return torch.as_tensor(out, dtype=dtype, device=device)

p_veg(layer='canopy_cover', low=-0.4, high=0.35, dtype=torch.float32, device=None)

Vegetation-type factor [H, W] from the canopy_cover layer.

Canopy cover (%) is mean-centered and then piecewise-rescaled so the grid mean maps to 0, the densest cell to high and the barest to low (see _center_scale). p_veg enters the model as (1 + p_veg), so cells with above-average fuel propagate faster and below-average cells slower, relative to the event's own mean.

Parameters:
  • layer (str, default: 'canopy_cover' ) –

    Continuous fuel layer to derive p_veg from.

  • low (float, default: -0.4 ) –

    Value the minimum (barest) cell maps to.

  • high (float, default: 0.35 ) –

    Value the maximum (densest) cell maps to.

Source code in pytorchfire/firedataforge.py
def p_veg(self, layer: str = "canopy_cover", low: float = -0.4,
          high: float = 0.35, dtype: torch.dtype = torch.float32,
          device=None) -> torch.Tensor:
    """Vegetation-type factor ``[H, W]`` from the ``canopy_cover`` layer.

    Canopy cover (%) is mean-centered and then piecewise-rescaled so the
    grid **mean** maps to ``0``, the densest cell to ``high`` and the barest
    to ``low`` (see ``_center_scale``).
    ``p_veg`` enters the model as ``(1 + p_veg)``, so cells with
    above-average fuel propagate faster and below-average cells slower,
    relative to the event's own mean.

    Parameters:
        layer (str): Continuous fuel layer to derive ``p_veg`` from.
        low (float): Value the minimum (barest) cell maps to.
        high (float): Value the maximum (densest) cell maps to.
    """
    if not self.has(layer):
        return torch.zeros(self.shape, dtype=dtype, device=device)
    out = _center_scale(self.frame(layer), low, high)
    return torch.as_tensor(out, dtype=dtype, device=device)

slope(dtype=torch.float32, device=None)

Terrain slope tensor [H, W, 3, 3] (degrees) from elevation.

Falls back to an all-zero (flat) slope when no elevation layer is available.

Source code in pytorchfire/firedataforge.py
def slope(self, dtype: torch.dtype = torch.float32, device=None) -> torch.Tensor:
    """Terrain slope tensor ``[H, W, 3, 3]`` (degrees) from ``elevation``.

    Falls back to an all-zero (flat) slope when no ``elevation`` layer is
    available.
    """
    if not self.has("elevation"):
        return torch.zeros((*self.shape, 3, 3), dtype=dtype, device=device)
    altitude = torch.as_tensor(
        self.frame("elevation").astype(np.float32), dtype=dtype, device=device)
    cell_size = torch.tensor(self.resolution, dtype=dtype, device=device)
    return calculate_slope(altitude, cell_size)

target(layer='burn_perimeter', index=-1, device=None)

Observed burned-area target [H, W] (bool) for calibration.

Defaults to the last burn_perimeter frame -- the cumulative final fire footprint.

Source code in pytorchfire/firedataforge.py
def target(self, layer: str = "burn_perimeter", index: int = -1,
           device: Any = None) -> torch.Tensor:
    """Observed burned-area target ``[H, W]`` (bool) for calibration.

    Defaults to the last ``burn_perimeter`` frame -- the cumulative final
    fire footprint.
    """
    mask = _as_numpy(self.frame(layer, index)).astype(bool)
    return torch.as_tensor(mask, dtype=torch.bool, device=device)

target_series(layer='burn_perimeter', device=None)

Stacked observed perimeters [T, H, W] (bool) over time.

Parallel to event.timestamps(layer); each slice is the cumulative burned area observed up to that timestamp.

Source code in pytorchfire/firedataforge.py
def target_series(self, layer: str = "burn_perimeter",
                  device: Any = None) -> torch.Tensor:
    """Stacked observed perimeters ``[T, H, W]`` (bool) over time.

    Parallel to ``event.timestamps(layer)``; each slice is the cumulative
    burned area observed up to that timestamp.
    """
    frames = np.stack([_as_numpy(f).astype(bool) for f in self.layer(layer).data])
    return torch.as_tensor(frames, dtype=torch.bool, device=device)

timestamps(name)

Return the per-frame timestamps of layer name (or None).

Source code in pytorchfire/firedataforge.py
def timestamps(self, name: str) -> Optional[list]:
    """Return the per-frame timestamps of layer ``name`` (or ``None``)."""
    return getattr(self.layer(name), "timestamps", None)

to_env_data(wind_frame=0, dtype=torch.float32, device=None, p_veg_layer='canopy_cover', p_den_layer='canopy_bulk_density')

Assemble the env_data dict accepted by WildfireModel.

Parameters:
  • wind_frame (int, default: 0 ) –

    Which weather frame to freeze into the model's wind fields. Update them later for a time-varying simulation (see apply_wind / clock).

  • dtype (torch.dtype, default: torch.float32 ) –

    Floating dtype for the returned tensors.

  • device (Any, default: None ) –

    Torch device for the tensors. Leave as None (CPU) when you intend to build a model and then .to(device) it, so the model's default scalar parameters stay on the same device.

  • p_veg_layer (str, default: 'canopy_cover' ) –

    Fuel layer used to derive p_veg.

  • p_den_layer (str, default: 'canopy_bulk_density' ) –

    Fuel layer used to derive p_den.

Returns:
  • dict

    A dict with p_veg, p_den, wind_velocity,

  • dict

    wind_towards_direction, slope and initial_ignition.

Examples:

from pytorchfire import WildfireModel
from pytorchfire.firedataforge import load_event

event = load_event('output/CA3432611848120191010')
model = WildfireModel(env_data=event.to_env_data())
Source code in pytorchfire/firedataforge.py
def to_env_data(self, wind_frame: int = 0, dtype: torch.dtype = torch.float32,
                device: Any = None, p_veg_layer: str = "canopy_cover",
                p_den_layer: str = "canopy_bulk_density") -> dict:
    """Assemble the ``env_data`` dict accepted by [`WildfireModel`][pytorchfire.WildfireModel].

    Parameters:
        wind_frame (int): Which weather frame to freeze into the model's
            wind fields. Update them later for a time-varying simulation
            (see [`apply_wind`][pytorchfire.firedataforge.FireDataForgeEvent.apply_wind] /
            [`clock`][pytorchfire.firedataforge.FireDataForgeEvent.clock]).
        dtype (torch.dtype): Floating dtype for the returned tensors.
        device: Torch device for the tensors. Leave as ``None`` (CPU) when
            you intend to build a model and then ``.to(device)`` it, so the
            model's default scalar parameters stay on the same device.
        p_veg_layer (str): Fuel layer used to derive ``p_veg``.
        p_den_layer (str): Fuel layer used to derive ``p_den``.

    Returns:
        A dict with ``p_veg``, ``p_den``, ``wind_velocity``,
        ``wind_towards_direction``, ``slope`` and ``initial_ignition``.

    Examples:
        ```python
        from pytorchfire import WildfireModel
        from pytorchfire.firedataforge import load_event

        event = load_event('output/CA3432611848120191010')
        model = WildfireModel(env_data=event.to_env_data())
        ```
    """
    env = {
        "p_veg": self.p_veg(layer=p_veg_layer, dtype=dtype, device=device),
        "p_den": self.p_den(layer=p_den_layer, dtype=dtype, device=device),
        "slope": self.slope(dtype=dtype, device=device),
        "initial_ignition": self.initial_ignition(device=device),
    }
    env.update(self.wind(frame=wind_frame, dtype=dtype, device=device))
    return env

wind(frame=0, dtype=torch.float32, device=None)

Wind velocity & direction on the grid for one weather frame.

Resamples the coarse HRRR u10/v10 fields to the event grid and converts them with convert_wind_components_to_velocity_and_direction.

Returns:
  • dict

    A dict with wind_velocity (m/s) and wind_towards_direction

  • dict

    (degrees from East, counter-clockwise), each shaped [H, W].

Source code in pytorchfire/firedataforge.py
def wind(self, frame: int = 0, dtype: torch.dtype = torch.float32,
         device=None) -> dict:
    """Wind velocity & direction on the grid for one weather frame.

    Resamples the coarse HRRR ``u10``/``v10`` fields to the event grid and
    converts them with
    [`convert_wind_components_to_velocity_and_direction`][pytorchfire.utils.convert_wind_components_to_velocity_and_direction].

    Returns:
        A dict with ``wind_velocity`` (m/s) and ``wind_towards_direction``
        (degrees from East, counter-clockwise), each shaped ``[H, W]``.
    """
    if not (self.has("u10") and self.has("v10")):
        zero = torch.zeros(self.shape, dtype=dtype, device=device)
        return {"wind_velocity": zero, "wind_towards_direction": zero.clone()}
    u = _resample_to_grid(self.frame("u10", frame), self.shape, dtype, device)
    v = _resample_to_grid(self.frame("v10", frame), self.shape, dtype, device)
    return convert_wind_components_to_velocity_and_direction(u, v)

wind_frame_index(when, layer='u10')

Index of the wind frame nearest a datetime when.

Useful for aligning the hourly weather series with an observed perimeter timestamp, e.g. event.wind(event.wind_frame_index(event.timestamps('burn_perimeter')[3])).

Source code in pytorchfire/firedataforge.py
def wind_frame_index(self, when, layer: str = "u10") -> int:
    """Index of the wind frame nearest a datetime ``when``.

    Useful for aligning the hourly weather series with an observed perimeter
    timestamp, e.g. ``event.wind(event.wind_frame_index(event.timestamps('burn_perimeter')[3]))``.
    """
    stamps = self.timestamps(layer)
    if not stamps:
        return 0
    deltas = [abs((stamp - when).total_seconds()) for stamp in stamps]
    return int(np.argmin(deltas))

load_event(event_dir)

Open a FireDataForge event output directory.

Thin convenience wrapper around FireDataForgeEvent.

Parameters:
  • event_dir (str) –

    Path to an event directory containing <layer>.npy files (e.g. "output/CA3432611848120191010").

Returns:

Examples:

from pytorchfire.firedataforge import load_event

event = load_event('output/CA3432611848120191010')
model = event.build_model()
Source code in pytorchfire/firedataforge.py
def load_event(event_dir: str) -> FireDataForgeEvent:
    """Open a FireDataForge event output directory.

    Thin convenience wrapper around [`FireDataForgeEvent`][pytorchfire.firedataforge.FireDataForgeEvent].

    Parameters:
        event_dir (str): Path to an event directory containing ``<layer>.npy``
            files (e.g. ``"output/CA3432611848120191010"``).

    Returns:
        A [`FireDataForgeEvent`][pytorchfire.firedataforge.FireDataForgeEvent] wrapping the directory.

    Examples:
        ```python
        from pytorchfire.firedataforge import load_event

        event = load_event('output/CA3432611848120191010')
        model = event.build_model()
        ```
    """
    return FireDataForgeEvent(event_dir)