cat > doc/source/api/modules.rst << ‘EOF’ API Reference =============

This page provides the complete API reference for PhenoNN.

Main Package

PhenoNN - Deep learning for phenology prediction using climate data.

This package provides LSTM, GRU, and Transformer models to predict Green Chromatic Coordinate (GCC) or Leaf Area Index (LAI) from climate data.

class phenonn.PhenoCamDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction.

Each sample is a 365-day sliding window of meteorological and site features, with the target being LAI on the final day of the window.

The feature tensor has shape (feature_channels, 365) where feature_channels = len(DYNAMIC_FEATURES) + len(CYCLIC_FEATURES) + len(STATIC_FEATURES) + n_pfts.

Parameters:
  • site_files (list of str) – Paths to site CSV files (e.g. [‘DB_asuhighlands.csv’, …]).

  • norm_stats (dict) – Normalization statistics from compute_norm_stats().

  • seq_length (int) – Window length in days. Default 365.

  • target_col (str) – Target column name. Default ‘LAI’.

  • normalize_target (bool) – Whether to z-score the target. Default False.

  • pft_list (list of str, optional) – Ordered list of all PFT codes for one-hot encoding. If None, auto-discovered from site_files.

  • stride (int) – Step between consecutive windows. Default 1 (every day).

  • years (list of int, optional) – If provided, only use windows whose target day falls in these years. Use this to split “predict year by year”.

Examples

> stats = compute_norm_stats(train_files) > train_ds = PhenoCamDataset(train_files, stats, pft_list=[‘DB’, ‘EN’, ‘GR’]) > features, target = train_ds[0] > features.shape torch.Size([14, 365]) # 7 meteo + 2 cyclic + 2 static + 3 PFT > target.shape torch.Size([1])

__init__(site_files: List[str], norm_stats: Dict[str, Dict[str, float]], seq_length: int = 365, target_col: str = 'LAI', normalize_target: bool = True, pft_list: List[str] | None = None, stride: int = 1, lai_norms=None, years: List[int] | None = None, n_target_days: int = 1, residual_csv: str | None = None, random_stride: int = 0, feature_mode: str = 'all', full_year: bool = False) None[source]
property feature_channels: int

Number of input feature channels.

resample()[source]

Randomly select random_stride samples per site-year.

Call this at the start of each training epoch for fresh random samples. No-op if random_stride was not set.

get_site_info(index: int) Dict[source]

Get metadata for a sample (useful for evaluation / plotting).

Returns dict with keys: site, pft, year, doy_index.

class phenonn.LAIDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction using flat feature and target CSVs.

Each sample is one (site_id, target_year) pair. The feature window covers seq_length consecutive days ending on the last day of the target year. The target is the 36 LAI observations for that year (days 5, 15, 25 of every month).

Feature tensor shape : (n_features, seq_length) — 31 channels by default Target tensor shape : (1, 36)

Parameters:
  • features_csv (str) – Path to flat daily features CSV.

  • target_csv (str) – Path to sparse LAI targets CSV.

  • norm_stats (dict) – Normalization stats from compute_norm_stats().

  • site_ids (list of str, optional) – Restrict to these site IDs. Default: all sites in features_csv.

  • seq_length (int) – Feature window length in days. Default 720 (≈ 2 years).

  • years (list of int, optional) – If provided, only include samples where the target year is in this list.

  • normalize_target (bool) – Z-score the LAI target. Default True.

  • lai_norms (dict, optional) – Per-site {site_id: {“lai_min”: float, “lai_max”: float}} for min-max normalization. Overrides normalize_target when site_id is present.

Examples

>>> stats = compute_norm_stats("features.csv", "targets.csv", train_ids)
>>> ds = LAIDataset("features.csv", "targets.csv", stats, site_ids=train_ids)
>>> feats, targets = ds[0]
>>> feats.shape
torch.Size([31, 720])
>>> targets.shape
torch.Size([1, 36])
__init__(features_csv: str, target_csv: str, norm_stats: Dict[str, Dict[str, float]], site_ids: List[str] | None = None, seq_length: int = 720, years: List[int] | None = None, normalize_target: bool = True, lai_norms: Dict | None = None) None[source]
property feature_channels: int

Number of input feature channels (31 by default).

get_site_info(index: int) Dict[source]

Return metadata for a sample (useful for evaluation / plotting).

phenonn.add_derived_features(df: pandas.DataFrame) pandas.DataFrame[source]

Add GDD, CDD, and Botta onset features to a site dataframe.

Must be called AFTER sorting by (year, doy) and BEFORE normalization. All features reset to zero on January 1st of each year.

Parameters:

df (pd.DataFrame) – Site data with columns: year, doy, tmin, tmax (at minimum).

Returns:

Same dataframe with added columns:

  • gdd_0, gdd_5, gdd_10 : Growing Degree Days at 0/5/10°C thresholds

  • cdd : Chilling Degree Days (cold accumulation below 5°C)

  • ncd : Number of Chilling Days (count of days where Tmean < 5°C)

  • botta_thresholdGDD threshold from Botta et al. (2000)

    G_thres = 964 * exp(-0.0058 * NCD) - 12.8

  • botta_forcingGDD_0 / G_thres (onset proximity indicator)

    ~0 in winter, approaches 1 at onset, >1 during growing season

Return type:

pd.DataFrame

phenonn.split_sites_by_fraction(site_files: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]][source]

Split site files into train and validation sets (leave-site-out).

Parameters:
  • site_files (list of str) – All site CSV paths.

  • val_fraction (float) – Fraction of sites to hold out for validation.

  • seed (int) – Random seed for reproducibility.

Returns:

(train_files, val_files)

Return type:

tuple of (list, list)

phenonn.split_sites_flat(site_ids: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]]

Split site_ids into (train, val) sets (leave-site-out).

Return type:

(train_ids, val_ids)

phenonn.get_site_ids(csv_path: str) List[str][source]

Return sorted list of unique site_ids from a features or targets CSV.

phenonn.extract_pft_and_site(filepath: str) Tuple[str, str][source]

Extract PFT code and site name from a filename like ‘DB_asuhighlands.csv’.

Parameters:

filepath (str) – Path to site CSV file.

Returns:

(pft_code, site_name), e.g. (‘DB’, ‘asuhighlands’).

Return type:

tuple of (str, str)

class phenonn.RNN_LSTM(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

LSTM-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use LSTM cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

Examples

>>> model = RNN_LSTM(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the LSTM model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

class phenonn.RNN_GRU(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

GRU-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use GRU cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

Examples

>>> model = RNN_GRU(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the GRU model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

class phenonn.EncoderTorch(*args: Any, **kwargs: Any)[source]

Bases: Module

__init__(feature_channel: int, output_channel: int, embed_size: int, num_layers: int, heads: int, forward_expansion: int, seq_length: int, dropout: float) None[source]
forward(x: torch.Tensor, mask: torch.Tensor | None = None, src_key_padding_mask: torch.Tensor | None = None) torch.Tensor[source]

x: (batch, feature_channel, seq_length)

class phenonn.CombinedModel(*args: Any, **kwargs: Any)[source]

Bases: Module

Hybrid Transformer-based model for sequence prediction with auxiliary features.

This model combines: - A linear projection of input features into model space - A Transformer module for sequence-to-sequence processing - Additional concatenated features (PFT inputs) - A stack of Transformer encoder layers with causal masking - Final linear projection to output space

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • hidden_dim (int, default=1024) – Feedforward dimension inside Transformer encoder layers.

  • hidden_dim_trans (int, default=1024) – Feedforward dimension inside the built-in Transformer module.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=32) – Internal embedding dimension used throughout the model.

  • nr_blocks (int, default=3) – Number of stacked TransformerEncoderLayer blocks.

lin1

Projects input features into model dimension (d_model).

Type:

torch.nn.Linear

trans

Transformer module applied to the projected input.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single feature.

Type:

torch.nn.Linear

lin3

Projects concatenated features into model dimension.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks.

Type:

torch.nn.ModuleList

lin4

Final projection from model dimension to output space (2).

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module.

Type:

PositionalEncoding

Notes

  • The model uses a causal (lower-triangular) attention mask to prevent attending to future timesteps.

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for Transformer processing.

__init__(input_dim=26, hidden_dim=1024, hidden_dim_trans=1024, output_dim=2, d_model=32, nr_blocks=3)[source]
forward(x, return_stress=False)[source]

Forward pass of the model using full input sequence.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim).

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, 2)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, 2) - stress_tensor: intermediate representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

forward_from_stress(x, pft)[source]

Forward pass starting from intermediate stress representation.

This bypasses the initial Transformer and lin1/lin2 layers, and instead continues processing from a reduced representation combined with auxiliary PFT features.

Parameters:
  • x (torch.Tensor) – Stress-like representation of shape (batch_size, sequence_length, 1).

  • pft (torch.Tensor) – Auxiliary features of shape (batch_size, sequence_length, 10).

Returns:

Output tensor of shape (batch_size, sequence_length, 2).

Return type:

torch.Tensor

class phenonn.BiTransformer(*args: Any, **kwargs: Any)[source]

Bases: Module

Bidirectional Transformer-style sequence model with auxiliary features (PFT).

This model combines: - A linear embedding layer for input features - A Transformer encoder-decoder block for global sequence mixing - A causal (autoregressive) TransformerEncoder stack - Concatenation of auxiliary PFT features - Final projection to output space

The architecture is designed for sequence-to-sequence prediction where past context is enforced via a causal attention mask.

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • feed_forward_trans (int, default=4) – Multiplier for Transformer feedforward dimension.

  • feed_forward_encoder (int, default=4) – Multiplier for encoder feedforward dimension.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=256) – Hidden representation size used throughout the model.

  • nr_blocks (int, default=3) – Number of TransformerEncoderLayer blocks.

  • dropout_trans (float, default=0.1) – Dropout rate used inside the Transformer module.

  • dropout_encoder (float, default=0.1) – Dropout rate used in encoder layers.

  • n_pft (int, default=1) – Number of trailing auxiliary features (PFT) appended to input.

lin1

Projects input features into d_model space.

Type:

torch.nn.Linear

trans

Transformer encoder-decoder module for global sequence mixing.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single-channel representation.

Type:

torch.nn.Linear

lin3

Projects concatenated [stress, PFT] features into d_model space.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks with causal masking.

Type:

torch.nn.ModuleList

lin4

Final projection to output_dim.

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module (defined but not used in forward).

Type:

PositionalEncoding

Notes

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • The last n_pft features are treated as auxiliary inputs and split off.

  • A causal (upper-triangular) mask is applied to prevent future information leakage in the encoder stack.

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for TransformerEncoder processing.

__init__(input_dim=26, feed_forward_trans=4, feed_forward_encoder=4, output_dim=2, d_model=256, nr_blocks=3, dropout_trans=0.1, dropout_encoder=0.1, n_pft=1)[source]
forward(x, return_stress=False)[source]

Forward pass of the BiTransformer model.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim), where the last n_pft channels are auxiliary PFT features.

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, output_dim)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, output_dim) - stress_tensor: intermediate scalar representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

phenonn.run_training()[source]
phenonn.run_training_flat()[source]
phenonn.run_training_big()[source]
phenonn.run_prediction()[source]
phenonn.run_prediction_flat()[source]
phenonn.get_version()[source]

Return the version string.

phenonn.get_versions()[source]

Return a dictionary with version information.

Data Module

PhenoNN Data Module

Provides dataset classes and data processing utilities for phenology prediction: - PhenoCamDataset: Per-site CSV format (original) - LAIDataset: Flat CSV format (features + targets) - Feature engineering: GDD, CDD, Botta onset features

class phenonn.data.PhenoCamDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction.

Each sample is a 365-day sliding window of meteorological and site features, with the target being LAI on the final day of the window.

The feature tensor has shape (feature_channels, 365) where feature_channels = len(DYNAMIC_FEATURES) + len(CYCLIC_FEATURES) + len(STATIC_FEATURES) + n_pfts.

Parameters:
  • site_files (list of str) – Paths to site CSV files (e.g. [‘DB_asuhighlands.csv’, …]).

  • norm_stats (dict) – Normalization statistics from compute_norm_stats().

  • seq_length (int) – Window length in days. Default 365.

  • target_col (str) – Target column name. Default ‘LAI’.

  • normalize_target (bool) – Whether to z-score the target. Default False.

  • pft_list (list of str, optional) – Ordered list of all PFT codes for one-hot encoding. If None, auto-discovered from site_files.

  • stride (int) – Step between consecutive windows. Default 1 (every day).

  • years (list of int, optional) – If provided, only use windows whose target day falls in these years. Use this to split “predict year by year”.

Examples

> stats = compute_norm_stats(train_files) > train_ds = PhenoCamDataset(train_files, stats, pft_list=[‘DB’, ‘EN’, ‘GR’]) > features, target = train_ds[0] > features.shape torch.Size([14, 365]) # 7 meteo + 2 cyclic + 2 static + 3 PFT > target.shape torch.Size([1])

__init__(site_files: List[str], norm_stats: Dict[str, Dict[str, float]], seq_length: int = 365, target_col: str = 'LAI', normalize_target: bool = True, pft_list: List[str] | None = None, stride: int = 1, lai_norms=None, years: List[int] | None = None, n_target_days: int = 1, residual_csv: str | None = None, random_stride: int = 0, feature_mode: str = 'all', full_year: bool = False) None[source]
property feature_channels: int

Number of input feature channels.

resample()[source]

Randomly select random_stride samples per site-year.

Call this at the start of each training epoch for fresh random samples. No-op if random_stride was not set.

get_site_info(index: int) Dict[source]

Get metadata for a sample (useful for evaluation / plotting).

Returns dict with keys: site, pft, year, doy_index.

class phenonn.data.LAIDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction using flat feature and target CSVs.

Each sample is one (site_id, target_year) pair. The feature window covers seq_length consecutive days ending on the last day of the target year. The target is the 36 LAI observations for that year (days 5, 15, 25 of every month).

Feature tensor shape : (n_features, seq_length) — 31 channels by default Target tensor shape : (1, 36)

Parameters:
  • features_csv (str) – Path to flat daily features CSV.

  • target_csv (str) – Path to sparse LAI targets CSV.

  • norm_stats (dict) – Normalization stats from compute_norm_stats().

  • site_ids (list of str, optional) – Restrict to these site IDs. Default: all sites in features_csv.

  • seq_length (int) – Feature window length in days. Default 720 (≈ 2 years).

  • years (list of int, optional) – If provided, only include samples where the target year is in this list.

  • normalize_target (bool) – Z-score the LAI target. Default True.

  • lai_norms (dict, optional) – Per-site {site_id: {“lai_min”: float, “lai_max”: float}} for min-max normalization. Overrides normalize_target when site_id is present.

Examples

>>> stats = compute_norm_stats("features.csv", "targets.csv", train_ids)
>>> ds = LAIDataset("features.csv", "targets.csv", stats, site_ids=train_ids)
>>> feats, targets = ds[0]
>>> feats.shape
torch.Size([31, 720])
>>> targets.shape
torch.Size([1, 36])
__init__(features_csv: str, target_csv: str, norm_stats: Dict[str, Dict[str, float]], site_ids: List[str] | None = None, seq_length: int = 720, years: List[int] | None = None, normalize_target: bool = True, lai_norms: Dict | None = None) None[source]
property feature_channels: int

Number of input feature channels (31 by default).

get_site_info(index: int) Dict[source]

Return metadata for a sample (useful for evaluation / plotting).

phenonn.data.load_site(filepath: str) pandas.DataFrame[source]

Load and clean a single site CSV.

Sorts by (year, doy), adds cyclic day-of-year features, and forward-fills small meteo gaps.

Parameters:

filepath (str) – Path to the CSV file.

Returns:

Cleaned dataframe with all feature and target columns.

Return type:

pd.DataFrame

phenonn.data.extract_pft_and_site(filepath: str) Tuple[str, str][source]

Extract PFT code and site name from a filename like ‘DB_asuhighlands.csv’.

Parameters:

filepath (str) – Path to site CSV file.

