Claude
feat: Add .quad file upload, curve modification, and AI enhancement
9aa8676 unverified
"""
Tests for the QTR .quad file parser.
"""
import pytest
from pathlib import Path
from ptpd_calibration.curves.parser import (
QuadProfile,
QuadFileParser,
ChannelCurve,
load_quad_file,
load_quad_string,
)
class TestChannelCurve:
"""Tests for ChannelCurve dataclass."""
def test_channel_curve_creation(self):
"""Test creating a channel curve."""
values = list(range(256))
curve = ChannelCurve(name="K", values=values)
assert curve.name == "K"
assert len(curve.values) == 256
assert curve.enabled is True
def test_as_normalized(self):
"""Test normalized output conversion."""
values = [0, 128, 255]
curve = ChannelCurve(name="K", values=values)
inputs, outputs = curve.as_normalized
assert len(inputs) == 3
assert len(outputs) == 3
assert inputs[0] == 0.0
assert inputs[-1] == pytest.approx(1.0, rel=0.01)
assert outputs[0] == 0.0
assert outputs[1] == pytest.approx(0.502, rel=0.01)
assert outputs[2] == 1.0
def test_to_curve_data(self):
"""Test conversion to CurveData model."""
values = [0, 64, 128, 192, 255]
curve = ChannelCurve(name="K", values=values)
curve_data = curve.to_curve_data(" - Test Profile")
assert "K - Test Profile" in curve_data.name
assert len(curve_data.input_values) == 5
assert len(curve_data.output_values) == 5
class TestQuadProfile:
"""Tests for QuadProfile dataclass."""
def test_profile_defaults(self):
"""Test default profile values."""
profile = QuadProfile()
assert profile.profile_name == "Untitled"
assert profile.resolution == 2880
assert profile.ink_limit == 100.0
assert len(profile.channels) == 0
def test_primary_channel(self):
"""Test primary channel accessor."""
profile = QuadProfile()
profile.channels["K"] = ChannelCurve(name="K", values=[0] * 256)
assert profile.primary_channel is not None
assert profile.primary_channel.name == "K"
def test_active_channels(self):
"""Test active channel detection."""
profile = QuadProfile()
profile.channels["K"] = ChannelCurve(name="K", values=[0, 10, 20, 30], enabled=True)
profile.channels["C"] = ChannelCurve(name="C", values=[0, 0, 0, 0], enabled=True)
profile.channels["M"] = ChannelCurve(name="M", values=[0, 5, 10, 15], enabled=False)
active = profile.active_channels
assert "K" in active
assert "C" not in active # All zeros
assert "M" not in active # Disabled
def test_get_channel(self):
"""Test channel retrieval."""
profile = QuadProfile()
profile.channels["K"] = ChannelCurve(name="K", values=[0] * 256)
assert profile.get_channel("K") is not None
assert profile.get_channel("k") is not None # Case insensitive
assert profile.get_channel("X") is None
def test_summary(self):
"""Test summary generation."""
profile = QuadProfile(profile_name="Test Profile", resolution=1440)
profile.channels["K"] = ChannelCurve(name="K", values=[i for i in range(256)])
summary = profile.summary()
assert "Test Profile" in summary
assert "1440" in summary
class TestQuadFileParser:
"""Tests for QuadFileParser."""
def test_parser_initialization(self):
"""Test parser initialization."""
parser = QuadFileParser()
assert parser._current_section is None
assert parser._profile is None
def test_parse_simple_quad_content(self):
"""Test parsing simple .quad content."""
content = """
[General]
ProfileName=Test Profile
Resolution=2880
InkLimit=95.0
[K]
0=0
1=5
2=10
255=255
"""
parser = QuadFileParser()
profile = parser.parse_string(content, "Test")
assert profile.profile_name == "Test Profile"
assert profile.resolution == 2880
assert profile.ink_limit == 95.0
assert "K" in profile.channels
def test_parse_multiple_channels(self):
"""Test parsing multiple channels."""
content = """
[General]
ProfileName=Multi-Channel
[K]
0=0
255=255
[C]
0=0
255=128
[M]
0=0
255=64
"""
parser = QuadFileParser()
profile = parser.parse_string(content, "Multi")
assert "K" in profile.channels
assert "C" in profile.channels
assert "M" in profile.channels
def test_parse_comments(self):
"""Test parsing comments."""
content = """
# This is a comment
; This is also a comment
[General]
ProfileName=Commented Profile
"""
parser = QuadFileParser()
profile = parser.parse_string(content)
assert len(profile.comments) == 2
assert "This is a comment" in profile.comments[0]
def test_parse_media_settings(self):
"""Test parsing media settings."""
content = """
[General]
MediaType=Glossy Photo
MediaSetting=Premium
"""
parser = QuadFileParser()
profile = parser.parse_string(content)
assert profile.media_type == "Glossy Photo"
assert profile.media_setting == "Premium"
def test_parse_curve_values_in_range(self):
"""Test that curve values are clamped to valid range."""
content = """
[K]
0=0
1=300
2=-10
"""
parser = QuadFileParser()
profile = parser.parse_string(content)
curve = profile.channels["K"]
assert curve.values[0] == 0
assert curve.values[1] == 255 # Clamped from 300
assert curve.values[2] == 0 # Clamped from -10
class TestConvenienceFunctions:
"""Tests for convenience functions."""
def test_load_quad_string(self):
"""Test load_quad_string function."""
content = """
[General]
ProfileName=String Profile
[K]
0=0
255=255
"""
profile = load_quad_string(content, "Named Profile")
assert profile.profile_name == "String Profile"
def test_load_quad_file_not_found(self, tmp_path):
"""Test load_quad_file with non-existent file."""
with pytest.raises(FileNotFoundError):
load_quad_file(tmp_path / "nonexistent.quad")
def test_load_quad_file(self, tmp_path):
"""Test load_quad_file with actual file."""
content = """
[General]
ProfileName=File Profile
Resolution=1440
[K]
0=0
128=100
255=255
"""
file_path = tmp_path / "test.quad"
file_path.write_text(content)
profile = load_quad_file(file_path)
assert profile.profile_name == "File Profile"
assert profile.resolution == 1440
assert profile.source_path == file_path
def test_to_curve_data_conversion(self):
"""Test converting profile channel to CurveData."""
content = """
[General]
ProfileName=Curve Test
MediaType=Arches Platine
[K]
0=0
64=48
128=96
192=168
255=255
"""
profile = load_quad_string(content)
curve_data = profile.to_curve_data("K")
assert "K" in curve_data.name
assert curve_data.paper_type == "Arches Platine"
assert len(curve_data.input_values) == 256
def test_to_curve_data_invalid_channel(self):
"""Test error when requesting invalid channel."""
content = """
[General]
ProfileName=Test
[K]
0=0
"""
profile = load_quad_string(content)
with pytest.raises(ValueError, match="not found"):
profile.to_curve_data("X")