"""JPEG repair Task 1-2の契約テスト。""" from __future__ import annotations from io import BytesIO import numpy as np import pytest from PIL import Image from pixelmodel_robustness.jpeg_repair import ( CLIP_STRATEGIES, CONDITION_IDS, build_jpeg_registry, clip_candidate, decode_jpeg_rgb, decode_protected, diagnostics, encode_jpeg_q100, encode_protected, materialize_fp16_png, repair_nonfinite, semantic_hash, validate_layer_offsets, ) def _image() -> Image.Image: return Image.fromarray( np.array([[[0, 1, 2], [20, 40, 60]], [[100, 120, 140], [240, 220, 200]]], dtype=np.uint8), "RGB", ) def test_registry_is_exact_ordered_and_semantically_hashed() -> None: registry = build_jpeg_registry() assert tuple(entry.condition_id for entry in registry) == CONDITION_IDS assert len(registry) == 18 assert len({entry.parent_raw_sha256 for entry in registry[1:17]}) == 1 assert semantic_hash(registry) == semantic_hash(build_jpeg_registry()) assert tuple(entry.condition_id for entry in registry) == CONDITION_IDS def test_jpeg_q100_source_is_one_reusable_decode() -> None: raw = encode_jpeg_q100(_image()) first = decode_jpeg_rgb(raw) second = decode_jpeg_rgb(raw) assert raw.startswith(b"\xff\xd8") np.testing.assert_array_equal(first, second) assert first.shape == (2, 2, 3) def test_fp16_flat_materialization_preserves_bits_and_png_contract() -> None: values = np.array([0.0, -0.0, 1.5, -2.25, np.float16("inf")], dtype=np.float16) png = materialize_fp16_png(values) pixels = np.asarray(Image.open(BytesIO(png)).convert("RGB"), dtype=np.uint8).reshape(-1, 3) np.testing.assert_array_equal((pixels[: len(values), 0].astype(np.uint16) << 8) | pixels[: len(values), 1], values.view(np.uint16)) assert pixels[:, 2].tolist() == [0] * len(values) def test_finite_repair_materialize_decode_preserves_original_uint16_bits() -> None: original = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16) repaired = repair_nonfinite(original, "layer_mean", {"layer": (0, len(original))}).values png = materialize_fp16_png(repaired) pixels = np.asarray(Image.open(BytesIO(png)).convert("RGB"), dtype=np.uint8).reshape(-1, 3) decoded_bits = (pixels[: len(original), 0].astype(np.uint16) << 8) | pixels[: len(original), 1].astype(np.uint16) np.testing.assert_array_equal(decoded_bits, original.view(np.uint16)) def test_protected_high_png_is_exact_low_jpeg_only_changes() -> None: values = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16) artifact = encode_protected(values, (2, 3)) decoded, metadata = decode_protected(artifact.payload, (2, 3)) np.testing.assert_array_equal(decoded.view(np.uint16), values.view(np.uint16)) high = np.asarray(Image.open(BytesIO(artifact.high_png_bytes)).convert("L"), dtype=np.uint8) np.testing.assert_array_equal(high.reshape(-1), (values.view(np.uint16) >> 8).astype(np.uint8)) assert metadata["payload_size_bytes"] == len(artifact.high_png_bytes) + len(artifact.low_jpeg_bytes) assert metadata["high_mode"] == metadata["low_mode"] == "L" def test_repair_replaces_only_nonfinite_and_reports_signed_zero_bits() -> None: raw = np.array([0.0, -0.0, np.nan, np.inf, -np.inf, 2.0], dtype=np.float16) result = repair_nonfinite(raw, "layer_mean", {"layer": (0, len(raw))}) assert result.repaired_value_count == 3 assert result.nan_count == result.posinf_count == result.neginf_count == 1 assert np.isfinite(result.values).all() np.testing.assert_array_equal(result.values.view(np.uint16)[[0, 1, 5]], raw.view(np.uint16)[[0, 1, 5]]) def test_zero_repair_accepts_all_nonfinite_layer_and_preserves_finite_bits() -> None: raw = np.array([np.nan, np.inf, -np.inf, 1.5, -0.0, np.nan], dtype=np.float16) original_bits = raw.view(np.uint16).copy() result = repair_nonfinite(raw, "zero", {"all_nonfinite": (0, 3), "mixed": (3, 6)}) assert np.isfinite(result.values).all() assert result.repaired_value_count == 4 assert result.nan_count == 2 assert result.posinf_count == 1 assert result.neginf_count == 1 assert [record.repaired_count for record in result.layer_records] == [3, 1] assert [record.nan_count for record in result.layer_records] == [1, 1] assert [record.posinf_count for record in result.layer_records] == [1, 0] assert [record.neginf_count for record in result.layer_records] == [1, 0] finite = np.isfinite(raw) np.testing.assert_array_equal(result.values.view(np.uint16)[finite], original_bits[finite]) def test_repair_rejects_empty_finite_layer() -> None: raw = np.array([np.nan, np.inf], dtype=np.float16) with pytest.raises(ValueError, match="finite values"): repair_nonfinite(raw, "layer_median", {"empty": (0, 2)}) @pytest.mark.parametrize("method", ["layer_median", "layer_mean"]) def test_repair_rejects_empty_finite_layer_for_statistical_methods(method: str) -> None: raw = np.array([np.nan, np.inf, -np.inf], dtype=np.float16) with pytest.raises(ValueError, match="finite values"): repair_nonfinite(raw, method, {"empty": (0, 3)}) @pytest.mark.parametrize("strategy", CLIP_STRATEGIES) def test_clip_strategies_use_candidate_only_and_record_changes(strategy: str) -> None: candidate = np.array([-10.0, -1.0, 0.0, 1.0, 10.0], dtype=np.float16) result = clip_candidate(candidate, {"layer": (0, 5)}, strategy) assert result.values.dtype == np.float16 assert result.layers[0].strategy == strategy assert result.layers[0].changed_count == int(np.count_nonzero(result.values.view(np.uint16) != candidate.view(np.uint16))) assert np.isfinite(result.values).all() assert result.layers[0].lower <= result.layers[0].upper def test_clip_uses_population_std_and_linear_percentile() -> None: candidate = np.array([0.0, 1.0, 2.0, 100.0], dtype=np.float16) result = clip_candidate(candidate, {"layer": (0, 4)}, "layer_mean_std_6") expected = float(np.mean(candidate.astype(np.float64)) + 6 * np.std(candidate.astype(np.float64), ddof=0)) assert result.layers[0].upper == pytest.approx(expected) percentile = clip_candidate(candidate, {"layer": (0, 4)}, "layer_pct_0.1_99.9") expected_percentile = float(np.percentile(candidate.astype(np.float64), 99.9, method="linear")) assert percentile.layers[0].upper == pytest.approx(expected_percentile) def test_clip_degenerate_and_threshold_equal_are_unchanged() -> None: candidate = np.array([2.0, 2.0], dtype=np.float16) result = clip_candidate(candidate, {"one": (0, 2)}, "layer_mean_std_5") np.testing.assert_array_equal(result.values.view(np.uint16), candidate.view(np.uint16)) assert result.layers[0].changed_count == 0 def test_fp16_bit_diagnostics_and_bounded_histogram() -> None: baseline = np.array([1.0, -1.0, 0.0, 1.0], dtype=np.float16) candidate = np.array([20.0, -1.0, np.float16("inf"), np.float16("nan")], dtype=np.float16) result = diagnostics(baseline, candidate, {"layer": (0, 4)}) assert result["sign_mismatch_count"] == 0 assert result["exponent_mismatch_count"] == 3 assert result["mantissa_mismatch_count"] == 2 assert result["exponent_to31_count"] == 2 assert result["finite_extreme_outlier_count"] == 1 assert set(result["histograms"]) == {"baseline", "candidate", "delta"} assert all(len(item["counts"]) <= 64 for item in result["histograms"].values()) def test_registry_parent_identity_is_only_shared_by_raw_jpeg_conditions() -> None: raw = encode_jpeg_q100(_image()) registry = build_jpeg_registry(raw) assert len({entry.parent_raw_sha256 for entry in registry[1:17]}) == 1 assert registry[0].parent_raw_sha256 != registry[1].parent_raw_sha256 assert registry[17].parent_raw_sha256 != registry[1].parent_raw_sha256 assert registry[0].parent_raw_sha256 != registry[17].parent_raw_sha256 def test_layer_offsets_are_contiguous_and_fail_closed() -> None: assert validate_layer_offsets({"a": (0, 2), "b": (2, 4)}, 4) == {"a": (0, 2), "b": (2, 4)} with pytest.raises(ValueError, match="contiguous"): validate_layer_offsets({"a": (0, 2), "b": (3, 4)}, 4) with pytest.raises(ValueError, match="overlap"): validate_layer_offsets({"a": (0, 3), "b": (2, 4)}, 4) def test_diagnostics_extreme_uses_layer_baseline_max_and_reports_finite_stats() -> None: baseline = np.array([1.0, 2.0, 0.0, 0.0], dtype=np.float16) candidate = np.array([25.0, 2.0, 1.0, 0.0], dtype=np.float16) result = diagnostics(baseline, candidate, {"first": (0, 2), "zero": (2, 4)}) assert result["finite_extreme_outlier_count"] == 2 assert result["finite_count"] == 4 assert result["max_abs_weight"] == pytest.approx(25.0) assert result["nan_count"] == result["posinf_count"] == result["neginf_count"] == 0 assert result["layers"][0]["finite_extreme_outlier_count"] == 1 assert result["layers"][1]["finite_extreme_outlier_count"] == 1 assert result["layers"][1]["max_abs_weight"] == pytest.approx(1.0) def test_repair_has_layer_records_and_bounded_repaired_histogram() -> None: raw = np.array([np.nan, np.inf, -np.inf, 1.0], dtype=np.float16) result = repair_nonfinite(raw, "zero", {"layer": (0, 4)}) record = result.layer_records[0] assert record.layer == "layer" assert record.repaired_count == 3 assert record.nan_count == record.posinf_count == record.neginf_count == 1 assert sum(result.repaired_histogram["counts"]) == 3 assert len(result.repaired_histogram["counts"]) <= 64 def test_clip_has_total_count_and_changed_value_histograms() -> None: candidate = np.array([-100.0, -1.0, 0.0, 1.0, 100.0], dtype=np.float16) result = clip_candidate(candidate, {"layer": (0, 5)}, "layer_mean_std_5") assert result.total_clipped_count == result.layers[0].changed_count assert sum(result.changed_pre_histogram["counts"]) == result.total_clipped_count assert sum(result.changed_post_histogram["counts"]) == result.total_clipped_count assert sum(result.changed_delta_histogram["counts"]) == result.total_clipped_count def test_protected_metadata_reports_decoded_planes_and_low_changes() -> None: rng = np.random.default_rng(20260727) values = rng.integers(0, 65536, size=32 * 32, dtype=np.uint16).view(np.float16) artifact = encode_protected(values, (32, 32)) decoded, metadata = decode_protected(artifact.payload, (32, 32), source_values=values) assert metadata["decoded_high_exact"] is True assert metadata["decoded_high_sha256"] assert metadata["decoded_low_sha256"] assert metadata["decoded_low_changed_count"] > 0 assert 0.0 < metadata["decoded_low_changed_rate"] <= 1.0 assert metadata["pillow_version"] assert metadata["combined_payload_exact"] is True assert metadata["high_mode"] == metadata["low_mode"] == "L" assert metadata["high_dimensions"] == metadata["low_dimensions"] == [32, 32] assert metadata["combined_payload_sha256"] assert metadata["combined_payload_bytes"] == metadata["high_bytes"] + metadata["low_bytes"] assert "jpeg_codec_version" in metadata assert decoded.shape == values.shape def test_combined_payload_parser_rejects_invalid_png_chunk_bounds() -> None: values = np.arange(16, dtype=np.float16) artifact = encode_protected(values, (4, 4)) broken = artifact.payload[:16] + b"\xff" with pytest.raises(ValueError, match="PNG"): decode_protected(broken, (4, 4))