Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDE files
.spyderproject
.spyproject
.ropeproject
.idea/
.vscode/
*.swp
*.swo
*~

# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini

# Linux
.directory
.Trash-*

# Claude
.claude/*

# Model and data files
*.pth
*.pt
*.pkl
*.h5
*.hdf5
model_zoo/*
!model_zoo/.gitkeep

# Temporary files
*.tmp
*.temp
*.log

# Testing artifacts
.pytest_cache/
.coverage
htmlcov/
coverage.xml
.hypothesis/
pytest_cache/

# MyPy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# Note: Do not ignore poetry.lock or uv.lock files
3,065 changes: 3,065 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
[tool.poetry]
name = "usrnet"
version = "0.1.0"
description = "Deep Unfolding Network for Image Super-Resolution"
authors = ["USRNet Contributors"]
readme = "README.md"
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
torch = ">=1.4.0"
opencv-python = "^4.8.0"
numpy = "^1.24.0"
scipy = "^1.10.0"
matplotlib = "^3.7.0"
requests = "^2.31.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"


[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--cov=models",
"--cov=utils",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing:skip-covered",
"--cov-fail-under=80",
"--tb=short",
"--maxfail=1",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]
console_output_style = "progress"

[tool.coverage.run]
source = ["models", "utils"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/migrations/*",
"*/.tox/*",
"*/.venv/*",
"*/venv/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"def __str__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\(Protocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = false
fail_under = 80

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
3 changes: 3 additions & 0 deletions test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
# Run pytest with Poetry
poetry run pytest "$@"
Empty file added tests/__init__.py
Empty file.
144 changes: 144 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"""Shared pytest fixtures and configuration for USRNet tests."""

import os
import sys
import tempfile
import shutil
from pathlib import Path
from typing import Generator, Dict, Any

import pytest
import numpy as np
import torch

# Add project root to Python path
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))


@pytest.fixture
def project_root() -> Path:
"""Return the project root directory."""
return PROJECT_ROOT


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def mock_config() -> Dict[str, Any]:
"""Return a mock configuration dictionary for testing."""
return {
"scale_factor": 4,
"noise_level": 2.55,
"kernel_width": 0.6,
"blur_type": "gaussian_iso",
"batch_size": 1,
"device": "cpu",
"model_path": None,
}


@pytest.fixture
def sample_image_array() -> np.ndarray:
"""Create a sample image array for testing."""
# Create a simple 64x64 RGB image
image = np.random.randint(0, 255, size=(64, 64, 3), dtype=np.uint8)
return image


@pytest.fixture
def sample_tensor() -> torch.Tensor:
"""Create a sample PyTorch tensor for testing."""
# Create a batch of 1 RGB image with shape (1, 3, 64, 64)
tensor = torch.randn(1, 3, 64, 64)
return tensor


@pytest.fixture
def sample_kernel() -> np.ndarray:
"""Create a sample blur kernel for testing."""
# Create a simple 5x5 Gaussian-like kernel
kernel = np.array([
[1, 4, 6, 4, 1],
[4, 16, 24, 16, 4],
[6, 24, 36, 24, 6],
[4, 16, 24, 16, 4],
[1, 4, 6, 4, 1]
], dtype=np.float32)
kernel = kernel / kernel.sum() # Normalize
return kernel


@pytest.fixture
def mock_model_weights(temp_dir: Path) -> Path:
"""Create a mock model weights file."""
weights_path = temp_dir / "test_model.pth"
# Create a simple state dict
state_dict = {
"model_state": {"layer1.weight": torch.randn(64, 3, 3, 3)},
"optimizer_state": {},
"epoch": 100,
}
torch.save(state_dict, weights_path)
return weights_path


@pytest.fixture
def device() -> torch.device:
"""Return the appropriate PyTorch device for testing."""
return torch.device("cuda" if torch.cuda.is_available() else "cpu")


@pytest.fixture(autouse=True)
def reset_random_seeds():
"""Reset random seeds before each test for reproducibility."""
np.random.seed(42)
torch.manual_seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed(42)
torch.cuda.manual_seed_all(42)


@pytest.fixture
def capture_logs(caplog):
"""Fixture to capture log messages during tests."""
with caplog.at_level("DEBUG"):
yield caplog


# Markers for different test types
def pytest_configure(config):
"""Configure pytest with custom markers."""
config.addinivalue_line(
"markers", "unit: mark test as a unit test"
)
config.addinivalue_line(
"markers", "integration: mark test as an integration test"
)
config.addinivalue_line(
"markers", "slow: mark test as slow running"
)


# Optional: Skip slow tests unless explicitly requested
def pytest_collection_modifyitems(config, items):
"""Modify test collection to handle slow tests."""
if config.getoption("--run-slow"):
return
skip_slow = pytest.mark.skip(reason="need --run-slow option to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)


def pytest_addoption(parser):
"""Add custom command line options."""
parser.addoption(
"--run-slow", action="store_true", default=False, help="run slow tests"
)
Empty file added tests/integration/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions tests/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
# Run pytest with Poetry
poetry run pytest "$@"
Loading