Returns:

(pft_code, site_name), e.g. (‘DB’, ‘asuhighlands’).

Return type:

tuple of (str, str)

phenonn.data.get_site_ids(csv_path: str) List[str][source]

Return sorted list of unique site_ids from a features or targets CSV.

phenonn.data.load_lai_norms(norms_csv: str, site_files: List[str] | None = None) Dict[str, Dict[str, float]][source]

Load per-site LAI min, max (from external file). Enables inter-site normalization: lai_norm = (lai - min) / (max - min).

phenonn.data.split_sites_by_fraction(site_files: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]][source]

Split site files into train and validation sets (leave-site-out).

Parameters:
  • site_files (list of str) – All site CSV paths.

  • val_fraction (float) – Fraction of sites to hold out for validation.

  • seed (int) – Random seed for reproducibility.

Returns:

(train_files, val_files)

Return type:

tuple of (list, list)

phenonn.data.split_sites_flat(site_ids: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]]

Split site_ids into (train, val) sets (leave-site-out).

Return type:

(train_ids, val_ids)

phenonn.data.add_derived_features(df: pandas.DataFrame) pandas.DataFrame[source]

Add GDD, CDD, and Botta onset features to a site dataframe.

Must be called AFTER sorting by (year, doy) and BEFORE normalization. All features reset to zero on January 1st of each year.

Parameters:

df (pd.DataFrame) – Site data with columns: year, doy, tmin, tmax (at minimum).

Returns:

Same dataframe with added columns:

  • gdd_0, gdd_5, gdd_10 : Growing Degree Days at 0/5/10°C thresholds

  • cdd : Chilling Degree Days (cold accumulation below 5°C)

  • ncd : Number of Chilling Days (count of days where Tmean < 5°C)

  • botta_thresholdGDD threshold from Botta et al. (2000)

    G_thres = 964 * exp(-0.0058 * NCD) - 12.8

  • botta_forcingGDD_0 / G_thres (onset proximity indicator)

    ~0 in winter, approaches 1 at onset, >1 during growing season

Return type:

pd.DataFrame

class phenonn.data.BigLAIDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

Streaming, per-epoch dataset for LAI prediction.

Parameters:
  • features_dir (str) – Folder containing features_{year}.csv files.

  • target_dir (str) – Folder containing target_{year}.csv files.

  • years (list of int) – Target years for which (site, year) samples will be produced. For each Y in this list, the dataset also loads the previous year’s feature file (features_{Y-1}.csv) so the seq_length-day window is complete.

  • site_ids (list of str) – Sites to keep. Ids that do not appear in the relevant year CSVs are silently dropped — pass the full candidate list and let the loader filter.

  • seq_length (int) – Feature window length in days (default 720 ≈ 2 years).

  • normalize (bool) – Must be False for now. Reserved for a future opt-in normalization.

Notes

All required data is loaded into memory at construction time. With ~500 sites and ~3 years per epoch this stays in the few-hundred-MB range. The expectation is that main_big.py creates a fresh dataset every epoch.

__init__(features_dir: str, target_dir: str, years: List[int], site_ids: List[str], seq_length: int = 720, normalize: bool = False) None[source]
property feature_channels: int
get_site_info(index: int) Dict[source]
phenonn.data.generate_site_ids_from_range(row_range: Tuple[int, int], col_range: Tuple[int, int]) List[str][source]

Build every pix_{row:04d}_{col:05d} id in the given inclusive grid range.

The caller can pass this list directly to BigLAIDataset; ids that do not appear in the CSV files are silently dropped at load time, so the range can be a superset of the actually-extracted sites.

phenonn.data.get_pixel_index(csv_path: str, verbose: bool = True) Tuple[numpy.ndarray, numpy.ndarray, bytes][source]

Return (site_ids, offsets, header), building & caching the index on first call. Subsequent calls hit the on-disk cache instantly.

LAI Prediction Dataset.

PyTorch dataset utilities for predicting Leaf Area Index (LAI) from daily meteorological, site, and vegetation characteristics derived from PhenoCam observations.

The module provides functionality to:

  • Load and preprocess site-level time series data.

  • Engineer derived phenological predictors such as growing degree days (GDD), chilling degree days (CDD), forcing metrics, and related indices.

  • Compute and apply feature normalization statistics.

  • Optionally normalize LAI targets globally or using site-specific minimum/maximum values.

  • Construct sliding-window datasets for supervised learning.

  • Support multiple feature configurations, including meteorological-only, site-only, or combined models.

  • Generate datasets for single-day prediction, multi-day sequence prediction, full-year forecasting, or residual learning workflows.

  • Create train/validation splits by site and/or year.

Dataset Structure

Input samples consist of a fixed-length sequence of daily observations ending on a target day.

Typical configuration:

  • Input:

    (n_features, seq_length) where seq_length is typically 365 days.

  • Output:

    (1,) LAI value on the final day of the sequence.

Alternative modes support:

  • Multi-day targets:

    (1, n_target_days)

  • Full-year prediction:

    (1, 365)

Feature Groups

Dynamic features

Daily meteorological variables and derived phenological metrics, including temperature, precipitation, radiation, snow water equivalent, vapor pressure deficit, and growing degree day indices.

Static features

Site-level attributes such as climatology, geographic coordinates, soil properties, elevation, and terrain characteristics.

Cyclic features

Sine/cosine encodings of day-of-year.

PFT features

One-hot encoding of plant functional type (PFT).

Normalization

Feature normalization statistics are computed from the training sites. Dynamic variables can optionally be log-transformed prior to computing means and standard deviations. Static variables are normalized using cross-site statistics.

Targets may be normalized either:

  • Globally using dataset-wide LAI mean and standard deviation.

  • Per site using externally supplied LAI minimum and maximum values.

Main Components

compute_norm_stats

Compute feature normalization statistics from training sites.

load_norm_stats

Load previously computed normalization statistics.

load_site

Load and preprocess a site-level CSV file.

PhenoCamDataset

PyTorch Dataset implementation providing sliding-window LAI prediction samples.

split_sites_by_fraction

Create leave-site-out train/validation splits.

split_by_year

Create train/validation datasets using year-based target splits.

Notes

This implementation is adapted from the RTnn data preprocessing workflow for daily PhenoCam time series and is designed for ecological forecasting and vegetation phenology modeling.

phenonn.data.dataset.extract_pft_and_site(filepath: str) Tuple[str, str][source]

Extract PFT code and site name from a filename like ‘DB_asuhighlands.csv’.

Parameters:

filepath (str) – Path to site CSV file.

Returns:

(pft_code, site_name), e.g. (‘DB’, ‘asuhighlands’).

Return type:

tuple of (str, str)

phenonn.data.dataset.load_site(filepath: str) pandas.DataFrame[source]

Load and clean a single site CSV.

Sorts by (year, doy), adds cyclic day-of-year features, and forward-fills small meteo gaps.

Parameters:

filepath (str) – Path to the CSV file.

Returns:

Cleaned dataframe with all feature and target columns.

Return type:

pd.DataFrame

phenonn.data.dataset.load_lai_norms(norms_csv: str, site_files: List[str] | None = None) Dict[str, Dict[str, float]][source]

Load per-site LAI min, max (from external file). Enables inter-site normalization: lai_norm = (lai - min) / (max - min).

phenonn.data.dataset.compute_norm_stats(site_files: List[str], save_path: str | None = None) Dict[str, Dict[str, float]][source]

Compute per-feature mean and std across all training sites.

For features in LOG_TRANSFORM_FEATURES, stats are computed on log1p(x). For static features (mat, map), stats are computed per-site (one value per site) to avoid inflating variance with repeated rows.

Parameters:
  • site_files (list of str) – Paths to training site CSVs.

  • save_path (str, optional) – If provided, save stats as JSON to this path.

Returns:

{feature_name: {“mean”: float, “std”: float}} for all features.

Return type:

dict

phenonn.data.dataset.load_norm_stats(path: str) Dict[str, Dict[str, float]][source]

Load normalization stats from a JSON file.

class phenonn.data.dataset.PhenoCamDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction.

Each sample is a 365-day sliding window of meteorological and site features, with the target being LAI on the final day of the window.

The feature tensor has shape (feature_channels, 365) where feature_channels = len(DYNAMIC_FEATURES) + len(CYCLIC_FEATURES) + len(STATIC_FEATURES) + n_pfts.

Parameters:
  • site_files (list of str) – Paths to site CSV files (e.g. [‘DB_asuhighlands.csv’, …]).

  • norm_stats (dict) – Normalization statistics from compute_norm_stats().

  • seq_length (int) – Window length in days. Default 365.

  • target_col (str) – Target column name. Default ‘LAI’.

  • normalize_target (bool) – Whether to z-score the target. Default False.

  • pft_list (list of str, optional) – Ordered list of all PFT codes for one-hot encoding. If None, auto-discovered from site_files.

  • stride (int) – Step between consecutive windows. Default 1 (every day).

  • years (list of int, optional) – If provided, only use windows whose target day falls in these years. Use this to split “predict year by year”.

Examples

> stats = compute_norm_stats(train_files) > train_ds = PhenoCamDataset(train_files, stats, pft_list=[‘DB’, ‘EN’, ‘GR’]) > features, target = train_ds[0] > features.shape torch.Size([14, 365]) # 7 meteo + 2 cyclic + 2 static + 3 PFT > target.shape torch.Size([1])

property feature_channels: int

Number of input feature channels.

__len__() int[source]
__getitem__(index: int) Tuple[torch.Tensor, torch.Tensor][source]

Get a single sample.

Parameters:

index (int) – Sample index.

Returns:

  • features (torch.Tensor) – Feature tensor of shape (feature_channels, seq_length)

  • target (torch.Tensor) – Target tensor. Shape is (1,) if n_target_days=1, or (1, n_target_days) if n_target_days>1

resample()[source]

Randomly select random_stride samples per site-year.

Call this at the start of each training epoch for fresh random samples. No-op if random_stride was not set.

get_site_info(index: int) Dict[source]

Get metadata for a sample (useful for evaluation / plotting).

Returns dict with keys: site, pft, year, doy_index.

phenonn.data.dataset.split_sites_by_fraction(site_files: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]][source]

Split site files into train and validation sets (leave-site-out).

Parameters:
  • site_files (list of str) – All site CSV paths.

  • val_fraction (float) – Fraction of sites to hold out for validation.

  • seed (int) – Random seed for reproducibility.

Returns:

(train_files, val_files)

Return type:

tuple of (list, list)

phenonn.data.dataset.split_by_year(site_files: List[str], norm_stats: Dict, train_years: List[int], val_years: List[int], pft_list: List[str], **kwargs) Tuple[PhenoCamDataset, PhenoCamDataset][source]

Create train and validation datasets split by target year.

All sites are used for both sets, but training windows target train_years and validation windows target val_years.

Parameters:
  • site_files (list of str) – All site CSV paths.

  • norm_stats (dict) – Normalization statistics.

  • train_years (list of int) – Years to include in training targets.

  • val_years (list of int) – Years to include in validation targets.

  • pft_list (list of str) – Ordered PFT codes.

  • **kwargs – Additional arguments passed to PhenoCamDataset.

Return type:

tuple of (PhenoCamDataset, PhenoCamDataset)

phenonn.data.dataset.split_by_sites_years(site_files: List[str], norm_stats: Dict, train_years: List[int], val_years: List[int], pft_list: List[str], val_fraction: float = 0.1, seed: int = 42, **kwargs) Tuple[PhenoCamDataset, PhenoCamDataset][source]

Create train and validation datasets split by both sites and years.

Validation set is composed of all years for val_fraction of sites (randomly selected) and of the years in val_years for the remaining sites. The training set is composed of the remaining years for the remaining sites.

Parameters:
  • site_files (list of str) – All site CSV paths.

  • norm_stats (dict) – Normalization statistics.

  • train_years (list of int) – Years to include in training targets.

  • val_years (list of int) – Years to include in validation targets.

  • pft_list (list of str) – Ordered PFT codes.

  • val_fraction (float) – Fraction of sites to hold out for validation.

  • seed (int) – Random seed for reproducibility.

LAI Prediction Dataset.

This dataset reads from two flat CSV files that replace the per-site CSVs used in dataset.py.

Files

features.csv

One row per (site_id, date) with 365 records per site-year.

Columns:

site_id
date (YYYYMMDD)
year
month
day
pft1_frac ... pft15_frac
tmin
tmax
daylength
prcp
srad
vpd
swe
targets.csv

One row per (site_id, date) with 36 observations per site-year.

Observation days are the 5th, 15th, and 25th of each month.

Columns:

site_id
date
year
month
day
LAI_raw
LAI

Notes

Each sample corresponds to a single (site_id, year) pair.

The returned tensors have the following shapes:

  • features: (n_features, seq_length)

    Sequence of trailing daily feature values.

  • targets: (1, 36)

    LAI observations for the corresponding year.

phenonn.data.dataset_flat.get_site_ids(csv_path: str) List[str][source]

Return sorted list of unique site_ids from a features or targets CSV.

phenonn.data.dataset_flat.split_sites_by_fraction(site_ids: List[str], val_fraction: float = 0.2, seed: int = 42) Tuple[List[str], List[str]][source]

Split site_ids into (train, val) sets (leave-site-out).

Return type:

(train_ids, val_ids)

phenonn.data.dataset_flat.compute_norm_stats(features_csv: str, target_csv: str, train_site_ids: List[str], save_path: str | None = None) Dict[str, Dict[str, float]][source]

Compute per-feature mean and std from training sites only.

Features in LOG_TRANSFORM_FEATURES are log1p-transformed before computing stats. PFT fraction columns are included.

Parameters:
  • features_csv (str) – Path to flat daily features CSV.

  • target_csv (str) – Path to sparse LAI targets CSV.

  • train_site_ids (list of str) – Site IDs belonging to the training split.

  • save_path (str, optional) – If provided, save stats as JSON to this path.

Returns:

{feature_name: {“mean”: float, “std”: float}} for all 31 features + “LAI”.

Return type:

dict

phenonn.data.dataset_flat.load_norm_stats(path: str) Dict[str, Dict[str, float]][source]

Load normalization stats from a JSON file.

class phenonn.data.dataset_flat.LAIDataset(*args: Any, **kwargs: Any)[source]

Bases: Dataset

PyTorch dataset for LAI prediction using flat feature and target CSVs.

Each sample is one (site_id, target_year) pair. The feature window covers seq_length consecutive days ending on the last day of the target year. The target is the 36 LAI observations for that year (days 5, 15, 25 of every month).

Feature tensor shape : (n_features, seq_length) — 31 channels by default Target tensor shape : (1, 36)

Parameters:
  • features_csv (str) – Path to flat daily features CSV.

  • target_csv (str) – Path to sparse LAI targets CSV.

  • norm_stats (dict) – Normalization stats from compute_norm_stats().

  • site_ids (list of str, optional) – Restrict to these site IDs. Default: all sites in features_csv.

  • seq_length (int) – Feature window length in days. Default 720 (≈ 2 years).

  • years (list of int, optional) – If provided, only include samples where the target year is in this list.

  • normalize_target (bool) – Z-score the LAI target. Default True.

  • lai_norms (dict, optional) – Per-site {site_id: {“lai_min”: float, “lai_max”: float}} for min-max normalization. Overrides normalize_target when site_id is present.

Examples

>>> stats = compute_norm_stats("features.csv", "targets.csv", train_ids)
>>> ds = LAIDataset("features.csv", "targets.csv", stats, site_ids=train_ids)
>>> feats, targets = ds[0]
>>> feats.shape
torch.Size([31, 720])
>>> targets.shape
torch.Size([1, 36])
property feature_channels: int

Number of input feature channels (31 by default).

__len__() int[source]
__getitem__(index: int) Tuple[torch.Tensor, torch.Tensor][source]
Returns:

  • features ((n_features, seq_length))

  • targets ((1, 36))

get_site_info(index: int) Dict[source]

Return metadata for a sample (useful for evaluation / plotting).

Feature engineering utilities for phenological modeling.

