Metrics¶
MSE
¶
Bases: Loss
Mean Squared Error. Per-pixel (y - x)**2 averaged over
non-batch dims, honouring optional y_mask. Inherits the
default calculate_score — only overrides pointwise.
PSNR
¶
Bases: MSE
Peak Signal-to-Noise Ratio. Higher is better.
Implements the Level-2 wrapping protocol by exposing
:meth:pointwise (inherited from :class:MSE, returns per-pixel
squared error). The PSNR-specific log10 transform lives in
:meth:calculate_score and runs after reduction, because
log10(mean(...)) is not commutable with per-pixel weighting.
A Level-2 wrapper like :class:UncertaintyLoss(PSNR()) is
accepted by construction (the pointwise method exists), but
numerically equals :class:UncertaintyLoss(MSE()) — the
log10 happens after the wrapper's reduction, not before. If
that's not the semantics you want, override :meth:pointwise
explicitly in a subclass.
pointwise(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor
¶
Per-pixel squared error — same as MSE. Exposed explicitly (rather than implicit via MRO) so PSNR's participation in the Level-2 wrapping protocol is discoverable from this class.
Charbonnier
¶
Bases: Loss
Masked Charbonnier loss (smooth L1). Lower is better. Inherits
the default calculate_score — only overrides pointwise.
L1
¶
TotalVariation
¶
Bases: Loss
Total Variation (TV) metric/penalty for images or feature maps.
TV encourages spatial smoothness by penalizing differences between neighboring
pixels. With anisotropic=True, it sums absolute horizontal and vertical
gradients (|dx| + |dy|). With anisotropic=False, it uses the isotropic form
sqrt(dx^2 + dy^2), which is rotation-invariant. Lower values indicate smoother
results. Supports optional spatial masks and returns a per-sample scalar
normalized by the number of valid pixels.
Parameters¶
anisotropic : bool, optional (default: False)
If True, use anisotropic TV; if False, use isotropic TV.
**kwargs : Any
Forwarded to the base Loss class.
Inputs¶
x : torch.Tensor Tensor of shape [B, C, H, W] to regularize (e.g., an image or residual). y : torch.Tensor or None Unused placeholder for API compatibility. mask : torch.Tensor or None Optional binary/soft mask of shape [B, 1, H, W] (or [B, H, W]) defining valid pixels used for normalization.
Returns¶
torch.Tensor Tensor of shape [B] with the TV value per sample; lower is better.
calculate_score(x: torch.Tensor, y: Optional[torch.Tensor] = None, mask: Optional[torch.Tensor] = None)
¶
x: [B, C, H, W] mask (optional): [B, 1, H, W] or [B, H, W]; 1 = valid Returns per-sample TV normalized by the number of valid gradient locations.
UncertaintyLoss
¶
Bases: Loss
Heteroscedastic-NLL data term for any pointwise base loss.
Multiplies the base's per-element error by exp(-log_var) (the
predicted precision, since precision = 1/variance =
exp(-log_var)). The result is the data term of the
heteroscedastic NLL:
::
UncertaintyLoss(x, y, log_var) = prefactor · base.pointwise(x, y) · exp(-log_var)
Note: the parameter is named log_var because that's
semantically what the network predicts — the log of the variance.
log_var = log(σ²), so exp(-log_var) = 1/σ².
The +log_var regularizer (the log-determinant of the Gaussian /
Laplacian density) is NOT included — pair this with a
:class:Regularizer (penalty="identity") on the same
log_var field in the same combiner. Splitting the data term
and the regularizer makes shared-uncertainty multi-task setups
composable without double-counting the regularizer when several
base metrics share one log_var.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_metric
|
Loss
|
A :class: |
required |
family
|
str
|
Selects the NLL prefactor.
The prefactor is a multiplicative constant; it doesn't
change the optimum, only the numerical loss value. Pick
|
'laplace'
|
logvar_clamp
|
tuple
|
|
(-14.0, 14.0)
|
weight
|
float
|
Scalar weight passed to the base |
1.0
|
**kwargs
|
Forwarded to the base |
{}
|
Example (YAML)::
loss:
_target: srforge.loss.LossCombiner
params:
losses:
- _target: srforge.loss.metrics.UncertaintyLoss
params:
base_metric: {_target: srforge.loss.metrics.MSE}
family: gauss
io: {inputs: {x: sr, y: hr, log_var: head_log_var}}
- _target: srforge.loss.metrics.Regularizer
params: {penalty: identity}
io: {inputs: {x: head_log_var}}
pointwise(x: torch.Tensor, y: torch.Tensor, log_var: torch.Tensor) -> torch.Tensor
¶
Per-pixel data term:
prefactor · base.pointwise(x, y) · exp(-log_var).
log_var is clamped to :attr:logvar_clamp first (if
enabled) to prevent exp(-log_var) from over/underflowing.
The +log_var regularizer is NOT included — pair this with
a :class:Regularizer (penalty="identity") on the same
log_var field.
calculate_score(x: torch.Tensor, y: torch.Tensor, log_var: torch.Tensor, y_mask: torch.Tensor = None) -> torch.Tensor
¶
Custom :meth:calculate_score because of the extra
log_var argument. Pointwise → masked-reduce flow with
log_var threaded through to :meth:pointwise.
Regularizer
¶
Bases: Loss
Single-tensor magnitude penalty.
Takes one tensor field, applies a pointwise penalty function, and
reduces the result to a per-sample scalar via :meth:Loss.reduce.
Useful whenever you want to penalise the magnitude (or some
function of the magnitude) of an auxiliary model output as part
of the total loss.
Built-in penalties:
============== ============================ ==================================
penalty= Function applied to x Common name
============== ============================ ==================================
"identity" x Mean of the field
"abs" |x| L1 norm / sparsity
"square" x² L2 norm / magnitude
"exp" exp(x) Penalises positive log-quantities
"entropy" -x·log(x) Element-wise entropy
"huber" smooth-L1 with huber_delta Quadratic near 0, linear elsewhere
callable any Tensor → Tensor Custom — same-shape output required
============== ============================ ==================================
Multiple :class:Regularizer instances can be combined in a
:class:LossCombiner with different penalties and/or fields —
each is a regular Loss with its own weight and IO binding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
penalty
|
Either a built-in name (see table) or a callable
|
'identity'
|
|
huber_delta
|
float
|
Transition point for |
1.0
|
Example::
# L1 sparsity penalty on an attention map
Regularizer(penalty="abs").set_io({"inputs": {"x": "attention"}})
# L2 magnitude penalty (with mask)
Regularizer(penalty="square").set_io(
{"inputs": {"x": "noise_pred", "y_mask": "valid_mask"}}
)
# Huber with custom delta
Regularizer(penalty="huber", huber_delta=0.5)
# Custom — log-L1 (robust soft-L1)
Regularizer(penalty=lambda x: torch.log1p(x.abs()))
CorrectedLoss
¶
Bases: Loss
Shift- and photometrically-corrected wrapper around any base
:class:Loss.
Modern wrapper analogue of :class:cLossCombiner. Same math, but
applied to a single base loss (rather than a combined batch),
and using :func:torch.Tensor.unfold for the shift extraction —
no Python-level for-loop, no intermediate torch.stack copy.
Per sample:
- Slice the central SR patch (
border-pixel margin on each spatial side). - Extract all
(2*border+1)²shifted HR patches via a single view-basedunfold. - Optional photometric correction:
b = mean(hr_patch - sr_patch)is added tosr_patchso per-shift intensity offset is normalised out. - Build a synthetic :class:
~srforge.data.Entrywith canonical field names (:data:_PRED_FIELD, :data:_TARGET_FIELD, :data:_MASK_FIELD) and call the base loss via its standard__call__/forwardpipeline. The base's own IO binding routes those fields to its parameters — no manual parameter-name resolution needed in this wrapper. - Pick per-pixel
min(ifbase.best_min) ormaxover shifts — i.e. each pixel gets credited for its best-aligned shift.
Two IO bindings, two roles:
- Outer
io(on :class:CorrectedLossitself) maps the caller's :class:Entryfields tox/y/y_mask— same as any other Loss. - Inner
io(onbase) maps the base's parameters to the canonical field names listed below. For bases with canonical(x, y, y_mask)parameter names, the default identity map suffices and no inneriois needed. For bases with custom parameter names, declare an innerioblock routing them to the canonical field names.
Example with a base that uses (sr, hr, mask) naming::
loss:
_target: srforge.loss.metrics.CorrectedLoss
params:
base_metric:
_target: my_module.MyLoss # params: (sr, hr, mask)
io:
inputs: # ← inner IO routes
sr: x # base params to
hr: y # canonical inner
mask: y_mask # field names
border: 3
io:
inputs: {x: pred_field, y: hr_field, y_mask: valid_mask}
The output equals
cLossCombiner([base_metric]).calculate_score(...)[base_metric.name]
modulo floating-point rounding (verified by
tests/loss/test_corrected_loss.py). Prefer this wrapper for
new code; keep :class:cLossCombiner for backward compatibility
and the rare case of N corrected losses sharing one shift loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_metric
|
Loss
|
The base :class: |
required |
border
|
int
|
How many pixels on each spatial side define the
shift range. |
3
|
do_correction
|
bool
|
Apply the additive photometric bias before
the base-loss call. Default |
True
|
MGE
¶
Bases: Loss
Mean Gradient Error (Sobel magnitude) with channel control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
per_channel
|
bool
|
If True, compare gradients per-channel. If False, first sum channels -> 1 channel, then compare. |
True
|
square_diff
|
bool
|
If True, use squared difference; else absolute difference. |
True
|
eps
|
float
|
Small constant in gradient magnitude. |
1e-06
|
Inputs to calculate_score:
x: Tensor (B,C,H,W) - prediction
y: Tensor (B,C,H,W) - reference/target
y_mask (optional): (B,C,H,W) or (B,1,H,W) mask in {0,1} (or float in [0,1]).
If per_channel=False and mask has C>1, it will be merged to (B,1,H,W).
SAM
¶
Bases: Loss
__init__(eps: float = 1e-08, clamp: float = 1.0 - 1e-07, unit: Literal['radians', 'degrees'] = 'radians', **kwargs)
¶
Spectral Angle Mapper (SAM) loss.
SAM measures the spectral similarity between two multispectral images by computing, per pixel, the angle between the spectral vectors (across bands) and then averaging over spatial dimensions.
Supports two input formats
1) Dict-of-bands: Dict[str, Tensor] where each value is [B,H,W] or [B,1,H,W] (bands are aligned by key; only common keys are used). 2) Stacked tensor: Tensor of shape [B,C,H,W] where C is the number of bands.
Parameters¶
eps : float Small constant to avoid division by zero in the cosine computation. clamp : float Clamp value applied to cosine similarity before acos to avoid numerical issues. Cosine is clamped to [-clamp, clamp]. unit : Literal['radians', 'degrees'] Output angle units. 'radians' returns angles in radians, 'degrees' converts to degrees.
Notes¶
- Output is one value per sample: Tensor[B].
- Smaller values indicate better spectral alignment (best_min = True).
pointwise(x: DictOrTensor, y: DictOrTensor) -> torch.Tensor
¶
Per-pixel spectral angle, in radians, shape [B, 1, H, W].
The channel/spectral dim is collapsed because the angle is
computed BETWEEN the spectral vectors at each (h, w) —
a single scalar per spatial position. The leading 1 is kept
(rather than returning [B, H, W]) so the output preserves
the input's dimensionality and integrates with reduce()
+ any future per-pixel modifier (UncertaintyLoss, etc.)
without dimension-juggling.
Unit conversion to degrees happens AFTER reduction in
:meth:calculate_score; the pointwise output is always in
radians so wrappers see a consistent scale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x, y
|
Either |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If x and y have different types, unsupported shapes, or share no common band keys. |
calculate_score(x: DictOrTensor, y: DictOrTensor) -> torch.Tensor
¶
Per-sample SAM angle in the requested unit.
Override (rather than inherit the default) because the
signature accepts DictOrTensor instead of Tensor,
AND the optional radians→degrees post-conversion happens
AFTER reduction.
Returns¶
Tensor
[B], in radians or degrees per self.unit.