77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
|
|
"""ceiling.py's reference blends, which are per band: a frame may lack some bands."""
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
torch = pytest.importorskip("torch")
|
||
|
|
|
||
|
|
import ceiling # noqa: E402
|
||
|
|
from suvi import model, samples # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
def conditioning(dts, states=None):
|
||
|
|
states = states or ["available"] * len(dts)
|
||
|
|
return torch.stack([model.frame_conditioning(state, True, dt)
|
||
|
|
for state, dt in zip(states, dts)])[None]
|
||
|
|
|
||
|
|
|
||
|
|
def test_prior_weights_normalise_per_band_over_the_frames_holding_it():
|
||
|
|
cond = conditioning([-240.0, -2400.0, 240.0])[0].numpy()
|
||
|
|
usable = np.ones((3, 6), dtype=bool)
|
||
|
|
usable[0, 2] = False
|
||
|
|
weights = ceiling.prior_weights(cond, usable, decay=1.5)
|
||
|
|
assert weights.shape == (3, 6)
|
||
|
|
np.testing.assert_allclose(weights.sum(axis=0), 1.0)
|
||
|
|
assert weights[0, 2] == 0.0
|
||
|
|
assert weights[0, 0] > weights[1, 0] # recency still rules
|
||
|
|
np.testing.assert_allclose(weights[:, 0], weights[:, 1])
|
||
|
|
|
||
|
|
|
||
|
|
def test_prior_weights_ignore_satellite_and_direction():
|
||
|
|
"""Recency alone: the counterpart arrives gain-matched, and past and future are
|
||
|
|
equally good evidence."""
|
||
|
|
cond = torch.stack([model.frame_conditioning("available", same, dt)
|
||
|
|
for same, dt in ((True, -240.0), (False, 240.0))]).numpy()
|
||
|
|
weights = ceiling.prior_weights(cond, np.ones((2, 6), dtype=bool), decay=1.5)
|
||
|
|
np.testing.assert_allclose(weights[0], weights[1])
|
||
|
|
|
||
|
|
|
||
|
|
def test_blend_psnr_blends_each_band_with_its_own_weights():
|
||
|
|
"""Truth is frame 1 in every band but 3, where it is frame 0 (slightly off,
|
||
|
|
so every PSNR is finite): the per-band mix must beat taking frame 1 whole."""
|
||
|
|
from suvi import metrics, paths
|
||
|
|
radiance = (1.0, 2.0)
|
||
|
|
candidates = np.stack([samples.encode_for_model(np.full((6, 8, 8), r, np.float32))
|
||
|
|
for r in radiance])
|
||
|
|
truth = np.full((6, 8, 8), radiance[1] * 1.02, np.float32)
|
||
|
|
truth[3] = radiance[0] * 1.02
|
||
|
|
truth_display = [metrics.to_display(t, w) for t, w in zip(truth, paths.WAVELENGTHS)]
|
||
|
|
whole = np.zeros((2, 6))
|
||
|
|
whole[1] = 1.0
|
||
|
|
mixed = whole.copy()
|
||
|
|
mixed[:, 3] = (1.0, 0.0)
|
||
|
|
assert (ceiling.blend_psnr(candidates, mixed, truth_display)
|
||
|
|
> ceiling.blend_psnr(candidates, whole, truth_display) + 5.0)
|
||
|
|
|
||
|
|
|
||
|
|
def test_score_target_declines_when_a_band_has_no_usable_frame():
|
||
|
|
aligned = torch.zeros(1, 2, 6, 8, 8)
|
||
|
|
condition = conditioning([-240.0, 240.0])
|
||
|
|
present = torch.ones(1, 2, 6)
|
||
|
|
present[0, :, 4] = 0.0
|
||
|
|
truth = np.ones((6, 8, 8), np.float32)
|
||
|
|
assert ceiling.score_target(aligned, condition, present, truth, (1.5,)) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_score_target_scores_a_partial_stack_on_the_bands_held():
|
||
|
|
aligned = torch.full((1, 2, 6, 8, 8), 0.2)
|
||
|
|
aligned[0, 0, 4] = 0.9 # absent band, stand-in pixels
|
||
|
|
condition = conditioning([-240.0, 240.0])
|
||
|
|
present = torch.ones(1, 2, 6)
|
||
|
|
present[0, 0, 4] = 0.0
|
||
|
|
truth = samples.decode_from_model(np.full((6, 8, 8), 0.2, np.float32))
|
||
|
|
scores = ceiling.score_target(aligned, condition, present, truth, (1.5,))
|
||
|
|
assert scores["best1"] == float("inf")
|
||
|
|
assert scores["uniform"] == float("inf") # the 0.9 never blended
|
||
|
|
assert scores["prior[1.5]"] == float("inf")
|