This module computes ecologically meaningful predictors derived from daily meteorological observations. The generated features are designed to capture temperature-driven vegetation dynamics, including heat accumulation, winter chilling, and spring onset forcing.

Derived variables are computed independently for each calendar year and are intended to be added to site-level meteorological records after temporal sorting and before any normalization or scaling.

Implemented Features

Growing Degree Days (GDD)

Cumulative heat accumulation above one or more base temperature thresholds. Multiple thresholds are provided (0, 5, and 10 °C) to allow downstream models to learn the most informative representation of thermal forcing.

Chilling Degree Days (CDD)

Cumulative cold exposure below a chilling threshold of 5 °C. This metric approximates winter dormancy release processes in temperate ecosystems.

Number of Chilling Days (NCD)

Running count of days with mean temperature below 5 °C. Unlike CDD, this measure captures the frequency rather than the magnitude of cold exposure.

Botta Onset Features

Phenological forcing metrics derived from the formulation proposed by Botta et al. (2000). The model assumes that accumulated chilling lowers the growing degree day requirement for spring onset.

Threshold:

G_thres = C1 * exp(C2 * NCD) + C3

Forcing Ratio:

GDD / G_thres

Values approaching 1 indicate proximity to the predicted onset of vegetation activity.

Notes

  • Input data must be sorted by (year, doy) before feature computation.

  • All cumulative variables reset on January 1 of each year.

  • Missing temperatures are forward/backward filled before accumulation to ensure stable cumulative calculations.

  • Feature generation is deterministic and does not modify the input dataframe in place.

References

Botta, A., Viovy, N., Ciais, P., Friedlingstein, P., & Monfray, P. (2000). A global prognostic scheme of leaf onset using satellite data. Global Change Biology, 6(7), 709–725.

phenonn.data.feature_engineering.add_derived_features(df: pandas.DataFrame) pandas.DataFrame[source]

Add GDD, CDD, and Botta onset features to a site dataframe.

Must be called AFTER sorting by (year, doy) and BEFORE normalization. All features reset to zero on January 1st of each year.

Parameters:

df (pd.DataFrame) – Site data with columns: year, doy, tmin, tmax (at minimum).

Returns:

Same dataframe with added columns:

  • gdd_0, gdd_5, gdd_10 : Growing Degree Days at 0/5/10°C thresholds

  • cdd : Chilling Degree Days (cold accumulation below 5°C)

  • ncd : Number of Chilling Days (count of days where Tmean < 5°C)

  • botta_thresholdGDD threshold from Botta et al. (2000)

    G_thres = 964 * exp(-0.0058 * NCD) - 12.8

  • botta_forcingGDD_0 / G_thres (onset proximity indicator)

    ~0 in winter, approaches 1 at onset, >1 during growing season

Return type:

pd.DataFrame

Models Module

Model architectures for RTnn.

class phenonn.models.RNN_LSTM(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

LSTM-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use LSTM cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

Examples

>>> model = RNN_LSTM(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the LSTM model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

class phenonn.models.RNN_GRU(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

GRU-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use GRU cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

Examples

>>> model = RNN_GRU(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the GRU model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

class phenonn.models.FCN(*args: Any, **kwargs: Any)[source]

Bases: Module

Fully Connected Network with configurable depth and width.

This model flattens the input sequence and processes it through a series of fully connected layers. It can optionally expand the sequence length using a linear transformation.

Parameters:
  • feature_channel (int) – Number of input features per time step.

  • output_channel (int) – Number of output channels.

  • num_layers (int) – Number of hidden layers.

  • hidden_size (int) – Size of hidden layers.

  • seq_length (int, optional) – Length of the input sequence. Default is 55.

  • dim_expand (int, optional) – Number of time steps to expand the output sequence by. Default is 0 (no expansion).

feature_channel

Number of input features.

Type:

int

output_channel

Number of output channels.

Type:

int

seq_length

Length of the input sequence.

Type:

int

dim_expand

Number of time steps to expand by.

Type:

int

input_layer

First fully connected layer.

Type:

FCBlock

hidden_layers

Stack of hidden layers.

Type:

nn.Sequential

output_layer

Final output layer.

Type:

nn.Linear

dim_change

Optional layer for sequence length expansion.

Type:

nn.Linear or None

Examples

>>> model = FCN(
...     feature_channel=6,
...     output_channel=4,
...     num_layers=3,
...     hidden_size=196,
...     seq_length=10
... )
>>> x = torch.randn(32, 6, 10)
>>> y = model(x)
>>> y.shape
torch.Size([32, 4, 10])
__init__(feature_channel: int, output_channel: int, num_layers: int, hidden_size: int, seq_length: int = 55, dim_expand: int = 0) None[source]

Initialize the FCN model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • num_layers (int) – Number of hidden layers.

  • hidden_size (int) – Size of hidden layers.

  • seq_length (int, optional) – Length of the input sequence. Default is 55.

  • dim_expand (int, optional) – Number of time steps to expand the output sequence by. Default is 0 (no expansion).

Raises:

ValueError – If num_layers is less than 1.

forward(x: torch.Tensor) torch.Tensor[source]

Forward pass through the FCN.

Parameters:

x (torch.Tensor) – Input tensor of shape (batch_size, feature_channel, seq_length).

Returns:

Output tensor of shape (batch_size, output_channel, seq_length + dim_expand) if dim_expand > 0, otherwise (batch_size, output_channel, seq_length).

Return type:

torch.Tensor

Notes

The forward pass: 1. Flattens the input to (batch_size, feature_channel * seq_length) 2. Passes through FCBlocks 3. Projects to output dimensions 4. Reshapes to (batch_size, output_channel, seq_length) 5. Optionally expands sequence length

class phenonn.models.EncoderTorch(*args: Any, **kwargs: Any)[source]

Bases: Module

__init__(feature_channel: int, output_channel: int, embed_size: int, num_layers: int, heads: int, forward_expansion: int, seq_length: int, dropout: float) None[source]
forward(x: torch.Tensor, mask: torch.Tensor | None = None, src_key_padding_mask: torch.Tensor | None = None) torch.Tensor[source]

x: (batch, feature_channel, seq_length)

class phenonn.models.CombinedModel(*args: Any, **kwargs: Any)[source]

Bases: Module

Hybrid Transformer-based model for sequence prediction with auxiliary features.

This model combines: - A linear projection of input features into model space - A Transformer module for sequence-to-sequence processing - Additional concatenated features (PFT inputs) - A stack of Transformer encoder layers with causal masking - Final linear projection to output space

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • hidden_dim (int, default=1024) – Feedforward dimension inside Transformer encoder layers.

  • hidden_dim_trans (int, default=1024) – Feedforward dimension inside the built-in Transformer module.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=32) – Internal embedding dimension used throughout the model.

  • nr_blocks (int, default=3) – Number of stacked TransformerEncoderLayer blocks.

lin1

Projects input features into model dimension (d_model).

Type:

torch.nn.Linear

trans

Transformer module applied to the projected input.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single feature.

Type:

torch.nn.Linear

lin3

Projects concatenated features into model dimension.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks.

Type:

torch.nn.ModuleList

lin4

Final projection from model dimension to output space (2).

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module.

Type:

PositionalEncoding

Notes

  • The model uses a causal (lower-triangular) attention mask to prevent attending to future timesteps.

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for Transformer processing.

__init__(input_dim=26, hidden_dim=1024, hidden_dim_trans=1024, output_dim=2, d_model=32, nr_blocks=3)[source]
forward(x, return_stress=False)[source]

Forward pass of the model using full input sequence.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim).

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, 2)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, 2) - stress_tensor: intermediate representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

forward_from_stress(x, pft)[source]

Forward pass starting from intermediate stress representation.

This bypasses the initial Transformer and lin1/lin2 layers, and instead continues processing from a reduced representation combined with auxiliary PFT features.

Parameters:
  • x (torch.Tensor) – Stress-like representation of shape (batch_size, sequence_length, 1).

  • pft (torch.Tensor) – Auxiliary features of shape (batch_size, sequence_length, 10).

Returns:

Output tensor of shape (batch_size, sequence_length, 2).

Return type:

torch.Tensor

class phenonn.models.BiTransformer(*args: Any, **kwargs: Any)[source]

Bases: Module

Bidirectional Transformer-style sequence model with auxiliary features (PFT).

This model combines: - A linear embedding layer for input features - A Transformer encoder-decoder block for global sequence mixing - A causal (autoregressive) TransformerEncoder stack - Concatenation of auxiliary PFT features - Final projection to output space

The architecture is designed for sequence-to-sequence prediction where past context is enforced via a causal attention mask.

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • feed_forward_trans (int, default=4) – Multiplier for Transformer feedforward dimension.

  • feed_forward_encoder (int, default=4) – Multiplier for encoder feedforward dimension.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=256) – Hidden representation size used throughout the model.

  • nr_blocks (int, default=3) – Number of TransformerEncoderLayer blocks.

  • dropout_trans (float, default=0.1) – Dropout rate used inside the Transformer module.

  • dropout_encoder (float, default=0.1) – Dropout rate used in encoder layers.

  • n_pft (int, default=1) – Number of trailing auxiliary features (PFT) appended to input.

lin1

Projects input features into d_model space.

Type:

torch.nn.Linear

trans

Transformer encoder-decoder module for global sequence mixing.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single-channel representation.

Type:

torch.nn.Linear

lin3

Projects concatenated [stress, PFT] features into d_model space.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks with causal masking.

Type:

torch.nn.ModuleList

lin4

Final projection to output_dim.

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module (defined but not used in forward).

Type:

PositionalEncoding

Notes

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • The last n_pft features are treated as auxiliary inputs and split off.

  • A causal (upper-triangular) mask is applied to prevent future information leakage in the encoder stack.

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for TransformerEncoder processing.

__init__(input_dim=26, feed_forward_trans=4, feed_forward_encoder=4, output_dim=2, d_model=256, nr_blocks=3, dropout_trans=0.1, dropout_encoder=0.1, n_pft=1)[source]
forward(x, return_stress=False)[source]

Forward pass of the BiTransformer model.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim), where the last n_pft channels are auxiliary PFT features.

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, output_dim)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, output_dim) - stress_tensor: intermediate scalar representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

class phenonn.models.LinearBaseline(*args: Any, **kwargs: Any)[source]

Bases: Module

Full-window linear regression baseline.

Flattens the entire (feature_channel × seq_length) input into one vector, applies a single Linear layer to produce a scalar prediction.

This is mathematically equivalent to:

gcc_pred = w @ flatten(input) + b

where w has C×L weights. It can learn things like “temperature on day 300 matters more than temperature on day 50” because each (channel, timestep) pair gets its own weight.

Parameters:
  • feature_channel (int) – Number of input feature channels.

  • seq_length (int) – Window length in days (365).

Examples

>>> model = LinearBaseline(feature_channel=16, seq_length=365)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(feature_channel: int, seq_length: int = 365)[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channel, seq_length))

Return type:

(batch, 1)

class phenonn.models.PerDayLinearBaseline(*args: Any, **kwargs: Any)[source]

Bases: Module

Per-day linear regression baseline (no temporal context).

Applies the same Linear(C, 1) to every timestep independently, then returns the prediction for the last day. This tests whether today’s feature values alone (without any history) can predict today’s LAI.

If this baseline scores well, it means the temporal context from the 365-day window isn’t adding much — the model is mostly using the current day’s meteorology.

Parameters:

feature_channel (int) – Number of input feature channels.

Examples

>>> model = PerDayLinearBaseline(feature_channel=16)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(feature_channel: int)[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channel, seq_length))

Return type:

(batch, 1)

Bidirectional recurrent neural network models for sequence modeling.

This module provides implementations of bidirectional recurrent neural networks (RNNs) using Long Short-Term Memory (LSTM) and Gated Recurrent Unit (GRU) cells. These models are designed for sequence-based data, such as time series or vertically structured physical profiles.

The module includes:

  • BaseRNN: A flexible base class supporting both LSTM and GRU architectures with bidirectional processing.

  • RNN_LSTM: A specialized LSTM-based model built on BaseRNN.

  • RNN_GRU: A specialized GRU-based model built on BaseRNN.

Features

  • Bidirectional sequence processing for improved context awareness

  • Support for stacked recurrent layers

  • Unified interface for LSTM and GRU architectures

  • Automatic hidden state initialization

  • Final 1D convolution layer for channel-wise output projection

  • Compatible with batched inputs and GPU acceleration

Notes

  • Inputs are expected in the shape (batch_size, feature_channel, seq_length).

  • Internally, inputs are permuted to (batch_size, seq_length, feature_channel) to match PyTorch RNN requirements.

  • Bidirectional RNNs double the hidden state size, which is handled automatically in the final projection layer.

  • Hidden states are initialized to zeros at each forward pass.

  • The final Conv1d layer maps hidden representations to the desired output channels while preserving sequence length.

Dependencies

  • torch

  • torch.nn

  • typing

Examples

Using LSTM-based model:

>>> model = RNN_LSTM(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)

Using GRU-based model:

>>> model = RNN_GRU(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)

Using BaseRNN directly:

>>> model = BaseRNN(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=64,
...     num_layers=2,
...     rnn_type="lstm"
... )
class phenonn.models.rnn.BaseRNN(*args: Any, **kwargs: Any)[source]

Bases: Module

Base class for bidirectional RNN modules (LSTM/GRU).

This class provides a common interface for both LSTM and GRU models with bidirectional processing and a final 1D convolutional layer to map the hidden states to the desired output channels.

Parameters:
  • feature_channel (int) – Number of input features per time step.

  • output_channel (int) – Number of output channels (target variables).

  • hidden_size (int) – Number of hidden units in the RNN layers.

  • num_layers (int) – Number of stacked RNN layers.

  • rnn_type (str) – Type of RNN cell, either ‘lstm’ or ‘gru’.

rnn

The bidirectional RNN layer.

Type:

nn.LSTM or nn.GRU

final

Final 1D convolution to project hidden states to output channels.

Type:

nn.Conv1d

hidden_size

Number of hidden units.

Type:

int

num_layers

Number of stacked layers.

Type:

int

output_channel

Number of output channels.

Type:

int

Examples

