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
149 changes: 149 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# 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/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
Pipfile.lock

# PEP 582
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

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

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# PyTorch
*.pth
*.pt

# Weights and Biases
wandb/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Claude Code settings
.claude/*
2,588 changes: 2,588 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "qualiclip"
version = "1.0.0"
description = "Quality-Aware Image-Text Alignment for Opinion-Unaware Image Quality Assessment"
authors = ["Lorenzo Agnolucci", "Leonardo Galteri", "Marco Bertini"]
readme = "README.md"
homepage = "https://github.com/miccunifi/QualiCLIP"
repository = "https://github.com/miccunifi/QualiCLIP"
license = "CC-BY-NC-4.0"
packages = [{include = "clip"}]

[tool.poetry.dependencies]
python = "^3.10"
torch = "2.1.1"
torchvision = "0.16.1"
pandas = "2.1.3"
matplotlib = "3.8.2"
pyyaml = "6.0.1"
dotmap = "1.3.30"
scipy = "1.11.4"
tqdm = "4.66.1"
ftfy = "6.1.1"
regex = "2023.10.3"
openpyxl = "3.1.2"
kornia = "0.7.1"
wandb = "0.16.1"
einops = "0.7.0"
scikit-learn = "1.3.2"
plotly = "5.18.0"
umap-learn = "0.5.5"

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


[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=clip",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-report=term-missing",
"--cov-fail-under=80"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests that take longer to run"
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning"
]

[tool.coverage.run]
source = ["clip"]
omit = [
"tests/*",
"*/tests/*",
"*/__pycache__/*",
"*/venv/*",
"*/.venv/*"
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:"
]
show_missing = true
precision = 2

[tool.coverage.html]
directory = "htmlcov"
Empty file added tests/__init__.py
Empty file.
125 changes: 125 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Shared pytest fixtures for QualiCLIP testing."""

import os
import tempfile
import shutil
from pathlib import Path
from typing import Generator, Dict, Any
import pytest
import torch
from PIL import Image
import numpy as np


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


@pytest.fixture
def sample_image() -> Image.Image:
"""Create a sample RGB image for testing."""
# Create a simple 224x224 RGB image with random colors
np.random.seed(42) # For reproducible tests
image_array = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8)
return Image.fromarray(image_array, mode='RGB')


@pytest.fixture
def sample_image_path(temp_dir: Path, sample_image: Image.Image) -> Path:
"""Save sample image to temporary file and return path."""
image_path = temp_dir / "sample_image.png"
sample_image.save(image_path)
return image_path


@pytest.fixture
def sample_tensor() -> torch.Tensor:
"""Create a sample normalized tensor for testing."""
torch.manual_seed(42) # For reproducible tests
return torch.randn(1, 3, 224, 224)


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


@pytest.fixture
def mock_model_config() -> Dict[str, Any]:
"""Mock model configuration for testing."""
return {
"embed_dim": 512,
"image_resolution": 224,
"vision_layers": 12,
"vision_width": 768,
"vision_patch_size": 32,
"context_length": 77,
"vocab_size": 49408,
"transformer_width": 512,
"transformer_heads": 8,
"transformer_layers": 12
}


@pytest.fixture
def mock_transforms():
"""Mock image transforms for testing."""
import torchvision.transforms as transforms
return transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(
mean=[0.48145466, 0.4578275, 0.40821073],
std=[0.26862954, 0.26130258, 0.27577711]
),
])


@pytest.fixture
def sample_batch_images(sample_tensor: torch.Tensor) -> torch.Tensor:
"""Create a batch of sample images for testing."""
return sample_tensor.repeat(4, 1, 1, 1) # Batch size of 4


@pytest.fixture
def mock_tokenizer_vocab(temp_dir: Path) -> Path:
"""Create a mock BPE vocabulary file for testing."""
vocab_path = temp_dir / "test_vocab.txt.gz"
# Create a minimal vocab file for testing
import gzip
with gzip.open(vocab_path, 'wt', encoding='utf-8') as f:
f.write("hello world\n")
f.write("test token\n")
return vocab_path


@pytest.fixture(autouse=True)
def cleanup_cuda():
"""Clean up CUDA memory after each test if available."""
yield
if torch.cuda.is_available():
torch.cuda.empty_cache()


@pytest.fixture
def set_random_seeds():
"""Set random seeds for reproducible testing."""
torch.manual_seed(42)
np.random.seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed(42)
torch.cuda.manual_seed_all(42)


@pytest.fixture
def original_working_dir():
"""Store and restore original working directory."""
original_dir = os.getcwd()
yield original_dir
os.chdir(original_dir)
Empty file added tests/integration/__init__.py
Empty file.
Loading