Skip to content

Commit 48273f0

Browse files
committed
increase test coverage, pot. incorrect upsampler fix
1 parent 5eb6fb5 commit 48273f0

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

opensr_srgan/model/model_blocks/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ def _icnr_(weight: torch.Tensor, scale: int = 2) -> None:
275275
def make_upsampler(n_channels: int, scale: int, *, use_icnr: bool = False) -> nn.Sequential:
276276
"""Create a pixel-shuffle upsampler matching the flexible generator implementation."""
277277

278+
if scale < 1 or (scale & (scale - 1)) != 0:
279+
raise ValueError("scale must be a positive power of two (1, 2, 4, 8, ...).")
280+
278281
stages: list[nn.Module] = []
279282
for _ in range(int(math.log2(scale))):
280283
conv = nn.Conv2d(n_channels, n_channels * 4, 3, padding=1)

tests/test_deployment_naming.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
from deployment.srgan_hpc.naming import (
6+
fused_output_name,
7+
patch_dir,
8+
patch_output_name,
9+
product_output_name,
10+
resolve_run_dir,
11+
)
12+
13+
14+
def test_output_name_helpers_format_expected_names() -> None:
15+
assert patch_output_name(45.1234567, 9.9876543) == "output_SR_image_45.123457_9.987654.tif"
16+
assert product_output_name("rgbnir") == "rgbnir_sr.tif"
17+
assert fused_output_name() == "fused_sr.tif"
18+
19+
20+
def test_run_and_patch_directory_helpers_join_paths() -> None:
21+
output_root = Path("/tmp/srgan-runs")
22+
run_dir = resolve_run_dir(output_root, "run_001")
23+
assert run_dir == output_root / "run_001"
24+
25+
patch_path = patch_dir(run_dir, "patch_0001")
26+
assert patch_path == output_root / "run_001" / "patches" / "patch_0001"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
torch = pytest.importorskip("torch")
6+
7+
from opensr_srgan.model.model_blocks import (
8+
ConvolutionalBlock,
9+
DenseBlock5,
10+
LKA,
11+
LKAResBlock,
12+
RCAB,
13+
RRDB,
14+
ResidualBlock,
15+
ResidualBlockNoBN,
16+
SubPixelConvolutionalBlock,
17+
make_upsampler,
18+
)
19+
from opensr_srgan.model.model_blocks import _icnr_
20+
21+
22+
def test_convolutional_block_rejects_unknown_activation() -> None:
23+
with pytest.raises(AssertionError, match="activation must be one of"):
24+
ConvolutionalBlock(16, 16, 3, activation="relu")
25+
26+
27+
def test_convolutional_block_tanh_path_keeps_spatial_shape() -> None:
28+
block = ConvolutionalBlock(16, 16, 3, batch_norm=True, activation="tanh")
29+
x = torch.randn(2, 16, 8, 8)
30+
y = block(x)
31+
assert y.shape == x.shape
32+
33+
34+
def test_subpixel_block_upsamples_by_scaling_factor() -> None:
35+
block = SubPixelConvolutionalBlock(n_channels=16, scaling_factor=2)
36+
x = torch.randn(1, 16, 8, 8)
37+
y = block(x)
38+
assert y.shape == (1, 16, 16, 16)
39+
40+
41+
def test_residual_and_attention_blocks_preserve_shape() -> None:
42+
x = torch.randn(1, 16, 8, 8)
43+
44+
assert ResidualBlock(n_channels=16)(x).shape == x.shape
45+
assert ResidualBlockNoBN(n_channels=16)(x).shape == x.shape
46+
assert RCAB(n_channels=16)(x).shape == x.shape
47+
assert DenseBlock5(n_features=16, growth_channels=8)(x).shape == x.shape
48+
assert RRDB(n_features=16, growth_channels=8)(x).shape == x.shape
49+
assert LKA(n_channels=16)(x).shape == x.shape
50+
assert LKAResBlock(n_channels=16)(x).shape == x.shape
51+
52+
53+
def test_icnr_requires_divisible_output_channels() -> None:
54+
weight = torch.empty(10, 4, 3, 3)
55+
with pytest.raises(ValueError, match=r"divisible by scale\*\*2"):
56+
_icnr_(weight, scale=2)
57+
58+
59+
def test_make_upsampler_scale_4_with_icnr_produces_expected_shape() -> None:
60+
upsampler = make_upsampler(16, scale=4, use_icnr=True)
61+
x = torch.randn(1, 16, 8, 8)
62+
y = upsampler(x)
63+
assert y.shape == (1, 16, 32, 32)
64+
65+
66+
def test_make_upsampler_rejects_non_power_of_two_scale() -> None:
67+
with pytest.raises(ValueError, match="power of two"):
68+
make_upsampler(16, scale=3)

0 commit comments

Comments
 (0)