>>> model = BaseRNN(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=64,
...     num_layers=2,
...     rnn_type='lstm'
... )
>>> x = torch.randn(32, 6, 10)  # (batch, features, sequence)
>>> y = model(x)
>>> y.shape
torch.Size([32, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int, rnn_type: str) None[source]

Initialize the BaseRNN module.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of RNN layers.

  • rnn_type (str) – Type of RNN (‘lstm’ or ‘gru’).

Raises:

ValueError – If rnn_type is not ‘lstm’ or ‘gru’.

init_hidden(batch_size: int, device: torch.device) torch.Tensor | Tuple[torch.Tensor, torch.Tensor][source]

Initialize the hidden state for the RNN.

Parameters:
  • batch_size (int) – Batch size for the input.

  • device (torch.device) – Device to create the hidden state on.

Returns:

For GRU: returns hidden state tensor of shape (2 * num_layers, batch_size, hidden_size) For LSTM: returns tuple (hidden, cell) both of same shape.

Return type:

torch.Tensor or tuple of torch.Tensor

forward(x: torch.Tensor) torch.Tensor[source]

Forward pass through the bidirectional RNN.

Parameters:

x (torch.Tensor) – Input tensor of shape (batch_size, feature_channel, seq_length).

Returns:

Output tensor of shape (batch_size, output_channel, seq_length).

Return type:

torch.Tensor

Notes

The input is permuted to (batch_size, seq_length, feature_channel) for the RNN, then the output is permuted back for the convolution.

class phenonn.models.rnn.RNN_LSTM(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

LSTM-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use LSTM cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

Examples

>>> model = RNN_LSTM(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the LSTM model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of LSTM layers.

class phenonn.models.rnn.RNN_GRU(*args: Any, **kwargs: Any)[source]

Bases: BaseRNN

GRU-based bidirectional RNN model.

This class inherits from BaseRNN and configures it to use GRU cells.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

Examples

>>> model = RNN_GRU(
...     feature_channel=6,
...     output_channel=4,
...     hidden_size=128,
...     num_layers=3
... )
>>> x = torch.randn(16, 6, 10)
>>> y = model(x)
>>> print(y.shape)
torch.Size([16, 4, 10])
__init__(feature_channel: int, output_channel: int, hidden_size: int, num_layers: int) None[source]

Initialize the GRU model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • hidden_size (int) – Size of hidden state.

  • num_layers (int) – Number of GRU layers.

Transformer-based encoder model for sequence modeling.

This module implements a Transformer encoder architecture using PyTorch’s native nn.TransformerEncoder components. It is designed for processing structured sequence data, such as time series or vertical profiles, where contextual relationships across positions are important.

The model projects input features into an embedding space, adds learnable positional encodings, and processes the sequence through stacked self-attention layers before projecting to the desired output channels.

Features

  • Learnable input projection to embedding space

  • Learnable positional embeddings for sequence order awareness

  • Multi-head self-attention via Transformer encoder layers

  • Configurable depth, attention heads, and feedforward expansion

  • Dropout for regularization

  • Final 1D convolution for channel-wise output projection

  • Support for attention masks and padding masks

Notes

  • Inputs are expected in the shape (batch_size, feature_channel, seq_length).

  • Internally, inputs are permuted to (batch_size, seq_length, feature_channel) to match Transformer expectations.

  • Positional embeddings are added to the projected input features.

  • The mask argument is used for attention masking (e.g., causal masking).

  • The src_key_padding_mask is used to ignore padded positions in sequences.

  • The final output preserves the sequence length and maps embeddings to output_channel dimensions.

Dependencies

  • torch

  • torch.nn

  • typing

Examples

Basic usage:

>>> model = EncoderTorch(
...     feature_channel=6,
...     output_channel=4,
...     embed_size=128,
...     num_layers=3,
...     heads=4,
...     forward_expansion=4,
...     seq_length=10,
...     dropout=0.1
... )
>>> x = torch.randn(32, 6, 10)
>>> y = model(x)
>>> y.shape
torch.Size([32, 4, 10])

Using attention masks:

>>> mask = torch.triu(torch.ones(10, 10), diagonal=1).bool()
>>> y = model(x, mask=mask)
class phenonn.models.transformer.EncoderTorch(*args: Any, **kwargs: Any)[source]

Bases: Module

__init__(feature_channel: int, output_channel: int, embed_size: int, num_layers: int, heads: int, forward_expansion: int, seq_length: int, dropout: float) None[source]
forward(x: torch.Tensor, mask: torch.Tensor | None = None, src_key_padding_mask: torch.Tensor | None = None) torch.Tensor[source]

x: (batch, feature_channel, seq_length)

Neural network architectures for phenological and vegetation dynamics modeling.

This module contains a collection of recurrent, transformer-based, and feed-forward neural network models originally developed for vegetation greenness prediction from meteorological and environmental forcing data. The architectures range from simple baselines to multi-stage transformer models that explicitly separate environmental stress estimation from phenological state prediction.

The implementations are adapted from work by Christian Reimers and collaborators at the Max Planck Institute for Biogeochemistry and are used within the PhenoCam LAI modeling framework.

Implemented Models

CombinedModel

Two-stage architecture that first estimates an intermediate environmental stress signal using a transformer module and then combines this signal with plant functional type (PFT) information before phenological prediction.

BiTransformer

Improved bidirectional transformer architecture that combines environmental stress estimation with causal temporal encoding and configurable plant functional type inputs. This model serves as the primary transformer implementation for vegetation prediction tasks.

Supporting Components

PositionalEncoding

Sinusoidal positional encoding module following the formulation introduced in “Attention Is All You Need” (Vaswani et al., 2017). Provides temporal position information to transformer-based models.

Model Inputs

Most models expect input tensors of shape:

(batch_size, sequence_length, n_features)

where each timestep contains meteorological, environmental, and optionally plant functional type (PFT) information.

Model Outputs

Models generally return predictions of shape:

(batch_size, sequence_length, output_dim)

where output_dim typically represents one or more vegetation state variables such as greenness, LAI, or intermediate stress indicators.

Notes

  • Several architectures support a return_stress mode that exposes intermediate latent stress representations used internally by the model.

  • Transformer-based models employ causal attention masks to prevent future information leakage during sequence prediction.

  • Plant Functional Type (PFT) variables are assumed to occupy the final feature channels of the input tensor when required.

  • Within the PhenoCam LAI workflow, wrapper modules may permute tensor dimensions to match the project’s standard convention:

    (batch_size, feature_channels, sequence_length)

    while the original models operate on:

    (batch_size, sequence_length, feature_channels)

References

Original vegetation prediction architectures developed at the Max Planck Institute for Biogeochemistry.

class phenonn.models.transformerbis.PositionalEncoding(*args: Any, **kwargs: Any)[source]

Bases: Module

Sinusoidal positional encoding module.

Adds fixed positional encodings to token embeddings as described in the Transformer architecture. The positional encodings are computed using sine and cosine functions of different frequencies and stored as a non-trainable buffer.

Parameters:
  • dim_model (int) – Dimensionality of the token embeddings.

  • dropout_p (float) – Dropout probability applied after adding positional encodings.

  • max_len (int) – Maximum sequence length for which positional encodings are precomputed.

dropout

Dropout layer applied to the sum of token embeddings and positional encodings.

Type:

torch.nn.Dropout

pos_encoding

Tensor of shape (max_len, 1, dim_model) containing the precomputed positional encodings.

Type:

torch.Tensor

Notes

For position pos and embedding dimension i:

  • Even dimensions:

    PE(pos, 2i) = sin(pos * scale_i)

  • Odd dimensions:

    PE(pos, 2i + 1) = cos(pos * scale_i)

where

scale_i = 10000^(2i / dim_model)

Examples

>>> pe = PositionalEncoding(dim_model=512, dropout_p=0.1, max_len=5000)
>>> x = torch.randn(20, 32, 512)
>>> y = pe(x)
>>> y.shape
torch.Size([20, 32, 512])
__init__(dim_model, dropout_p, max_len)[source]
forward(token_embeding: torch.tensor) torch.tensor[source]

Add positional encodings to token embeddings.

Parameters:

token_embeding (torch.Tensor) – Input embeddings of shape (sequence_length, batch_size, dim_model).

Returns:

Embeddings with positional encodings added and dropout applied. Shape is identical to the input: (sequence_length, batch_size, dim_model).

Return type:

torch.Tensor

class phenonn.models.transformerbis.CombinedModel(*args: Any, **kwargs: Any)[source]

Bases: Module

Hybrid Transformer-based model for sequence prediction with auxiliary features.

This model combines: - A linear projection of input features into model space - A Transformer module for sequence-to-sequence processing - Additional concatenated features (PFT inputs) - A stack of Transformer encoder layers with causal masking - Final linear projection to output space

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • hidden_dim (int, default=1024) – Feedforward dimension inside Transformer encoder layers.

  • hidden_dim_trans (int, default=1024) – Feedforward dimension inside the built-in Transformer module.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=32) – Internal embedding dimension used throughout the model.

  • nr_blocks (int, default=3) – Number of stacked TransformerEncoderLayer blocks.

lin1

Projects input features into model dimension (d_model).

Type:

torch.nn.Linear

trans

Transformer module applied to the projected input.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single feature.

Type:

torch.nn.Linear

lin3

Projects concatenated features into model dimension.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks.

Type:

torch.nn.ModuleList

lin4

Final projection from model dimension to output space (2).

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module.

Type:

PositionalEncoding

Notes

  • The model uses a causal (lower-triangular) attention mask to prevent attending to future timesteps.

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for Transformer processing.

__init__(input_dim=26, hidden_dim=1024, hidden_dim_trans=1024, output_dim=2, d_model=32, nr_blocks=3)[source]
forward(x, return_stress=False)[source]

Forward pass of the model using full input sequence.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim).

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, 2)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, 2) - stress_tensor: intermediate representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

forward_from_stress(x, pft)[source]

Forward pass starting from intermediate stress representation.

This bypasses the initial Transformer and lin1/lin2 layers, and instead continues processing from a reduced representation combined with auxiliary PFT features.

Parameters:
  • x (torch.Tensor) – Stress-like representation of shape (batch_size, sequence_length, 1).

  • pft (torch.Tensor) – Auxiliary features of shape (batch_size, sequence_length, 10).

Returns:

Output tensor of shape (batch_size, sequence_length, 2).

Return type:

torch.Tensor

class phenonn.models.transformerbis.BiTransformer(*args: Any, **kwargs: Any)[source]

Bases: Module

Bidirectional Transformer-style sequence model with auxiliary features (PFT).

This model combines: - A linear embedding layer for input features - A Transformer encoder-decoder block for global sequence mixing - A causal (autoregressive) TransformerEncoder stack - Concatenation of auxiliary PFT features - Final projection to output space

The architecture is designed for sequence-to-sequence prediction where past context is enforced via a causal attention mask.

Parameters:
  • input_dim (int, default=26) – Number of input features per timestep.

  • feed_forward_trans (int, default=4) – Multiplier for Transformer feedforward dimension.

  • feed_forward_encoder (int, default=4) – Multiplier for encoder feedforward dimension.

  • output_dim (int, default=2) – Number of output features per timestep.

  • d_model (int, default=256) – Hidden representation size used throughout the model.

  • nr_blocks (int, default=3) – Number of TransformerEncoderLayer blocks.

  • dropout_trans (float, default=0.1) – Dropout rate used inside the Transformer module.

  • dropout_encoder (float, default=0.1) – Dropout rate used in encoder layers.

  • n_pft (int, default=1) – Number of trailing auxiliary features (PFT) appended to input.

lin1

Projects input features into d_model space.

Type:

torch.nn.Linear

trans

Transformer encoder-decoder module for global sequence mixing.

Type:

torch.nn.Transformer

lin2

Reduces Transformer output to a single-channel representation.

Type:

torch.nn.Linear

lin3

Projects concatenated [stress, PFT] features into d_model space.

Type:

torch.nn.Linear

encoder

Stack of TransformerEncoderLayer blocks with causal masking.

Type:

torch.nn.ModuleList

lin4

Final projection to output_dim.

Type:

torch.nn.Linear

positional_encoder

Sinusoidal positional encoding module (defined but not used in forward).

Type:

PositionalEncoding

Notes

  • Input tensors are expected in shape: (batch_size, sequence_length, input_dim)

  • The last n_pft features are treated as auxiliary inputs and split off.

  • A causal (upper-triangular) mask is applied to prevent future information leakage in the encoder stack.

  • Internally, tensors are permuted to: (sequence_length, batch_size, d_model) for TransformerEncoder processing.

__init__(input_dim=26, feed_forward_trans=4, feed_forward_encoder=4, output_dim=2, d_model=256, nr_blocks=3, dropout_trans=0.1, dropout_encoder=0.1, n_pft=1)[source]
forward(x, return_stress=False)[source]

Forward pass of the BiTransformer model.

Parameters:
  • x (torch.Tensor) – Input tensor of shape (batch_size, sequence_length, input_dim), where the last n_pft channels are auxiliary PFT features.

  • return_stress (bool, default=False) – If True, also returns intermediate representation after lin2.

Returns:

If return_stress=False:

Output tensor of shape (batch_size, sequence_length, output_dim)

If return_stress=True:

Tuple (output, stress_tensor) where: - output: final predictions (B, T, output_dim) - stress_tensor: intermediate scalar representation (B, T, 1)

Return type:

torch.Tensor or tuple of torch.Tensor

Neural network building blocks and radiative transfer-inspired models.

This module represents a PyTorch modules for fully connected networks designed for structured data, particularly vertical profile modeling such as atmospheric or canopy radiative transfer.

The module includes:

  • FCBlock: A reusable fully connected block with normalization and activation.

  • FCN: A configurable fully connected network for sequence-like inputs.

Features

  • Modular fully connected components with batch normalization

  • Flexible depth and width configuration for dense networks

  • Support for sequence reshaping and optional dimension expansion

Notes

  • FCN expects inputs shaped as (batch_size, feature_channel, seq_length) and internally flattens them before processing.

Examples

Using FCBlock:

>>> block = FCBlock(128, 64)
>>> x = torch.randn(32, 128)
>>> y = block(x)

Using FCN:

>>> model = FCN(
...     feature_channel=6,
...     output_channel=4,
...     num_layers=3,
...     hidden_size=196,
...     seq_length=10
... )
>>> x = torch.randn(32, 6, 10)
>>> y = model(x)
class phenonn.models.fcn.FCBlock(*args: Any, **kwargs: Any)[source]

Bases: Module

A fully connected block with linear layer, batch normalization, and ReLU activation.

This module applies a linear transformation, followed by batch normalization, and then a ReLU activation function.

Parameters:
  • in_features (int) – Number of input features.

  • out_features (int) – Number of output features.

linear

Linear transformation layer.

Type:

nn.Linear

bn

Batch normalization layer.

Type:

nn.BatchNorm1d

relu

ReLU activation function.

Type:

nn.ReLU

Examples

>>> block = FCBlock(128, 64)
>>> x = torch.randn(32, 128)
>>> y = block(x)
>>> y.shape
torch.Size([32, 64])
__init__(in_features: int, out_features: int) None[source]

Initialize the FCBlock.

Parameters:
  • in_features (int) – Number of input features.

  • out_features (int) – Number of output features.

forward(x: torch.Tensor) torch.Tensor[source]

Forward pass through the FCBlock.

Parameters:

x (torch.Tensor) – Input tensor of shape (batch_size, in_features).

Returns:

Output tensor of shape (batch_size, out_features).

Return type:

torch.Tensor

Notes

The forward pass applies: ReLU(BatchNorm(Linear(x)))

class phenonn.models.fcn.FCN(*args: Any, **kwargs: Any)[source]

Bases: Module

Fully Connected Network with configurable depth and width.

This model flattens the input sequence and processes it through a series of fully connected layers. It can optionally expand the sequence length using a linear transformation.

Parameters:
  • feature_channel (int) – Number of input features per time step.

  • output_channel (int) – Number of output channels.

  • num_layers (int) – Number of hidden layers.

  • hidden_size (int) – Size of hidden layers.

  • seq_length (int, optional) – Length of the input sequence. Default is 55.

  • dim_expand (int, optional) – Number of time steps to expand the output sequence by. Default is 0 (no expansion).

feature_channel

Number of input features.

Type:

int

output_channel

Number of output channels.

Type:

int

seq_length

Length of the input sequence.

Type:

int

dim_expand

Number of time steps to expand by.

Type:

int

input_layer

First fully connected layer.

Type:

FCBlock

hidden_layers

Stack of hidden layers.

Type:

nn.Sequential

output_layer

Final output layer.

Type:

nn.Linear

dim_change

Optional layer for sequence length expansion.

Type:

nn.Linear or None

Examples

>>> model = FCN(
...     feature_channel=6,
...     output_channel=4,
...     num_layers=3,
...     hidden_size=196,
...     seq_length=10
... )
>>> x = torch.randn(32, 6, 10)
>>> y = model(x)
>>> y.shape
torch.Size([32, 4, 10])
__init__(feature_channel: int, output_channel: int, num_layers: int, hidden_size: int, seq_length: int = 55, dim_expand: int = 0) None[source]

Initialize the FCN model.

Parameters:
  • feature_channel (int) – Number of input features.

  • output_channel (int) – Number of output channels.

  • num_layers (int) – Number of hidden layers.

  • hidden_size (int) – Size of hidden layers.

  • seq_length (int, optional) – Length of the input sequence. Default is 55.

  • dim_expand (int, optional) – Number of time steps to expand the output sequence by. Default is 0 (no expansion).

Raises:

ValueError – If num_layers is less than 1.

forward(x: torch.Tensor) torch.Tensor[source]

Forward pass through the FCN.

Parameters:

x (torch.Tensor) – Input tensor of shape (batch_size, feature_channel, seq_length).

Returns:

Output tensor of shape (batch_size, output_channel, seq_length + dim_expand) if dim_expand > 0, otherwise (batch_size, output_channel, seq_length).

Return type:

torch.Tensor

Notes

The forward pass: 1. Flattens the input to (batch_size, feature_channel * seq_length) 2. Passes through FCBlocks 3. Projects to output dimensions 4. Reshapes to (batch_size, output_channel, seq_length) 5. Optionally expands sequence length

Linear regression baselines for LAI prediction.

This module provides simple linear models that serve as reference baselines for evaluating more complex neural network architectures. Both models map meteorological feature sequences to a single LAI prediction while maintaining the same input/output interface as the project’s single-day prediction models.

Implemented Models

LinearBaseline

A full-window linear regression model that flattens the entire input sequence and applies a single fully connected layer. Each feature at each timestep receives an independent weight, allowing the model to learn temporally specific linear relationships.

Mathematically:

y = w^T x + b

where x is the flattened (C × L) feature window.

This model is equivalent to ordinary least squares regression over all feature–time combinations and provides a strong linear benchmark.

PerDayLinearBaseline

A per-timestep linear model that ignores temporal history. The model applies a shared linear mapping from features to LAI and returns the prediction associated with the final timestep of the input sequence.

This baseline evaluates how much predictive information is contained in the current day’s meteorological conditions alone. Strong performance indicates that temporal context contributes little beyond instantaneous feature values.

Input and Output Conventions

Both models follow the project’s single-day prediction interface:

Input:

Tensor of shape (batch_size, feature_channels, sequence_length).

Output:

Tensor of shape (batch_size, 1).

Notes

  • Neither model contains hidden layers, nonlinear activations, recurrence, attention mechanisms, or convolutional operations.

  • The models are intentionally simple and interpretable, making them useful as lower-bound performance baselines.

  • Performance gains achieved by more sophisticated architectures can be interpreted relative to these linear reference models.

class phenonn.models.linear_baseline.LinearBaseline(*args: Any, **kwargs: Any)[source]

Bases: Module

Full-window linear regression baseline.

Flattens the entire (feature_channel × seq_length) input into one vector, applies a single Linear layer to produce a scalar prediction.

This is mathematically equivalent to:

gcc_pred = w @ flatten(input) + b

where w has C×L weights. It can learn things like “temperature on day 300 matters more than temperature on day 50” because each (channel, timestep) pair gets its own weight.

Parameters:
  • feature_channel (int) – Number of input feature channels.

  • seq_length (int) – Window length in days (365).

Examples

>>> model = LinearBaseline(feature_channel=16, seq_length=365)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(feature_channel: int, seq_length: int = 365)[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channel, seq_length))

Return type:

(batch, 1)

class phenonn.models.linear_baseline.PerDayLinearBaseline(*args: Any, **kwargs: Any)[source]

Bases: Module

Per-day linear regression baseline (no temporal context).

Applies the same Linear(C, 1) to every timestep independently, then returns the prediction for the last day. This tests whether today’s feature values alone (without any history) can predict today’s LAI.

If this baseline scores well, it means the temporal context from the 365-day window isn’t adding much — the model is mostly using the current day’s meteorology.

Parameters:

feature_channel (int) – Number of input feature channels.

Examples

>>> model = PerDayLinearBaseline(feature_channel=16)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(feature_channel: int)[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channel, seq_length))

Return type:

(batch, 1)

Training Module

PhenoNN Training Module

Provides training functions for both per-site CSV and flat CSV formats.

phenonn.training.run_training()[source]
phenonn.training.run_training_flat()[source]
phenonn.training.run_training_big()[source]

LAI Prediction — Training Pipeline

This module implements a full training pipeline for Leaf Area Index (LAI) prediction from meteorological time series using deep learning models.

The model predicts LAI at day x using a sliding window of the previous seq_length days (default: 365 days) of meteorological and auxiliary features. It supports multiple architectures including LSTM, GRU, Transformer, FCN, and linear baselines.

The pipeline includes: - Site- or year-based dataset splitting - Feature engineering (meteorological, cyclic, static, PFT) - Optional per-site LAI normalization - Optional residual learning (predicting obs - pred) - Flexible sequence sampling (stride or random sampling) - Gradient-aware temporal loss (optional) - Early stopping and learning-rate scheduling - Diagnostic logging and training curves - Checkpointing of best model

Supported models

  • LSTM

  • GRU

  • Transformer

  • FCN / FullyConnected

  • Linear

  • Linear-per-day

  • Bi-directional Transformer variants

  • 1-year sequence models (LSTM / BiTransformer)

Typical usage

Train an LSTM on all sites:

python -m phenocam.run_training –data_dir ./data/DB/ –type lstm –hidden_size 128 –num_layers 2 –num_epochs 50 –batch_size 64

Train with year-based split:

python -m phenocam.run_training –data_dir ./data/DB/ –type transformer –split_mode year –train_years 2018,2019,2020 –val_years 2021 –embed_size 64 –nhead 4

Notes

  • Normalization statistics are computed on training data only.

  • Validation can be performed on held-out sites or held-out years.

  • Loss functions include MSE, MAE, Huber, NMSE, and gradient-based losses.

  • The model can optionally predict full yearly sequences (365-day outputs).

  • Residual learning mode uses external predictions as input targets.

phenonn.training.train.parse_args()[source]
phenonn.training.train.parse_year_list(s: str)[source]

Parse ‘2018,2019,2020’ or ‘2018-2020’ into [2018, 2019, 2020].

phenonn.training.train.train_one_epoch(model, loader, criterion, optimizer, device, max_grad_norm=1.0)[source]
phenonn.training.train.validate(model, loader, criterion, device, dataset=None, n_target_days=1, full_year=False)
phenonn.training.train.run_training()[source]

Training pipeline for LAI prediction from flat CSV datasets.

This script trains sequence-based neural networks to predict Leaf Area Index (LAI) from meteorological, ecological, and land-cover features stored in two flat CSV files containing daily predictors and sparse LAI observations.

The workflow is intended for datasets that fit comfortably in memory and provides a complete training pipeline including dataset construction, normalization, model initialization, validation, checkpointing, and diagnostic visualization.

Problem Formulation

Each sample corresponds to a unique (site_id, year) pair.

Input:

Tensor of shape:

(n_features, sequence_length)

containing a rolling history of environmental forcing variables, typically spanning approximately two years.

Output:

Tensor of shape:

(1, 36)

containing 36 LAI observations for the target year, corresponding to days 5, 15, and 25 of each month.

The model predicts the complete annual LAI trajectory from the historical environmental context.

Supported Model Architectures

LSTM

Multi-layer recurrent neural network using Long Short-Term Memory units.

GRU

Multi-layer recurrent neural network using Gated Recurrent Units.

Transformer

Self-attention encoder model for sequence modeling.

BiTransformer

Transformer architecture incorporating environmental forcing and plant functional type information.

All models are wrapped by Every10DaysWrapper to produce LAI predictions at the observation frequency used by the target dataset.

Dataset Splitting

Two validation strategies are supported.

Site Split

Training and validation use disjoint sets of sites.

This evaluates spatial generalization and answers the question:

“Can the model predict LAI at previously unseen locations?”

Year Split

All sites are retained, but training and validation use different years.

This evaluates temporal generalization and answers the question:

“Can the model predict vegetation dynamics in unseen years?”

Normalization

Feature normalization statistics are computed from the training set and stored in a JSON file for reproducibility.

If a normalization file already exists, it can be reused to avoid recomputation.

The normalization pipeline:

  1. Computes feature means and standard deviations.

  2. Applies z-score normalization to input features.

  3. Normalizes target values for training stability.

  4. Stores statistics alongside model checkpoints.

Training Features

  • Mini-batch training with PyTorch DataLoaders.

  • Configurable model architectures.

  • Multiple loss functions (MSE, MAE, Huber, SmoothL1, normalized losses, and gradient-aware losses).

  • Gradient clipping.

  • Adaptive learning-rate scheduling.

  • Early stopping.

  • Best-model checkpointing.

  • Training and validation diagnostics.

  • Reproducible experiment logging.

Evaluation Metrics

Validation performance is reported using:

Loss

Training objective selected by --loss_type.

RMSE

Root Mean Squared Error computed across all predicted LAI values.

Coefficient of determination computed across all validation samples.

Data Requirements

Feature CSV

Daily meteorological, ecological, and land-cover predictors indexed by site and date.

Target CSV

Sparse LAI observations containing three measurements per month (days 5, 15, and 25).

The dataset builder constructs fixed-length historical windows ending in the target year and aligns them with the corresponding annual LAI sequence.

Examples

Train an LSTM model:

>>> python -m phenonn.run_train_flat ...     --features_csv data/features.csv ...     --target_csv data/targets.csv ...     --type lstm ...     --hidden_size 128 ...     --num_layers 2 ...     --num_epochs 50 ...     --batch_size 32

Train a transformer model:

>>> python -m phenonn.run_train_flat ...     --features_csv data/features.csv ...     --target_csv data/targets.csv ...     --type transformer ...     --embed_size 128 ...     --num_layers 4

Notes

This training script is intended for moderate-sized datasets that can be indexed efficiently from flat CSV files. For very large archives distributed across yearly files, the streaming workflow implemented in main_big.py provides a more scalable alternative.

phenonn.training.train_flat.parse_args()[source]
phenonn.training.train_flat.parse_args_bis()[source]
phenonn.training.train_flat.parse_year_list(s: str)[source]

Parse ‘2000,2001,2002’ or ‘2000-2002’ into [2000, 2001, 2002].

phenonn.training.train_flat.build_model(args) torch.nn.Module[source]

Instantiate base model and wrap with Every10DaysWrapper.

phenonn.training.train_flat.train_one_epoch(model, loader, criterion, optimizer, device, max_grad_norm)[source]
phenonn.training.train_flat.validate(model, loader, criterion, device)
phenonn.training.train_flat.run_training_flat()[source]

Prediction Module

PhenoNN Prediction Module

Provides functions for running predictions with trained models: - Single-year prediction (per-site CSV format) - Flat CSV prediction (features/targets format)

phenonn.prediction.run_prediction()[source]
phenonn.prediction.run_prediction_flat()[source]

LAI Prediction — Year-by-Year Inference

Loads a trained model checkpoint and predicts the full annual LAI curve. Supports both site-split and year-split models.

Usage

# Predict on validation sites, all available years: python -m phenocam.predict –checkpoint ./runs/exp01/checkpoints/best_model.pth –data_dir ./data/DB/

# Predict on all sites, specific years: python -m phenocam.predict –checkpoint ./runs/exp01/checkpoints/best_model.pth –data_dir ./data/DB/ –predict_sites all –predict_years 2022,2023

# Predict on training sites only: python -m phenocam.predict –checkpoint ./runs/exp01/checkpoints/best_model.pth –data_dir ./data/DB/ –predict_sites train –predict_years 2022

phenonn.prediction.predict.parse_args()[source]
phenonn.prediction.predict.run_prediction()[source]

LAI Prediction — Flat CSV Inference

Loads a trained checkpoint from main_flat.py and predicts LAI at the 36 observation days (5th, 15th, 25th of each month) for every (site_id, year) pair in the chosen site set.

Output

predictions_flat.csvstr

One row per (site, year, observation day)

pred_vs_obs.pngstr

Scatter plot on real LAI values

pred_vs_obs_norm.pngstr

Scatter plot on normalized values

lai_curves.pngstr

Annual curves for 3 representative sites

lai_curves_all.pngstr

Annual curves for all sites (grid)

Usage

Basic prediction:
python -m phenonn.prediction.predict_flat

–checkpoint runs/exp_flat/checkpoints/best_model.pth –features_csv data/features.csv –target_csv data/targets.csv

Predict on all sites for specific years:
python -m phenonn.prediction.predict_flat

–checkpoint runs/exp_flat/checkpoints/best_model.pth –predict_sites all –predict_years 2002,2003

Examples

Run prediction on validation sites:
>>> python -m phenonn.prediction.predict_flat \
...     --checkpoint runs/exp_flat/checkpoints/best_model.pth \
...     --features_csv data/features.csv \
...     --target_csv data/targets.csv
Run prediction on all sites for years 2020-2022:
>>> python -m phenonn.prediction.predict_flat \
...     --checkpoint runs/exp_flat/checkpoints/best_model.pth \
...     --features_csv data/features.csv \
...     --target_csv data/targets.csv \
...     --predict_sites all \
...     --predict_years 2020,2021,2022

Notes

The input features CSV must contain daily data with columns:

site_id, date, year, month, day, pft1_frac..pft15_frac, tmin, tmax, daylength, prcp, srad, vpd, swe

The target CSV must contain LAI observations for days 5, 15, 25 of each month:

site_id, date, year, month, day, LAI

phenonn.prediction.predict_flat.parse_args()[source]
phenonn.prediction.predict_flat.run_prediction_flat()[source]

Utils Module

PhenoNN Utility Modules

Provides logging, diagnostics, model utilities, and evaluation functions.

class phenonn.utils.Logger(console_output=True, file_output=False, log_file='module_log_file.log', pretty_print=True, record=False)[source]

Bases: object

__init__(console_output=True, file_output=False, log_file='module_log_file.log', pretty_print=True, record=False)[source]
clear_logs()[source]

Clear the stored Rich logs if record=True.

show_header(module_name)[source]

Display startup banner.

start_task(task_name: str, description: str = '', **meta)[source]

Display a clearly formatted ‘task start’ message with good spacing.

log_metrics()[source]

Log pipeline metrics

info(message)[source]

Formatted info message

warning(message)[source]

Formatted warning message

success(message)[source]

Custom success level (not default logging level)

step(step_name, message)[source]

Highlight pipeline step events

exception(message, exception=None)[source]

Display a formatted exception message with visual stack trace.

error(message, exception=None)[source]

Display a formatted error log, optionally including exception trace.

phenonn.utils.plot_loss_histories(train_loss: Sequence[float], valid_loss: Sequence[float], filename: str = 'loss_history.png', logger=None, log_scale: bool = True, title: str = 'Training / Validation Loss') None[source]

Plot training and validation loss curves over epochs.

Parameters:
  • train_loss (sequence of float) – Per-epoch training loss.

  • valid_loss (sequence of float) – Per-epoch validation loss (same length as train_loss).

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional) – If provided, log the save location instead of printing.

  • log_scale (bool) – Use log y-axis (default True — losses usually span orders of magnitude).

  • title (str) – Figure title.

Notes

  • Uses mpltex linestyles for consistent styling

  • Gray vertical dashed line: best validation epoch

  • Includes grid for better readability

phenonn.utils.plot_metric_histories(train_history: Dict[str, Sequence[float]], valid_history: Dict[str, Sequence[float]], filename: str = 'metric_history.png', logger=None, log_metrics: Sequence[str] | None = None, cols: int = 3) None[source]

Multi-panel figure of metric evolution over epochs.

One panel per metric. Train and validation plotted together on each panel.

Parameters:
  • train_history (dict) – {metric_name: per-epoch values}. Example: {“rmse”: […], “r2”: […]}.

  • valid_history (dict) – Same keys as train_history, same lengths.

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • log_metrics (sequence of str, optional) – Metric names that should use log y-scale. Default: all except R²-like metrics (anything with “r2” or “r²” in the name).

  • cols (int) – Number of columns in the grid.

Notes

  • Uses mpltex linestyles for consistent styling

  • Missing/NaN values in a metric series are skipped — useful when you record metrics conditionally and some epochs lack certain values.

  • Includes grid for better readability

phenonn.utils.plot_pred_vs_obs(pred: numpy.ndarray | Sequence[float], obs: numpy.ndarray | Sequence[float], filename: str = 'pred_vs_obs.png', logger=None, title: str = 'Predicted vs Observed LAI', xlabel: str = 'Observed LAI', ylabel: str = 'Predicted LAI', gridsize: int = 40, hexbin: bool = True) Dict[str, float][source]

Scatter plot of predictions vs observations with y=x reference and metrics.

Uses hexbin density by default (fast, readable for >10k points). Falls back to a regular scatter for small sample sets where hexbin would be mostly empty.

Parameters:
  • pred (array-like) – Predicted values (any shape — will be flattened).

  • obs (array-like) – Observed values (same shape).

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • title (str) – Plot labels.

  • xlabel (str) – Plot labels.

  • ylabel (str) – Plot labels.

  • gridsize (int) – Hexbin resolution (number of hexagons along each axis).

  • hexbin (bool) – If True, use hexbin density. If False, use scatter. Default True.

Returns:

Computed metrics: rmse, mae, bias, r2, n.

Return type:

dict

Notes

Errors ignored in computation: NaN and Inf pairs are dropped. Plot axis limits are set to cover both predictions and observations with a small margin, so the y=x line always appears diagonal.

phenonn.utils.plot_gcc_curves(df: pandas.DataFrame, filename: str = 'gcc_curves_by_r2.png', logger=None, seed: int = 42, site_col: str = 'site', year_col: str = 'year', doy_col: str = 'day_index', pred_col: str = 'lai_pred', obs_col: str = 'lai_obs') Dict[str, float][source]

Plot observed vs predicted annual LAI curves for three representative sites.

Picks one random site with low R², one with medium R², and one with high R² relative to the mean across all sites. Each site gets a subplot showing all validation years overlaid, with observed LAI as a solid line and predicted LAI as a dashed line.

Parameters:
  • df (pd.DataFrame) – Predictions dataframe as produced by predict.py, with columns for site, year, day index, lai_pred, and lai_obs.

  • filename (str) – Output path for the figure.

  • logger (phenonn.utils.logger.Logger, optional)

  • seed (int) – Random seed for reproducible site selection.

  • site_col (str) – Column names in df. Defaults match predict.py output.

  • year_col (str) – Column names in df. Defaults match predict.py output.

  • doy_col (str) – Column names in df. Defaults match predict.py output.

  • pred_col (str) – Column names in df. Defaults match predict.py output.

  • obs_col (str) – Column names in df. Defaults match predict.py output.

Returns:

{site_name: r2_value} for the three selected sites.

Return type:

dict

Notes

Site selection: all sites are ranked by R². The site pool is split into three terciles (low / mid / high). One site is drawn randomly from each tercile. Using terciles rather than the absolute best/worst avoids always showing the same outlier sites.

phenonn.utils.plot_gcc_curves_all(df: pandas.DataFrame, filename: str = 'gcc_curves_all.png', logger=None, cols: int = 4, site_col: str = 'site', year_col: str = 'year', doy_col: str = 'day_index', pred_col: str = 'lai_pred', obs_col: str = 'lai_obs') Dict[str, float][source]

Plot observed vs predicted annual LAI curves for every site.

One small subplot per site, arranged in a grid, sorted by R² from best (top-left) to worst (bottom-right). Each subplot overlays all validation years with observed (solid) and predicted (dashed) lines.

Parameters:
  • df (pd.DataFrame) – Predictions dataframe with columns for site, year, day index, lai_pred, and lai_obs.

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • cols (int) – Number of columns in the grid. Default 4.

  • site_col (str) – Column names in df.

  • year_col (str) – Column names in df.

  • doy_col (str) – Column names in df.

  • pred_col (str) – Column names in df.

  • obs_col (str) – Column names in df.

Returns:

{site_name: r2_value} for all sites.

Return type:

dict

phenonn.utils.make_history_dicts() tuple[source]

Shortcut for initializing matched train/valid history dicts.

Returns:

(train_hist, valid_hist) – Each with empty lists keyed by ‘loss’, ‘rmse’, ‘r2’.

Return type:

tuple of dict

Example

>>> train_hist, valid_hist = make_history_dicts()
>>> # inside training loop:
>>> train_hist['loss'].append(train_loss)
>>> valid_hist['loss'].append(val_loss)
>>> valid_hist['rmse'].append(val_rmse)
>>> valid_hist['r2'].append(val_r2)
phenonn.utils.get_loss_function(loss_type, args, logger=None)[source]

Factory function to instantiate the requested loss function.

Parameters:
  • loss_type (str) – Type of loss function. Options: - ‘mse’: Mean Squared Error - ‘mae’: Mean Absolute Error - ‘nmae’: Normalized Mean Absolute Error - ‘nmse’: Normalized Mean Squared Error - ‘wmse’: Weighted Mean Squared Error - ‘logcosh’: Log-Cosh loss - ‘smoothl1’: Smooth L1 Loss (Huber-like) - ‘huber’: Huber Loss

  • args (argparse.Namespace) – Arguments containing loss-specific parameters (e.g., beta_delta for Huber).

Returns:

Initialized loss function.

Return type:

torch.nn.Module

Raises:

ValueError – If loss_type is not supported or required parameters are missing.

Examples

>>> args = argparse.Namespace(beta_delta=1.0)
>>> criterion = get_loss_function('huber', args)
class phenonn.utils.ModelUtils[source]

Bases: object

Utility class for model inspection, checkpointing, and memory profiling.

This class provides static methods for common model operations including parameter counting, memory usage analysis, checkpoint management, and model inspection.

Examples

>>> utils = ModelUtils()
>>> param_counts = ModelUtils.get_parameter_number(model)
>>> ModelUtils.save_checkpoint(state, "checkpoint.pth.tar", logger)
__init__()[source]

Initialize ModelUtils instance.

static get_parameter_number(model, logger=None)[source]

Calculate the total and trainable number of parameters in a model.

Parameters:
  • model (torch.nn.Module) – PyTorch model to inspect

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Returns:

Dictionary containing: - ‘Total’: Total number of parameters - ‘Trainable’: Number of trainable parameters

Return type:

dict

Examples

>>> model = torch.nn.Linear(10, 5)
>>> counts = ModelUtils.get_parameter_number(model, logger)
static print_model_layers(model, logger=None)[source]

Print model parameter names along with their gradient requirements.

Parameters:
  • model (torch.nn.Module) – PyTorch model to inspect

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> model = torch.nn.Sequential(
...     torch.nn.Linear(10, 5),
...     torch.nn.ReLU(),
...     torch.nn.Linear(5, 1)
... )
>>> ModelUtils.print_model_layers(model, logger)
static save_checkpoint(state, filename='checkpoint.pth.tar', logger=None)[source]

Save model and optimizer state to a file.

Parameters:
  • state (dict) – Dictionary containing model state_dict and other training information. Typically includes: - ‘state_dict’: Model parameters - ‘optimizer’: Optimizer state - ‘epoch’: Current epoch - ‘loss’: Current loss value

  • filename (str, optional) – File path to save the checkpoint, by default “checkpoint.pth.tar”

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> state = {
...     'state_dict': model.state_dict(),
...     'optimizer': optimizer.state_dict(),
...     'epoch': epoch,
...     'loss': loss
... }
>>> ModelUtils.save_checkpoint(state, 'model_checkpoint.pth.tar', logger)
static load_checkpoint(checkpoint, model, optimizer=None, logger=None)[source]

Load model and optimizer state from a checkpoint file.

Parameters:
  • checkpoint (dict) – Loaded checkpoint dictionary

  • model (torch.nn.Module) – Model to load weights into

  • optimizer (torch.optim.Optimizer, optional) – Optimizer to restore state, by default None

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> checkpoint = torch.load('model_checkpoint.pth.tar')
>>> ModelUtils.load_checkpoint(checkpoint, model, optimizer, logger)
static load_training_checkpoint(checkpoint_path, model, optimizer, device, logger=None)[source]

Load comprehensive training checkpoint.

Parameters:
  • checkpoint_path (str) – Path to checkpoint file

  • model (torch.nn.Module) – Model to load weights into

  • optimizer (torch.optim.Optimizer) – Optimizer to restore state

  • device (torch.device) – Device to load checkpoint to

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output

Returns:

(epoch, samples_processed, batches_processed, best_val_loss, best_epoch, checkpoint)

Return type:

tuple

static count_parameters_by_layer(model, logger=None)[source]

Count parameters for each layer in the model.

Parameters:
  • model (torch.nn.Module) – PyTorch model to analyze

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Returns:

Dictionary with layer names as keys and parameter counts as values

Return type:

dict

Examples

>>> layer_params = ModelUtils.count_parameters_by_layer(model, logger)
static log_model_summary(model, input_shape=None, logger=None)[source]

Log comprehensive model summary including parameters and architecture.

Parameters:
  • model (torch.nn.Module) – PyTorch model to summarize

  • input_shape (tuple, optional) – Input shape for memory analysis, by default None

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

static save_training_checkpoint(model, optimizer, epoch, samples_processed, batches_processed, train_loss_history, valid_loss_history, valid_metrics_history, best_val_loss, best_epoch, avg_val_loss, avg_epoch_loss, args, paths, logger, checkpoint_type='epoch', save_full_model=True)[source]

Save comprehensive training checkpoint with consistent formatting.

Parameters:
  • model (torch.nn.Module) – Model to save

  • optimizer (torch.optim.Optimizer) – Optimizer to save

  • epoch (int) – Current epoch

  • samples_processed (int) – Number of samples processed so far

  • batches_processed (int) – Number of batches processed so far

  • train_loss_history (list) – History of training losses

  • valid_loss_history (list) – History of validation losses

  • valid_metrics_history (dict) – History of validation metrics

  • best_val_loss (float) – Best validation loss so far

  • best_epoch (int) – Epoch with best validation loss

  • avg_val_loss (float) – Current epoch validation loss

  • avg_epoch_loss (float) – Current epoch training loss

  • args (argparse.Namespace) – Command line arguments

  • paths (EasyDict) – Directory paths

  • logger (phenonn.utils.logger.Logger) – phenonn.utils.logger.Logger instance

  • checkpoint_type (str) – Type of checkpoint: “samples”, “epoch”, “best”, “final”

  • save_full_model (bool) – Whether to also save the full model separately

Returns:

(checkpoint_filename, full_model_filename)

Return type:

tuple

Examples

>>> checkpoint_file, full_model_file = ModelUtils.save_training_checkpoint(
...     model, optimizer, epoch, samples_processed, batches_processed,
...     train_loss_history, valid_loss_history, valid_metrics_history,
...     best_val_loss, best_epoch, avg_val_loss, avg_epoch_loss,
...     args, paths, logger, checkpoint_type="best"
... )
static save_emergency_checkpoint(model, optimizer, epoch, samples_processed, batches_processed, train_loss_history, valid_loss_history, valid_metrics_history, args, paths, logger, reason='emergency')[source]

Save emergency checkpoint for recovery.

Parameters:

reason (str) – Reason for emergency save (e.g., “crash”, “interrupt”, “error”)

Returns:

(checkpoint_filename, full_model_filename)

Return type:

tuple

phenonn.utils.load_model(args)[source]

Instantiate and wrap a model for single-day GCC prediction.

Parameters:

args (argparse.Namespace or EasyDict) –

Must contain at minimum:

typestr

One of ‘lstm’, ‘gru’, ‘transformer’, ‘fcn’, ‘fullyconnected’.

feature_channelint

Number of input feature channels (meteo + cyclic + static + PFT).

output_channelint

Number of output channels (1 for gcc_lowess).

seq_lengthint

Window length (365).

Plus architecture-specific parameters (see original model_loader).

Returns:

Model whose forward returns (batch, output_channel).

Return type:

SingleDayWrapper

class phenonn.utils.FileUtils[source]

Bases: object

Utility class for file and directory operations.

__init__()[source]

Initialize the FileUtils class. This class does not maintain any state, so the constructor is empty.

static makedir(dirs)[source]

Create a directory if it does not exist.

Parameters:

dirs (str) – The path of the directory to be created.

static makefile(dirs, filename)[source]

Create an empty file in the specified directory. :param dirs: The path of the directory where the file will be created. :type dirs: str :param filename: The name of the file to be created. :type filename: str

class phenonn.utils.EasyDict[source]

Bases: dict

A dictionary subclass that allows for attribute-style access to its items. This class extends the built-in dict and overrides the __getattr__, __setattr__, and __delattr__ methods to enable accessing dictionary keys as attributes. Original work: Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. Original source: https://github.com/NVlabs/edm

class phenonn.utils.Every10DaysWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Wraps any RTnn model to output predictions at the 36 LAI observation days.

Extracts the last 365 timesteps of the model output, then selects the 36 positions corresponding to days 5, 15, 25 of each month in a non-leap year.

Input : (B, C_in, L) with L ≥ 365 Output : (B, C_out, 36)

Parameters:

base_model (nn.Module) – Any model with forward signature (B, C_in, L) -> (B, C_out, L).

Examples

>>> base = RNN_LSTM(feature_channel=31, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = Every10DaysWrapper(base)
>>> x = torch.randn(32, 31, 720)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1, 36])
__init__(base_model: torch.nn.Module) None[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channels, seq_length) — seq_length ≥ 365)

Return type:

(batch, output_channel, 36)

class phenonn.utils.permuteWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Permutes input dimensions to match expected order for RTnn models.

RTnn models expect input shape (batch, feature_channels, seq_length). If your data is in (batch, seq_length, feature_channels), this wrapper permutes the dimensions before passing to the base model.

Parameters:

base_model (nn.Module) – Any model with forward signature (batch, C_in, L) -> (batch, C_out, L).

Examples

>>> from rtnn.models.rnn import RNN_LSTM
>>> base = RNN_LSTM(feature_channel=14, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = permuteWrapper(base)
>>> x = torch.randn(32, 365, 14)  # Note seq_length and feature_channels swapped
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(base_model)[source]
forward(x)[source]
class phenonn.utils.LastNDaysWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Wraps any RTnn model to output the last N timesteps.

Takes the last N timesteps of the wrapped model’s sequence output, yielding shape (batch, output_channel, N) instead of (batch, output_channel, seq_length).

Used for gradient-aware loss where we need GCC(t) and GCC(t-1) while keeping the full input window for context.

Parameters:
  • base_model (nn.Module) – Any model with forward signature (batch, C_in, L) -> (batch, C_out, L).

  • n_days (int) – Number of trailing timesteps to keep (default: 2).

Examples

>>> base = RNN_LSTM(feature_channel=16, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = LastNDaysWrapper(base, n_days=2)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1, 2])
__init__(base_model: torch.nn.Module, n_days: int = 2) None[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channels, seq_length))

Return type:

(batch, output_channel, n_days)

class phenonn.utils.logger.Logger(console_output=True, file_output=False, log_file='module_log_file.log', pretty_print=True, record=False)[source]

Bases: object

__init__(console_output=True, file_output=False, log_file='module_log_file.log', pretty_print=True, record=False)[source]
clear_logs()[source]

Clear the stored Rich logs if record=True.

show_header(module_name)[source]

Display startup banner.

start_task(task_name: str, description: str = '', **meta)[source]

Display a clearly formatted ‘task start’ message with good spacing.

log_metrics()[source]

Log pipeline metrics

info(message)[source]

Formatted info message

warning(message)[source]

Formatted warning message

success(message)[source]

Custom success level (not default logging level)

step(step_name, message)[source]

Highlight pipeline step events

exception(message, exception=None)[source]

Display a formatted exception message with visual stack trace.

error(message, exception=None)[source]

Display a formatted error log, optionally including exception trace.

phenonn.utils.diagnostics.plot_loss_histories(train_loss: Sequence[float], valid_loss: Sequence[float], filename: str = 'loss_history.png', logger=None, log_scale: bool = True, title: str = 'Training / Validation Loss') None[source]

Plot training and validation loss curves over epochs.

Parameters:
  • train_loss (sequence of float) – Per-epoch training loss.

  • valid_loss (sequence of float) – Per-epoch validation loss (same length as train_loss).

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional) – If provided, log the save location instead of printing.

  • log_scale (bool) – Use log y-axis (default True — losses usually span orders of magnitude).

  • title (str) – Figure title.

Notes

  • Uses mpltex linestyles for consistent styling

  • Gray vertical dashed line: best validation epoch

  • Includes grid for better readability

phenonn.utils.diagnostics.plot_metric_histories(train_history: Dict[str, Sequence[float]], valid_history: Dict[str, Sequence[float]], filename: str = 'metric_history.png', logger=None, log_metrics: Sequence[str] | None = None, cols: int = 3) None[source]

Multi-panel figure of metric evolution over epochs.

One panel per metric. Train and validation plotted together on each panel.

Parameters:
  • train_history (dict) – {metric_name: per-epoch values}. Example: {“rmse”: […], “r2”: […]}.

  • valid_history (dict) – Same keys as train_history, same lengths.

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • log_metrics (sequence of str, optional) – Metric names that should use log y-scale. Default: all except R²-like metrics (anything with “r2” or “r²” in the name).

  • cols (int) – Number of columns in the grid.

Notes

  • Uses mpltex linestyles for consistent styling

  • Missing/NaN values in a metric series are skipped — useful when you record metrics conditionally and some epochs lack certain values.

  • Includes grid for better readability

phenonn.utils.diagnostics.plot_pred_vs_obs(pred: numpy.ndarray | Sequence[float], obs: numpy.ndarray | Sequence[float], filename: str = 'pred_vs_obs.png', logger=None, title: str = 'Predicted vs Observed LAI', xlabel: str = 'Observed LAI', ylabel: str = 'Predicted LAI', gridsize: int = 40, hexbin: bool = True) Dict[str, float][source]

Scatter plot of predictions vs observations with y=x reference and metrics.

Uses hexbin density by default (fast, readable for >10k points). Falls back to a regular scatter for small sample sets where hexbin would be mostly empty.

Parameters:
  • pred (array-like) – Predicted values (any shape — will be flattened).

  • obs (array-like) – Observed values (same shape).

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • title (str) – Plot labels.

  • xlabel (str) – Plot labels.

  • ylabel (str) – Plot labels.

  • gridsize (int) – Hexbin resolution (number of hexagons along each axis).

  • hexbin (bool) – If True, use hexbin density. If False, use scatter. Default True.

Returns:

Computed metrics: rmse, mae, bias, r2, n.

Return type:

dict

Notes

Errors ignored in computation: NaN and Inf pairs are dropped. Plot axis limits are set to cover both predictions and observations with a small margin, so the y=x line always appears diagonal.

phenonn.utils.diagnostics.plot_gcc_curves(df: pandas.DataFrame, filename: str = 'gcc_curves_by_r2.png', logger=None, seed: int = 42, site_col: str = 'site', year_col: str = 'year', doy_col: str = 'day_index', pred_col: str = 'lai_pred', obs_col: str = 'lai_obs') Dict[str, float][source]

Plot observed vs predicted annual LAI curves for three representative sites.

Picks one random site with low R², one with medium R², and one with high R² relative to the mean across all sites. Each site gets a subplot showing all validation years overlaid, with observed LAI as a solid line and predicted LAI as a dashed line.

Parameters:
  • df (pd.DataFrame) – Predictions dataframe as produced by predict.py, with columns for site, year, day index, lai_pred, and lai_obs.

  • filename (str) – Output path for the figure.

  • logger (phenonn.utils.logger.Logger, optional)

  • seed (int) – Random seed for reproducible site selection.

  • site_col (str) – Column names in df. Defaults match predict.py output.

  • year_col (str) – Column names in df. Defaults match predict.py output.

  • doy_col (str) – Column names in df. Defaults match predict.py output.

  • pred_col (str) – Column names in df. Defaults match predict.py output.

  • obs_col (str) – Column names in df. Defaults match predict.py output.

Returns:

{site_name: r2_value} for the three selected sites.

Return type:

dict

Notes

Site selection: all sites are ranked by R². The site pool is split into three terciles (low / mid / high). One site is drawn randomly from each tercile. Using terciles rather than the absolute best/worst avoids always showing the same outlier sites.

phenonn.utils.diagnostics.plot_gcc_curves_all(df: pandas.DataFrame, filename: str = 'gcc_curves_all.png', logger=None, cols: int = 4, site_col: str = 'site', year_col: str = 'year', doy_col: str = 'day_index', pred_col: str = 'lai_pred', obs_col: str = 'lai_obs') Dict[str, float][source]

Plot observed vs predicted annual LAI curves for every site.

One small subplot per site, arranged in a grid, sorted by R² from best (top-left) to worst (bottom-right). Each subplot overlays all validation years with observed (solid) and predicted (dashed) lines.

Parameters:
  • df (pd.DataFrame) – Predictions dataframe with columns for site, year, day index, lai_pred, and lai_obs.

  • filename (str) – Output path.

  • logger (phenonn.utils.logger.Logger, optional)

  • cols (int) – Number of columns in the grid. Default 4.

  • site_col (str) – Column names in df.

  • year_col (str) – Column names in df.

  • doy_col (str) – Column names in df.

  • pred_col (str) – Column names in df.

  • obs_col (str) – Column names in df.

Returns:

{site_name: r2_value} for all sites.

Return type:

dict

phenonn.utils.diagnostics.plot_feature_distributions(site_files: List[str], filename: str = 'feature_distributions.png', logger=None, cols: int = 4, n_bins: int = 60, max_sites: int = 20) None[source]

Plot histograms of all input features and the target variable.

Shows the raw distribution and, for log-transformed features, the distribution after log1p. Useful for checking skewness, spotting outliers, and verifying that normalization choices are appropriate.

Parameters:
  • site_files (list of str) – Paths to site CSVs. A random subset of max_sites is used to keep computation fast.

  • filename (str) – Output path for the figure.

  • logger (phenonn.utils.logger.Logger, optional)

  • cols (int) – Number of columns in the grid.

  • n_bins (int) – Number of histogram bins.

  • max_sites (int) – Maximum number of sites to sample (for speed).

Notes

For each feature, the histogram shows: - Blue: raw values (pooled across all sampled sites) - Orange (if applicable): values after log1p transform - Vertical red dashed lines: mean ± 1 std - Title includes skewness and % of near-zero values

The target (LAI) is shown in green in the last panel.

phenonn.utils.diagnostics.plot_feature_distributions_per_site(site_files: List[str], output_dir: str = './feature_distributions', logger=None, cols: int = 4, n_bins: int = 60) None[source]

Generate one feature distribution plot per site.

Produces one PNG file per site, named {sitename}_feature_distribution.png, in the output directory. Each file shows the same histogram layout as plot_feature_distributions but for a single site only.

Parameters:
  • site_files (list of str) – Paths to site CSVs.

  • output_dir (str) – Directory where individual PNGs will be saved.

  • logger (phenonn.utils.logger.Logger, optional)

  • cols (int) – Number of columns in the histogram grid.

  • n_bins (int) – Number of histogram bins.

Examples

>>> site_files = sorted(glob.glob("./data/DB/*.csv"))
>>> plot_feature_distributions_per_site(site_files, output_dir="./runs/dist_per_site")
# Creates: ./runs/dist_per_site/DB_asuhighlands_feature_distribution.png
#          ./runs/dist_per_site/DB_bartlett_feature_distribution.png
#          ... (one per site)
phenonn.utils.diagnostics.make_history_dicts() tuple[source]

Shortcut for initializing matched train/valid history dicts.

Returns:

(train_hist, valid_hist) – Each with empty lists keyed by ‘loss’, ‘rmse’, ‘r2’.

Return type:

tuple of dict

Example

>>> train_hist, valid_hist = make_history_dicts()
>>> # inside training loop:
>>> train_hist['loss'].append(train_loss)
>>> valid_hist['loss'].append(val_loss)
>>> valid_hist['rmse'].append(val_rmse)
>>> valid_hist['r2'].append(val_r2)

Evaluation utilities for RTnn model assessment.

This module provides comprehensive evaluation tools for radiative transfer neural network models, including custom loss functions, metric computation, and visualization helpers.

The module includes: - Custom loss functions (NMSE, NMAE, combined MSE-MAE, LogCosh, Weighted MSE) - Metric calculators for evaluation (MSE, MAE, MBE, R², NMSE, NMAE, MARE, GMRAE) - Data normalization/de-normalization utilities - Absorption rate calculations - Main evaluation loop for LSM models

Dependencies

torch : For tensor operations and loss functions numpy : For numerical operations plot_helper : For visualization utilities

class phenonn.utils.evaluater.GradientAwareLoss(*args: Any, **kwargs: Any)[source]

Bases: Module

MSE + temporal gradient penalty on the last N predicted days.

Combines two terms:

L = MSE(ŷ, y) + λ * MSE(Δŷ, Δy)

where Δ denotes the discrete temporal difference between consecutive days:

Δy = y[…, 1:] - y[…, :-1]

Designed for n_target_days=2: the model predicts [GCC(t-1), GCC(t)] and the loss penalises both the values and the day-to-day change.

Parameters:
  • grad_weight (float) – Weight λ for the gradient term. 0 = pure MSE. Typical values: 0.1–1.0.

  • base_loss (str) – Base reconstruction loss: ‘mse’ or ‘huber’.

  • huber_delta (float) – Delta for Huber loss (only used if base_loss=’huber’).

  • shapes (Input)

  • ------------

  • pred (torch.Tensor) – Shape (batch, 1, N) — last N days predictions (typically N=2)

  • target (torch.Tensor) – Shape (batch, 1, N) — last N days targets

Examples

>>> criterion = GradientAwareLoss(grad_weight=0.5)
>>> pred = torch.randn(32, 1, 2)     # [ŷ(t-1), ŷ(t)]
>>> target = torch.randn(32, 1, 2)   # [y(t-1), y(t)]
>>> loss = criterion(pred, target)
__init__(grad_weight=0.5, base_loss='mse', huber_delta=1.0)[source]
forward(pred, target)[source]
class phenonn.utils.evaluater.NMSELoss(*args: Any, **kwargs: Any)[source]

Bases: Module

Normalized Mean Squared Error Loss.

Computes MSE normalized by the mean square of the target values. Useful when the scale of the target variable varies.

Parameters:

eps (float, optional) – Small constant for numerical stability. Default is 1e-8.

Examples

>>> criterion = NMSELoss()
>>> loss = criterion(predictions, targets)
__init__(eps=1e-08)[source]
forward(pred, target)[source]
class phenonn.utils.evaluater.NMAELoss(*args: Any, **kwargs: Any)[source]

Bases: Module

Normalized Mean Absolute Error Loss.

Computes MAE normalized by the mean absolute value of the target. Provides a scale-invariant error metric.

Parameters:

eps (float, optional) – Small constant for numerical stability. Default is 1e-8.

__init__(eps=1e-08)[source]
forward(pred, target)[source]
class phenonn.utils.evaluater.MetricTracker[source]

Bases: object

A utility class for tracking and computing statistics of metric values.

This class maintains a running average of metric values and provides methods to compute mean and root mean squared values.

value

Cumulative weighted sum of metric values

Type:

float

count

Total number of samples processed

Type:

int

Examples

>>> tracker = MetricTracker()
>>> tracker.update(10.0, 5)  # value=10.0, count=5 samples
>>> tracker.update(20.0, 3)  # value=20.0, count=3 samples
>>> print(tracker.getmean())  # (10*5 + 20*3) / (5+3) = 110/8 = 13.75
13.75
>>> print(tracker.getsqrtmean())  # sqrt(13.75)
3.7080992435478315
__init__()[source]

Initialize MetricTracker with zero values.

reset()[source]

Reset all tracked values to zero.

Return type:

None

update(value, count)[source]

Update the tracker with new metric values.

Parameters:
  • value (float) – The metric value to add

  • count (int) – Number of samples this value represents (weight)

Return type:

None

getmean()[source]

Calculate the mean of all tracked values.

Returns:

Weighted mean of all values: total_value / total_count

Return type:

float

Raises:

ZeroDivisionError – If no values have been added (count == 0)

getstd()[source]

Calculate the standard deviation of all tracked values.

Returns:

Weighted standard deviation of all values: sqrt(E(x^2) - (E(x))^2)

Return type:

float

Raises:

ZeroDivisionError – If no values have been added (count == 0)

getsqrtmean()[source]

Calculate the square root of the mean of all tracked values.

Returns:

Square root of the weighted mean: sqrt(total_value / total_count)

Return type:

float

Raises:

ZeroDivisionError – If no values have been added (count == 0)

phenonn.utils.evaluater.get_loss_function(loss_type, args, logger=None)[source]

Factory function to instantiate the requested loss function.

Parameters:
  • loss_type (str) – Type of loss function. Options: - ‘mse’: Mean Squared Error - ‘mae’: Mean Absolute Error - ‘nmae’: Normalized Mean Absolute Error - ‘nmse’: Normalized Mean Squared Error - ‘wmse’: Weighted Mean Squared Error - ‘logcosh’: Log-Cosh loss - ‘smoothl1’: Smooth L1 Loss (Huber-like) - ‘huber’: Huber Loss

  • args (argparse.Namespace) – Arguments containing loss-specific parameters (e.g., beta_delta for Huber).

Returns:

Initialized loss function.

Return type:

torch.nn.Module

Raises:

ValueError – If loss_type is not supported or required parameters are missing.

Examples

>>> args = argparse.Namespace(beta_delta=1.0)
>>> criterion = get_loss_function('huber', args)
phenonn.utils.evaluater.mse_all(pred, true)[source]

Compute Mean Squared Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, mse_value)

Return type:

tuple

phenonn.utils.evaluater.mbe_all(pred, true)[source]

Compute Mean Bias Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, mbe_value)

Return type:

tuple

phenonn.utils.evaluater.mae_all(pred, true)[source]

Compute Mean Absolute Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, mae_value)

Return type:

tuple

phenonn.utils.evaluater.r2_all(pred, true)[source]

Calculate R2 (coefficient of determination) between predicted and true values.

Computes the R2 metric and returns both the number of elements and the R2 value.

Parameters:
  • pred (torch.Tensor) – Predicted values from the model

  • true (torch.Tensor) – Ground truth values

Returns:

(num_elements, r2_value) where: - num_elements (int): Total number of elements in the tensors - r2_value (torch.Tensor): R2 score

Return type:

tuple

Notes

R2 is calculated as:

R2 = 1 - sum((true - pred)^2) / sum((true - mean(true))^2)

This implementation is fully torch-based and works on CPU and GPU.

phenonn.utils.evaluater.nmae_all(pred, true)[source]

Compute Normalized Mean Absolute Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, nmae_value)

Return type:

tuple

phenonn.utils.evaluater.nmse_all(pred, true)[source]

Compute Normalized Mean Squared Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, nmse_value)

Return type:

tuple

phenonn.utils.evaluater.mare_all(pred, true)[source]

Compute Mean Absolute Relative Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, mare_value)

Return type:

tuple

phenonn.utils.evaluater.gmrae_all(pred, true)[source]

Compute Geometric Mean Relative Absolute Error.

Parameters:
  • pred (torch.Tensor) – Predictions.

  • true (torch.Tensor) – Ground truth.

Returns:

(num_elements, gmrae_value)

Return type:

tuple

Phenonn Model Loader

Factory module for instantiating deep learning models.

This module provides a unified interface to build different neural network architectures (RNNs, Transformers, FCNs, and linear baselines) and automatically wraps them to ensure consistent output formatting for training and evaluation pipelines.

Design

All models are constructed as base networks and then wrapped using lightweight adapters that enforce consistent output shapes:

  • SingleDayWrapper:

    Ensures output shape (batch, 1) for standard single-day prediction.

  • LastNDaysWrapper:

    Returns predictions over multiple target days (used in gradient loss or full-sequence forecasting).

  • permuteWrapper:

    Handles tensor dimension reordering for transformer-based models.

  • Special cases:

    Some models (e.g. linear baselines) are returned without wrapping because they already produce correctly shaped outputs.

Supported models

  • RNN-based models:
    • LSTM (RNN_LSTM)

    • GRU (RNN_GRU)

    • 1-year LSTM variant (sequence-to-sequence style)

  • Transformer models:
    • Encoder-only Transformer (EncoderTorch)

    • BiTransformer variants

    • Combined Transformer-RNN hybrid (transformerbis)

  • Feed-forward models:
    • FCN (Fully Connected Network)

  • Linear baselines:
    • LinearBaseline

    • PerDayLinearBaseline

Wrapper logic

The final wrapper depends on training configuration:

  • args.n_target_days > 1:

    → LastNDaysWrapper is used (multi-day regression / gradient loss)

  • otherwise:

    → SingleDayWrapper is used (standard single-step prediction)

Special cases: - 1-year models automatically return LastNDaysWrapper(365)

param args:

Configuration object containing at least:

typestr

Model type identifier (e.g., ‘lstm’, ‘transformer’, ‘fcn’).

feature_channelint

Number of input features (meteorology + cyclic + static + PFT).

output_channelint

Number of output targets (typically 1 for LAI/GCC).

seq_lengthint

Input sequence length (e.g., 365 days).

Plus architecture-specific hyperparameters:

hidden_size, num_layers, embed_size, nhead, dropout, etc.

type args:

argparse.Namespace or EasyDict

returns:

Wrapped model ready for training. Output shape depends on wrapper:

  • (batch, 1) for SingleDayWrapper

  • (batch, n_target_days) for LastNDaysWrapper

rtype:

nn.Module

raises ValueError:

If args.type does not match any supported architecture.

Notes

  • This module standardizes heterogeneous architectures under a single training interface.

  • Wrapping ensures compatibility with loss functions and dataset outputs.

  • Transformer-based models may internally permute tensor dimensions using permuteWrapper.

phenonn.utils.model_loader.load_model(args)[source]

Instantiate and wrap a model for single-day GCC prediction.

Parameters:

args (argparse.Namespace or EasyDict) –

Must contain at minimum:

typestr

One of ‘lstm’, ‘gru’, ‘transformer’, ‘fcn’, ‘fullyconnected’.

feature_channelint

Number of input feature channels (meteo + cyclic + static + PFT).

output_channelint

Number of output channels (1 for gcc_lowess).

seq_lengthint

Window length (365).

Plus architecture-specific parameters (see original model_loader).

Returns:

Model whose forward returns (batch, output_channel).

Return type:

SingleDayWrapper

Model utility functions for PyTorch training workflows.

This module provides a collection of helper utilities for inspecting models, analyzing parameter distributions, and managing checkpoints during training. It is designed to standardize common operations such as saving/loading model states, logging architecture details, and maintaining reproducible training artifacts.

The utilities support both single-GPU and multi-GPU (DataParallel) setups and include safeguards for compatibility when loading checkpoints across different hardware configurations.

Features

  • Parameter counting (total and trainable)

  • Layer-wise parameter inspection

  • Model structure logging

  • Checkpoint saving and loading

  • Full training state persistence

  • Emergency checkpointing for crash recovery

  • DataParallel-aware state dictionary handling

Notes

  • Checkpoints include both model weights and optimizer states to enable seamless training resumption.

  • File naming conventions are automatically adapted based on checkpoint type (e.g., epoch, best, final, emergency).

  • When using torch.nn.DataParallel, the module automatically adjusts state dictionary keys to ensure compatibility between wrapped and unwrapped models.

Dependencies

  • torch

  • os

  • datetime

Examples

Basic usage:

>>> from model_utils import ModelUtils
>>> counts = ModelUtils.get_parameter_number(model)
>>> ModelUtils.print_model_layers(model)

Saving and loading checkpoints:

>>> state = {
...     "state_dict": model.state_dict(),
...     "optimizer": optimizer.state_dict(),
...     "epoch": epoch,
... }
>>> ModelUtils.save_checkpoint(state, "checkpoint.pth.tar")
>>> checkpoint = torch.load("checkpoint.pth.tar")
>>> ModelUtils.load_checkpoint(checkpoint, model, optimizer)

Saving a full training checkpoint:

>>> ModelUtils.save_training_checkpoint(
...     model, optimizer, epoch, samples_processed, batches_processed,
...     train_loss_history, valid_loss_history, valid_metrics_history,
...     best_val_loss, best_epoch, avg_val_loss, avg_epoch_loss,
...     args, paths, logger, checkpoint_type="best"
... )
class phenonn.utils.model_utils.ModelUtils[source]

Bases: object

Utility class for model inspection, checkpointing, and memory profiling.

This class provides static methods for common model operations including parameter counting, memory usage analysis, checkpoint management, and model inspection.

Examples

>>> utils = ModelUtils()
>>> param_counts = ModelUtils.get_parameter_number(model)
>>> ModelUtils.save_checkpoint(state, "checkpoint.pth.tar", logger)
__init__()[source]

Initialize ModelUtils instance.

static get_parameter_number(model, logger=None)[source]

Calculate the total and trainable number of parameters in a model.

Parameters:
  • model (torch.nn.Module) – PyTorch model to inspect

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Returns:

Dictionary containing: - ‘Total’: Total number of parameters - ‘Trainable’: Number of trainable parameters

Return type:

dict

Examples

>>> model = torch.nn.Linear(10, 5)
>>> counts = ModelUtils.get_parameter_number(model, logger)
static print_model_layers(model, logger=None)[source]

Print model parameter names along with their gradient requirements.

Parameters:
  • model (torch.nn.Module) – PyTorch model to inspect

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> model = torch.nn.Sequential(
...     torch.nn.Linear(10, 5),
...     torch.nn.ReLU(),
...     torch.nn.Linear(5, 1)
... )
>>> ModelUtils.print_model_layers(model, logger)
static save_checkpoint(state, filename='checkpoint.pth.tar', logger=None)[source]

Save model and optimizer state to a file.

Parameters:
  • state (dict) – Dictionary containing model state_dict and other training information. Typically includes: - ‘state_dict’: Model parameters - ‘optimizer’: Optimizer state - ‘epoch’: Current epoch - ‘loss’: Current loss value

  • filename (str, optional) – File path to save the checkpoint, by default “checkpoint.pth.tar”

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> state = {
...     'state_dict': model.state_dict(),
...     'optimizer': optimizer.state_dict(),
...     'epoch': epoch,
...     'loss': loss
... }
>>> ModelUtils.save_checkpoint(state, 'model_checkpoint.pth.tar', logger)
static load_checkpoint(checkpoint, model, optimizer=None, logger=None)[source]

Load model and optimizer state from a checkpoint file.

Parameters:
  • checkpoint (dict) – Loaded checkpoint dictionary

  • model (torch.nn.Module) – Model to load weights into

  • optimizer (torch.optim.Optimizer, optional) – Optimizer to restore state, by default None

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Examples

>>> checkpoint = torch.load('model_checkpoint.pth.tar')
>>> ModelUtils.load_checkpoint(checkpoint, model, optimizer, logger)
static load_training_checkpoint(checkpoint_path, model, optimizer, device, logger=None)[source]

Load comprehensive training checkpoint.

Parameters:
  • checkpoint_path (str) – Path to checkpoint file

  • model (torch.nn.Module) – Model to load weights into

  • optimizer (torch.optim.Optimizer) – Optimizer to restore state

  • device (torch.device) – Device to load checkpoint to

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output

Returns:

(epoch, samples_processed, batches_processed, best_val_loss, best_epoch, checkpoint)

Return type:

tuple

static count_parameters_by_layer(model, logger=None)[source]

Count parameters for each layer in the model.

Parameters:
  • model (torch.nn.Module) – PyTorch model to analyze

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

Returns:

Dictionary with layer names as keys and parameter counts as values

Return type:

dict

Examples

>>> layer_params = ModelUtils.count_parameters_by_layer(model, logger)
static log_model_summary(model, input_shape=None, logger=None)[source]

Log comprehensive model summary including parameters and architecture.

Parameters:
  • model (torch.nn.Module) – PyTorch model to summarize

  • input_shape (tuple, optional) – Input shape for memory analysis, by default None

  • logger (phenonn.utils.logger.Logger, optional) – phenonn.utils.logger.Logger instance for output, by default None

static save_training_checkpoint(model, optimizer, epoch, samples_processed, batches_processed, train_loss_history, valid_loss_history, valid_metrics_history, best_val_loss, best_epoch, avg_val_loss, avg_epoch_loss, args, paths, logger, checkpoint_type='epoch', save_full_model=True)[source]

Save comprehensive training checkpoint with consistent formatting.

Parameters:
  • model (torch.nn.Module) – Model to save

  • optimizer (torch.optim.Optimizer) – Optimizer to save

  • epoch (int) – Current epoch

  • samples_processed (int) – Number of samples processed so far

  • batches_processed (int) – Number of batches processed so far

  • train_loss_history (list) – History of training losses

  • valid_loss_history (list) – History of validation losses

  • valid_metrics_history (dict) – History of validation metrics

  • best_val_loss (float) – Best validation loss so far

  • best_epoch (int) – Epoch with best validation loss

  • avg_val_loss (float) – Current epoch validation loss

  • avg_epoch_loss (float) – Current epoch training loss

  • args (argparse.Namespace) – Command line arguments

  • paths (EasyDict) – Directory paths

  • logger (phenonn.utils.logger.Logger) – phenonn.utils.logger.Logger instance

  • checkpoint_type (str) – Type of checkpoint: “samples”, “epoch”, “best”, “final”

  • save_full_model (bool) – Whether to also save the full model separately

Returns:

(checkpoint_filename, full_model_filename)

Return type:

tuple

Examples

>>> checkpoint_file, full_model_file = ModelUtils.save_training_checkpoint(
...     model, optimizer, epoch, samples_processed, batches_processed,
...     train_loss_history, valid_loss_history, valid_metrics_history,
...     best_val_loss, best_epoch, avg_val_loss, avg_epoch_loss,
...     args, paths, logger, checkpoint_type="best"
... )
static save_emergency_checkpoint(model, optimizer, epoch, samples_processed, batches_processed, train_loss_history, valid_loss_history, valid_metrics_history, args, paths, logger, reason='emergency')[source]

Save emergency checkpoint for recovery.

Parameters:

reason (str) – Reason for emergency save (e.g., “crash”, “interrupt”, “error”)

Returns:

(checkpoint_filename, full_model_filename)

Return type:

tuple

RTnn Output Wrappers for Phenonn GCC Prediction

This module defines lightweight PyTorch wrappers that adapt RTnn-style sequence models to standardized prediction targets used in Phenonn GCC / LAI regression tasks.

Most RTnn models produce dense sequence outputs of shape:

(batch, output_channel, seq_length)

However, downstream training and evaluation typically require simplified or structured outputs such as:

  • Single-day prediction: (batch, output_channel)

  • Multi-day regression: (batch, output_channel, N)

  • Irregular observation days: (batch, output_channel, 36)

This module provides wrappers to convert full-sequence outputs into these standardized formats without modifying the underlying models.

Core Idea

All wrappers assume a base model with signature:

(batch, C_in, L) → (batch, C_out, L)

and apply deterministic slicing or permutation to produce task-specific outputs.

Classes

SingleDayWrapper

Extracts the last timestep of the sequence output, producing a single prediction per sample.

permuteWrapper

Reorders input/output dimensions to match RTnn conventions when data is provided in (batch, seq_length, feature_channels) format.

LastNDaysWrapper

Extracts the last N timesteps of the model output for temporal consistency losses (e.g., gradient-based or multi-step supervision).

Every10DaysWrapper

Selects predictions at fixed phenological observation days (days 5, 15, 25 of each month → 36 total points per year).

Constants

_OBS_POSITIONSlist[int]

Precomputed 0-indexed day-of-year positions (length = 36) corresponding to LAI observation dates in a non-leap year.

Design Rationale

  • Keeps RTnn models unchanged and reusable across tasks

  • Centralizes output-shaping logic in a single module

  • Ensures consistent tensor shapes across training, validation, and metrics

  • Enables flexible supervision (single-step, multi-step, sparse observations)

Notes

  • All wrappers inherit from torch.nn.Module

  • No learnable parameters are added (pure structural transforms)

  • Assumes base models return full sequence outputs

  • Indexing is deterministic and non-learned

class phenonn.utils.wrappers.SingleDayWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Wraps any RTnn model to output a single scalar per sample.

Takes the last timestep of the wrapped model’s sequence output, yielding shape (batch, output_channel) instead of (batch, output_channel, seq_length).

Parameters:

base_model (nn.Module) – Any model with forward signature (batch, C_in, L) -> (batch, C_out, L).

Examples

>>> from rtnn.models.rnn import RNN_LSTM
>>> base = RNN_LSTM(feature_channel=14, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = SingleDayWrapper(base)
>>> x = torch.randn(32, 14, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(base_model: torch.nn.Module) None[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x (torch.Tensor) – Input of shape (batch, feature_channels, seq_length).

Returns:

Output of shape (batch, output_channel).

Return type:

torch.Tensor

class phenonn.utils.wrappers.permuteWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Permutes input dimensions to match expected order for RTnn models.

RTnn models expect input shape (batch, feature_channels, seq_length). If your data is in (batch, seq_length, feature_channels), this wrapper permutes the dimensions before passing to the base model.

Parameters:

base_model (nn.Module) – Any model with forward signature (batch, C_in, L) -> (batch, C_out, L).

Examples

>>> from rtnn.models.rnn import RNN_LSTM
>>> base = RNN_LSTM(feature_channel=14, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = permuteWrapper(base)
>>> x = torch.randn(32, 365, 14)  # Note seq_length and feature_channels swapped
>>> y = model(x)
>>> y.shape
torch.Size([32, 1])
__init__(base_model)[source]
forward(x)[source]
class phenonn.utils.wrappers.LastNDaysWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Wraps any RTnn model to output the last N timesteps.

Takes the last N timesteps of the wrapped model’s sequence output, yielding shape (batch, output_channel, N) instead of (batch, output_channel, seq_length).

Used for gradient-aware loss where we need GCC(t) and GCC(t-1) while keeping the full input window for context.

Parameters:
  • base_model (nn.Module) – Any model with forward signature (batch, C_in, L) -> (batch, C_out, L).

  • n_days (int) – Number of trailing timesteps to keep (default: 2).

Examples

>>> base = RNN_LSTM(feature_channel=16, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = LastNDaysWrapper(base, n_days=2)
>>> x = torch.randn(32, 16, 365)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1, 2])
__init__(base_model: torch.nn.Module, n_days: int = 2) None[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channels, seq_length))

Return type:

(batch, output_channel, n_days)

class phenonn.utils.wrappers.Every10DaysWrapper(*args: Any, **kwargs: Any)[source]

Bases: Module

Wraps any RTnn model to output predictions at the 36 LAI observation days.

Extracts the last 365 timesteps of the model output, then selects the 36 positions corresponding to days 5, 15, 25 of each month in a non-leap year.

Input : (B, C_in, L) with L ≥ 365 Output : (B, C_out, 36)

Parameters:

base_model (nn.Module) – Any model with forward signature (B, C_in, L) -> (B, C_out, L).

Examples

>>> base = RNN_LSTM(feature_channel=31, output_channel=1,
...                 hidden_size=128, num_layers=2)
>>> model = Every10DaysWrapper(base)
>>> x = torch.randn(32, 31, 720)
>>> y = model(x)
>>> y.shape
torch.Size([32, 1, 36])
__init__(base_model: torch.nn.Module) None[source]
forward(x: torch.Tensor) torch.Tensor[source]
Parameters:

x ((batch, feature_channels, seq_length) — seq_length ≥ 365)

Return type:

(batch, output_channel, 36)

CLI Module

PhenoNN Command Line Interface

Usage examples:

# Train LSTM model (per-site CSV format) phenonn train –data_dir ./data/DB/ –type lstm –hidden_size 128

# Train on flat CSVs phenonn train-flat –features_csv features.csv –target_csv targets.csv

# Train big model with year-based split phenonn train-big –data_dir ./data/DB/ –type transformer –split_mode year

# Predict (per-site CSV format) phenonn predict –checkpoint ./runs/exp01/checkpoints/best_model.pth –data_dir ./data/DB/

# Predict on flat CSVs phenonn predict-flat –checkpoint ./runs/exp_flat/checkpoints/best_model.pth

# Show version phenonn –version

phenonn.cli.main()[source]

Version Module

Version information for PhenoNN.

phenonn.version.get_version()[source]

Return the version string.

phenonn.version.get_versions()[source]

Return a dictionary with version information.

EOF