Add files using upload-large-folder tool
Browse files- .gitattributes +5 -35
- .gitignore +26 -0
- LICENSE +21 -0
- README.md +284 -0
- pyproject.toml +43 -0
- samples/avif_q70_protected_high/pm4-060-9ea3ad52.png +0 -0
- samples/avif_q70_protected_high/pm4-080-78dedcb6.png +0 -0
- samples/avif_q70_protected_high/pm4-090-c71ebd94.png +0 -0
- samples/jpeg_q100_repair_zero/pm4-016-fd1eb48d.png +0 -0
- samples/jpeg_q100_repair_zero/pm4-075-3906d66e.png +0 -0
- samples/jpeg_q100_repair_zero/pm4-086-90e23232.png +0 -0
- samples/jpeg_q100_repair_zero/pm4-097-0b860832.png +0 -0
- samples/jpeg_q80_protected_high/pm4-002-effb6eaa.png +0 -0
- samples/jpeg_q80_protected_high/pm4-016-fd1eb48d.png +0 -0
- samples/jpeg_q80_protected_high/pm4-051-093f1478.png +0 -0
- samples/jpeg_q80_protected_high/pm4-060-9ea3ad52.png +0 -0
- samples/jpeg_q80_protected_high/pm4-075-3906d66e.png +0 -0
- samples/jpeg_q80_protected_high/pm4-076-a577a15d.png +0 -0
- samples/jpeg_q80_protected_high/pm4-078-a77b762e.png +0 -0
- samples/jpeg_q80_protected_high/pm4-080-78dedcb6.png +0 -0
- samples/jpeg_q80_protected_high/pm4-082-6adb4a49.png +0 -0
- samples/jpeg_q80_protected_high/pm4-089-60ad56aa.png +0 -0
- samples/jpeg_q80_protected_high/pm4-090-c71ebd94.png +0 -0
- samples/jpeg_q80_protected_high/pm4-097-0b860832.png +0 -0
- samples/webp_lossless/pm4-002-effb6eaa.png +0 -0
- samples/webp_lossless/pm4-051-093f1478.png +0 -0
- samples/webp_lossless/pm4-075-3906d66e.png +0 -0
- samples/webp_lossless/pm4-082-6adb4a49.png +0 -0
- samples/webp_lossless/pm4-089-60ad56aa.png +0 -0
- samples/webp_q80_protected_high/pm4-002-effb6eaa.png +0 -0
- samples/webp_q80_protected_high/pm4-016-fd1eb48d.png +0 -0
- samples/webp_q80_protected_high/pm4-051-093f1478.png +0 -0
- samples/webp_q80_protected_high/pm4-060-9ea3ad52.png +0 -0
- samples/webp_q80_protected_high/pm4-075-3906d66e.png +0 -0
- samples/webp_q80_protected_high/pm4-076-a577a15d.png +0 -0
- samples/webp_q80_protected_high/pm4-078-a77b762e.png +0 -0
- samples/webp_q80_protected_high/pm4-080-78dedcb6.png +0 -0
- samples/webp_q80_protected_high/pm4-082-6adb4a49.png +0 -0
- samples/webp_q80_protected_high/pm4-089-60ad56aa.png +0 -0
- samples/webp_q80_protected_high/pm4-090-c71ebd94.png +0 -0
- samples/webp_q80_protected_high/pm4-097-0b860832.png +0 -0
- tests/test_cross_codec.py +482 -0
- tests/test_cross_codec_experiment.py +552 -0
- tests/test_cross_codec_report.py +547 -0
- tests/test_jpeg_repair.py +238 -0
- tests/test_render_visual_tables.py +182 -0
- tests/test_verify_archive.py +186 -0
- uv.lock +0 -0
- weights/README.md +7 -0
- weights/manifest.csv +14 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,5 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
weights/**/*.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
weights/**/*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
weights/**/*.webp filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
weights/**/*.avif filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
weights/**/*.jxl filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
.pytest_cache/
|
| 5 |
+
.ruff_cache/
|
| 6 |
+
.venv/
|
| 7 |
+
venv/
|
| 8 |
+
.cache/
|
| 9 |
+
.DS_Store
|
| 10 |
+
*.log
|
| 11 |
+
|
| 12 |
+
# Secrets and local state
|
| 13 |
+
.env
|
| 14 |
+
.env.*
|
| 15 |
+
*.pem
|
| 16 |
+
*.key
|
| 17 |
+
|
| 18 |
+
# Scratch outputs only; archived results and weights remain tracked
|
| 19 |
+
scratch/
|
| 20 |
+
tmp/
|
| 21 |
+
runs/
|
| 22 |
+
outputs/
|
| 23 |
+
|
| 24 |
+
# Local agent/project notes
|
| 25 |
+
tasks/
|
| 26 |
+
.hermes/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 PixelModel-v4 image codec study contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- research
|
| 5 |
+
- weight-compression
|
| 6 |
+
- image-codecs
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
# PixelModel-v4 Image Codec Experiments
|
| 10 |
+
|
| 11 |
+
This repository is a research archive for measuring how image codecs affect a
|
| 12 |
+
PixelModel-v4 weight image and the model reconstructed from it. It is not the
|
| 13 |
+
main PixelModel-v4 model repository and is not a replacement model card.
|
| 14 |
+
|
| 15 |
+
## Scope/Provenance
|
| 16 |
+
|
| 17 |
+
The upstream model card and model assets are licensed under MIT by
|
| 18 |
+
[bench-labs/PixelModel-v4](https://huggingface.co/bench-labs/PixelModel-v4). The
|
| 19 |
+
upstream model revision used by the experiment is
|
| 20 |
+
`80878fdd6e130d229c2581424a3456b47b979d4b`. The packaged experimental source was captured from robustness-study commit
|
| 21 |
+
`939465cee88e49ae212df48aac18e74459763a72`.
|
| 22 |
+
|
| 23 |
+
The archive records a fixed comparison: 100 prompts, 50 sampling steps, CFG 6,
|
| 24 |
+
Apple MPS, and identical prompt, seed, initial latent, and settings across
|
| 25 |
+
conditions. The archive is intended for auditability and re-running the
|
| 26 |
+
experiment, not for making claims about codecs outside these recorded settings.
|
| 27 |
+
The `samples/` tree contains one artifact for every successful generation row
|
| 28 |
+
(800 artifacts in the full 17-condition run), while failed rows retain null
|
| 29 |
+
sample provenance.
|
| 30 |
+
|
| 31 |
+
## Motivation
|
| 32 |
+
|
| 33 |
+
PixelModel-v4 stores trained weights as image data, which makes ordinary image
|
| 34 |
+
storage and transport tools relevant to model serialization. Earlier work used
|
| 35 |
+
PNG as the lossless weight container. This repository explores what happens when
|
| 36 |
+
the same fp16 weight image is passed through modern lossless and lossy codecs,
|
| 37 |
+
then reconstructed with several byte-level methods. This is exploratory research:
|
| 38 |
+
the goal is to record the observed relationship between codec output,
|
| 39 |
+
reconstructed weight values, and generation behavior without proposing a
|
| 40 |
+
general-purpose compression result.
|
| 41 |
+
|
| 42 |
+
## Experimental Setup
|
| 43 |
+
|
| 44 |
+
- Model: PixelModel-v4 DiT with an fp16 weight image, using the upstream revision above.
|
| 45 |
+
- Weight representation: each fp16 word is serialized across image bytes; the
|
| 46 |
+
high byte contains the sign, exponent, and upper mantissa bits.
|
| 47 |
+
- Prompts: 100 prompts.
|
| 48 |
+
- Sampling: 50 steps, CFG 6.
|
| 49 |
+
- Controls: identical prompt, seed, initial latent, and inference settings.
|
| 50 |
+
- Device: Apple MPS for the archived full run.
|
| 51 |
+
- Raw reconstruction is accepted only when the decoded weight tensor is finite.
|
| 52 |
+
A NaN count by itself is not called “nonfinite”; the gate checks the complete
|
| 53 |
+
tensor and reports NaN, +Inf, and -Inf separately.
|
| 54 |
+
- FID is N/A for this archive. CLIP and confidence intervals are not included
|
| 55 |
+
unless they are present in the archived CSV.
|
| 56 |
+
|
| 57 |
+
## Codecs Evaluated
|
| 58 |
+
|
| 59 |
+
| Codec | Fixed setting | Role |
|
| 60 |
+
|---|---:|---|
|
| 61 |
+
| PNG | lossless | baseline |
|
| 62 |
+
| WebP | lossless | lossless control |
|
| 63 |
+
| JPEG | Q100 | lossy codec |
|
| 64 |
+
| JPEG | Q80 | lossy codec |
|
| 65 |
+
| WebP | Q80 | lossy codec |
|
| 66 |
+
| AVIF | Q70 | lossy codec |
|
| 67 |
+
| JPEG XL | distance 1 (`d1`) | lossy codec |
|
| 68 |
+
|
| 69 |
+
The protected representation keeps the high byte as PNG data and stores the low
|
| 70 |
+
byte through the lossy codec. The code also records the raw and finite-repair
|
| 71 |
+
paths separately.
|
| 72 |
+
|
| 73 |
+
## Reconstruction Methods
|
| 74 |
+
|
| 75 |
+
| Method | Description |
|
| 76 |
+
|---|---|
|
| 77 |
+
| Raw | Decode the codec payload directly into the weight image representation. |
|
| 78 |
+
| Finite Repair Zero | Replace decoded NaN, +Inf, and -Inf values with zero while leaving finite values unchanged. |
|
| 79 |
+
| Protected High Byte | Preserve the high byte losslessly and decode only the low byte through the lossy codec. |
|
| 80 |
+
|
| 81 |
+
## Representative Results
|
| 82 |
+
|
| 83 |
+
The following links are relative archive paths. A failed cell is rendered as an
|
| 84 |
+
explicit “NO SAMPLE” panel; it is not replaced with a black image or an inferred
|
| 85 |
+
metric.
|
| 86 |
+
|
| 87 |
+

|
| 88 |
+
|
| 89 |
+
*Caption: representative prompt panels across PNG, WebP lossless, raw lossy,
|
| 90 |
+
finite repair, and protected high-byte reconstructions.*
|
| 91 |
+
|
| 92 |
+

|
| 93 |
+
|
| 94 |
+
*Caption: JPEG Q100 table showing raw, finite-repair, and protected cells for representative prompts.*
|
| 95 |
+
|
| 96 |
+

|
| 97 |
+
|
| 98 |
+
*Caption: JPEG Q80 table. Raw and Finite Repair Zero cells have no generated sample; Protected High Byte cells show the archived outputs.*
|
| 99 |
+
|
| 100 |
+

|
| 101 |
+
|
| 102 |
+
*Caption: lossy WebP Q80 table with explicit failed cells and archived Protected High Byte outputs.*
|
| 103 |
+
|
| 104 |
+

|
| 105 |
+
|
| 106 |
+
*Caption: AVIF Q70 table with the same prompts, seeds, and inference settings as the controls.*
|
| 107 |
+
|
| 108 |
+

|
| 109 |
+
|
| 110 |
+
*Caption: JPEG XL distance 1 table. Failed conditions are marked `NO SAMPLE`; no replacement image is used.*
|
| 111 |
+
|
| 112 |
+
## Results table from available CSV only
|
| 113 |
+
|
| 114 |
+
The values below are transcribed from the archived CSV outputs for the full
|
| 115 |
+
100-prompt run. They are counts and payload measurements, not estimates.
|
| 116 |
+
|
| 117 |
+
| Condition | Raw generation | Finite Repair Zero | Protected High Byte | Raw NaN / +Inf / -Inf | Protected exponent mismatch | Reliable payload saving vs PNG |
|
| 118 |
+
|---|---:|---:|---:|---:|---:|---:|
|
| 119 |
+
| PNG | 100/100 | N/A | N/A | 0 / 0 / 0 | 0 | 0.0000% |
|
| 120 |
+
| WebP lossless | 100/100 | N/A | N/A | 0 / 0 / 0 | 0 | 24.2799% |
|
| 121 |
+
| JPEG Q100 | 0/100 | 100/100 | 100/100 | 1,257 / 0 / 0 | 0 | -6.8825% |
|
| 122 |
+
| JPEG Q80 | 0/100 | 0/100 | 100/100 | 144,962 / 289 / 0 | 0 | 36.5568% |
|
| 123 |
+
| WebP Q80 | 0/100 | 0/100 | 100/100 | 1,097,350 / 0 / 0 | 0 | 37.6701% |
|
| 124 |
+
| AVIF Q70 | 0/100 | 0/100 | 100/100 | 445,041 / 59 / 0 | 0 | 39.2740% |
|
| 125 |
+
| JPEG XL d1 | 0/100 | 0/100 | 100/100 | 59,017 / 13 / 0 | 0 | 37.2180% |
|
| 126 |
+
|
| 127 |
+
The lossless WebP payload divided by the PNG payload is
|
| 128 |
+
`0.7572009918694315`. The saving percentages use PNG baseline payload bytes as
|
| 129 |
+
the denominator; Protected High Byte payloads include the high-byte PNG and the
|
| 130 |
+
codec-specific low-byte payload. If an
|
| 131 |
+
archive CSV is absent or differs, the CSV is authoritative and this table must
|
| 132 |
+
not be treated as an additional measurement.
|
| 133 |
+
|
| 134 |
+
## Main Observations
|
| 135 |
+
|
| 136 |
+
- PNG and WebP lossless completed 100/100 generations.
|
| 137 |
+
- All five lossy raw conditions completed 0/100 generations.
|
| 138 |
+
- JPEG was not uniquely fragile in this matrix: every tested lossy Raw setting
|
| 139 |
+
introduced non-finite fp16 values and was blocked before generation.
|
| 140 |
+
- Finite Repair Zero completed 100/100 only for JPEG Q100; the other four lossy
|
| 141 |
+
conditions recorded non-finite generation-stage failures.
|
| 142 |
+
- Protected High Byte completed 100/100 for all five lossy codecs.
|
| 143 |
+
- The protected rows recorded zero exponent mismatches.
|
| 144 |
+
- The raw diagnostic counts differ substantially across codecs; NaN, +Inf, and
|
| 145 |
+
-Inf are kept as separate fields.
|
| 146 |
+
|
| 147 |
+
## Discussion
|
| 148 |
+
|
| 149 |
+
Facts are the counts, byte diagnostics, exponent-mismatch counts, and payload
|
| 150 |
+
measurements recorded in the CSV files. They describe this run and do not by
|
| 151 |
+
themselves establish a causal mechanism.
|
| 152 |
+
|
| 153 |
+
A hypothesis consistent with the observations is that the fp16 high byte—which
|
| 154 |
+
contains the sign, exponent, and upper mantissa bits—is substantially more
|
| 155 |
+
important to the recorded model behavior than the lower byte. Protected High
|
| 156 |
+
Byte preserves that high byte while allowing low-byte compression to alter less
|
| 157 |
+
significant bits. The successful protected runs demonstrate the outcome for this
|
| 158 |
+
matrix, but they do not establish the proposed bit-importance mechanism as a
|
| 159 |
+
general causal fact. Protected High Byte is also a hybrid representation, not a
|
| 160 |
+
pure lossy weight artifact. Codec implementation choices, decoder behavior,
|
| 161 |
+
tensor layout, and the MPS runtime may contribute.
|
| 162 |
+
|
| 163 |
+
## Repository Contents
|
| 164 |
+
|
| 165 |
+
```text
|
| 166 |
+
images/ Archived figures and representative images
|
| 167 |
+
results/ CSV results, reports, and archive_manifest.json
|
| 168 |
+
samples/ 800 successful sample artifacts for all successful generation rows
|
| 169 |
+
weights/ Archived payloads and weights/manifest.csv
|
| 170 |
+
scripts/cross_codec_report.py Report generation
|
| 171 |
+
scripts/render_visual_tables.py Portable fail-closed visual-table renderer
|
| 172 |
+
scripts/verify_archive.py Hash, byte-count, containment, and symlink verifier
|
| 173 |
+
scripts/pixelmodel_robustness/ 11 packaged robustness modules
|
| 174 |
+
scripts/dit.py Packaged DiT implementation
|
| 175 |
+
scripts/config/ Experiment configuration and prompt manifests
|
| 176 |
+
tests/ Focused codec, repair, report, renderer, and archive tests
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
## Reproducibility
|
| 180 |
+
|
| 181 |
+
The full runner may require Apple MPS because that is the archived runtime. The
|
| 182 |
+
following commands are generic and intentionally do not promise universal
|
| 183 |
+
hardware reproducibility.
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
uv sync --locked
|
| 187 |
+
uv run --locked pytest
|
| 188 |
+
PYTHONPATH=scripts uv run --locked python scripts/verify_archive.py --root .
|
| 189 |
+
PYTHONPATH=scripts uv run --locked python scripts/render_visual_tables.py \
|
| 190 |
+
--run-dir . \
|
| 191 |
+
--prompt-ids-manifest results/representative_prompt_ids.json \
|
| 192 |
+
--output scratch/rendered-tables
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
The complete archived experiment uses the packaged runner and the archived
|
| 196 |
+
configuration. Start with a small smoke test before attempting the full run.
|
| 197 |
+
The full implementation may require MPS and the source assets.
|
| 198 |
+
|
| 199 |
+
## How to verify/re-run
|
| 200 |
+
|
| 201 |
+
1. Verify the archive before reading results:
|
| 202 |
+
|
| 203 |
+
```bash
|
| 204 |
+
PYTHONPATH=scripts uv run --locked python scripts/verify_archive.py --root .
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
2. Run the focused tests:
|
| 208 |
+
|
| 209 |
+
```bash
|
| 210 |
+
uv run --locked pytest
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
3. Inspect the packaged report and experiment command surfaces:
|
| 214 |
+
|
| 215 |
+
```bash
|
| 216 |
+
PYTHONPATH=scripts uv run --locked python scripts/cross_codec_report.py --help
|
| 217 |
+
PYTHONPATH=scripts uv run --locked python -m pixelmodel_robustness.cross_codec_experiment --help
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
4. For a fresh source checkout, obtain the upstream assets at the exact
|
| 221 |
+
revision. This command is a prerequisite only; it does not alter this
|
| 222 |
+
archive:
|
| 223 |
+
|
| 224 |
+
```bash
|
| 225 |
+
hf download bench-labs/PixelModel-v4 \
|
| 226 |
+
--revision 80878fdd6e130d229c2581424a3456b47b979d4b
|
| 227 |
+
```
|
| 228 |
+
|
| 229 |
+
5. Use the packaged runner's `--help` output to select the input paths and
|
| 230 |
+
output run directory, then run on a machine with the required model runtime.
|
| 231 |
+
Preserve the runner manifest, encoded payloads, materialized weight images,
|
| 232 |
+
samples, resource log, and raw rows together. `samples/` contains all 800
|
| 233 |
+
successful sample artifacts, so raw sample provenance is complete even though
|
| 234 |
+
the compact archive intentionally deduplicates payloads. Complete
|
| 235 |
+
report regeneration should target a fresh canonical run directory with
|
| 236 |
+
materialized weight images; the compact archive layout is not sufficient for
|
| 237 |
+
a fully fresh report regeneration.
|
| 238 |
+
|
| 239 |
+
For AVIF, Pillow must have AVIF support. JPEG XL requires an ImageMagick build
|
| 240 |
+
with the libjxl delegate. These codec prerequisites are recorded by the
|
| 241 |
+
experiment where available.
|
| 242 |
+
|
| 243 |
+
## Weights archive explanation
|
| 244 |
+
|
| 245 |
+
The `weights/` directory is an archive of experiment inputs and codec payloads,
|
| 246 |
+
not a new or fine-tuned model release. `weights/manifest.csv` records the path,
|
| 247 |
+
byte count, and SHA-256 for each listed payload. Git LFS is configured for this
|
| 248 |
+
directory. The archive verifier checks that paths stay inside the repository,
|
| 249 |
+
that listed files exist with the recorded bytes and hashes, that paths are not
|
| 250 |
+
duplicated, and that no symlink is used in the archived trees.
|
| 251 |
+
|
| 252 |
+
## Limitations
|
| 253 |
+
|
| 254 |
+
- The experiment covers one model version, PixelModel-v4 at the pinned revision.
|
| 255 |
+
- Only the listed codec settings were evaluated; the setting matrix is limited.
|
| 256 |
+
- No result here establishes that one codec is universally superior.
|
| 257 |
+
- The full run uses one fixed prompt/seed/latent/settings protocol and one
|
| 258 |
+
Apple MPS runtime.
|
| 259 |
+
- The results do not establish codec performance for other models, tensor
|
| 260 |
+
layouts, image dimensions, devices, Pillow versions, or libjxl versions.
|
| 261 |
+
- Generation success is a gate outcome, not a quality ranking.
|
| 262 |
+
- No FID result is reported; FID is N/A here.
|
| 263 |
+
- Missing or failed samples are not imputed.
|
| 264 |
+
- The archive does not claim that protected high-byte storage preserves every
|
| 265 |
+
aspect of a model outside the recorded experiment.
|
| 266 |
+
- These observations should not be generalized beyond this experiment without
|
| 267 |
+
additional models, settings, runtimes, and independently specified evaluations.
|
| 268 |
+
|
| 269 |
+
## Future Work
|
| 270 |
+
|
| 271 |
+
- Perform bit-level importance analysis with pre-registered corruption masks.
|
| 272 |
+
- Evaluate exponent-only corruption.
|
| 273 |
+
- Evaluate mantissa-only corruption at controlled bit positions.
|
| 274 |
+
- Test additional codecs and codec settings.
|
| 275 |
+
- Investigate learned, weight-aware compression methods.
|
| 276 |
+
- Repeat the protocol across additional model revisions, tensor layouts, prompt
|
| 277 |
+
sets, devices, and decoder versions.
|
| 278 |
+
|
| 279 |
+
## License and acknowledgment
|
| 280 |
+
|
| 281 |
+
This archive is released under the MIT License. It acknowledges
|
| 282 |
+
[bench-labs/PixelModel-v4](https://huggingface.co/bench-labs/PixelModel-v4) and
|
| 283 |
+
its MIT-licensed upstream model card. This repository is an experimental archive
|
| 284 |
+
built from the robustness-study source; it is not the upstream model repository.
|
pyproject.toml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "pixelmodel-v4-image-codec-study"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Research archive for PixelModel-v4 weight-image codec robustness experiments"
|
| 5 |
+
requires-python = ">=3.11,<3.12"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"numpy",
|
| 8 |
+
"pillow",
|
| 9 |
+
"pandas",
|
| 10 |
+
"matplotlib",
|
| 11 |
+
"scikit-image",
|
| 12 |
+
"pytest",
|
| 13 |
+
"ruff",
|
| 14 |
+
"torch",
|
| 15 |
+
"diffusers==0.31.0",
|
| 16 |
+
"transformers==4.49.0",
|
| 17 |
+
"safetensors",
|
| 18 |
+
"torchmetrics",
|
| 19 |
+
"torch-fidelity",
|
| 20 |
+
"scipy",
|
| 21 |
+
"accelerate>=1.14.0",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
[tool.pytest.ini_options]
|
| 25 |
+
testpaths = ["tests"]
|
| 26 |
+
pythonpath = ["scripts"]
|
| 27 |
+
|
| 28 |
+
[tool.ruff]
|
| 29 |
+
line-length = 120
|
| 30 |
+
target-version = "py311"
|
| 31 |
+
exclude = [
|
| 32 |
+
"scripts/cross_codec_report.py",
|
| 33 |
+
"scripts/dit.py",
|
| 34 |
+
"scripts/pixelmodel_robustness/**",
|
| 35 |
+
"tests/test_cross_codec.py",
|
| 36 |
+
"tests/test_cross_codec_experiment.py",
|
| 37 |
+
"tests/test_cross_codec_report.py",
|
| 38 |
+
"tests/test_jpeg_repair.py",
|
| 39 |
+
".venv",
|
| 40 |
+
".pytest_cache",
|
| 41 |
+
".ruff_cache",
|
| 42 |
+
"scratch",
|
| 43 |
+
]
|
samples/avif_q70_protected_high/pm4-060-9ea3ad52.png
ADDED
|
|
samples/avif_q70_protected_high/pm4-080-78dedcb6.png
ADDED
|
|
samples/avif_q70_protected_high/pm4-090-c71ebd94.png
ADDED
|
|
samples/jpeg_q100_repair_zero/pm4-016-fd1eb48d.png
ADDED
|
|
samples/jpeg_q100_repair_zero/pm4-075-3906d66e.png
ADDED
|
|
samples/jpeg_q100_repair_zero/pm4-086-90e23232.png
ADDED
|
|
samples/jpeg_q100_repair_zero/pm4-097-0b860832.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-002-effb6eaa.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-016-fd1eb48d.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-051-093f1478.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-060-9ea3ad52.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-075-3906d66e.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-076-a577a15d.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-078-a77b762e.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-080-78dedcb6.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-082-6adb4a49.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-089-60ad56aa.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-090-c71ebd94.png
ADDED
|
|
samples/jpeg_q80_protected_high/pm4-097-0b860832.png
ADDED
|
|
samples/webp_lossless/pm4-002-effb6eaa.png
ADDED
|
|
samples/webp_lossless/pm4-051-093f1478.png
ADDED
|
|
samples/webp_lossless/pm4-075-3906d66e.png
ADDED
|
|
samples/webp_lossless/pm4-082-6adb4a49.png
ADDED
|
|
samples/webp_lossless/pm4-089-60ad56aa.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-002-effb6eaa.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-016-fd1eb48d.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-051-093f1478.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-060-9ea3ad52.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-075-3906d66e.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-076-a577a15d.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-078-a77b762e.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-080-78dedcb6.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-082-6adb4a49.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-089-60ad56aa.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-090-c71ebd94.png
ADDED
|
|
samples/webp_q80_protected_high/pm4-097-0b860832.png
ADDED
|
|
tests/test_cross_codec.py
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Task 1 cross-codec adapterの契約テスト。"""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import FrozenInstanceError, replace
|
| 6 |
+
from io import BytesIO
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
from pixelmodel_robustness.cross_codec import (
|
| 13 |
+
CODEC_SPECS,
|
| 14 |
+
CONDITION_IDS,
|
| 15 |
+
CONDITION_REGISTRY,
|
| 16 |
+
CodecSpec,
|
| 17 |
+
ConditionSpec,
|
| 18 |
+
JxlCodecError,
|
| 19 |
+
ProtectedArtifact,
|
| 20 |
+
decode_l_plane,
|
| 21 |
+
decode_protected_high,
|
| 22 |
+
decode_rgb,
|
| 23 |
+
encode_l_plane,
|
| 24 |
+
encode_protected_high,
|
| 25 |
+
encode_rgb,
|
| 26 |
+
preflight_codecs,
|
| 27 |
+
repair_nonfinite_zero,
|
| 28 |
+
semantic_hash,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def rgb_image() -> Image.Image:
|
| 33 |
+
return Image.fromarray(
|
| 34 |
+
np.array(
|
| 35 |
+
[[[0, 1, 2], [20, 40, 60]], [[100, 120, 140], [240, 220, 200]]],
|
| 36 |
+
dtype=np.uint8,
|
| 37 |
+
),
|
| 38 |
+
"RGB",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def gray_plane() -> np.ndarray:
|
| 43 |
+
return np.arange(16, dtype=np.uint8).reshape(4, 4) * 13
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_registry_has_exact_order_and_stable_semantic_hash() -> None:
|
| 47 |
+
assert CONDITION_IDS == (
|
| 48 |
+
"png_baseline", "webp_lossless", "jpeg_q100_raw", "jpeg_q100_repair_zero",
|
| 49 |
+
"jpeg_q100_protected_high", "jpeg_q80_raw", "jpeg_q80_repair_zero",
|
| 50 |
+
"jpeg_q80_protected_high", "webp_q80_raw", "webp_q80_repair_zero",
|
| 51 |
+
"webp_q80_protected_high", "avif_q70_raw", "avif_q70_repair_zero",
|
| 52 |
+
"avif_q70_protected_high", "jxl_d1_raw", "jxl_d1_repair_zero",
|
| 53 |
+
"jxl_d1_protected_high",
|
| 54 |
+
)
|
| 55 |
+
assert tuple(CONDITION_REGISTRY) == CONDITION_IDS
|
| 56 |
+
assert semantic_hash(CONDITION_REGISTRY) == semantic_hash(tuple(CONDITION_REGISTRY))
|
| 57 |
+
with pytest.raises(TypeError):
|
| 58 |
+
CONDITION_REGISTRY["new"] = CONDITION_REGISTRY["png_baseline"] # type: ignore[index]
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_codec_spec_is_immutable_and_options_are_fixed() -> None:
|
| 62 |
+
spec = CODEC_SPECS["jpeg_q80"]
|
| 63 |
+
assert isinstance(spec, CodecSpec)
|
| 64 |
+
assert spec.quality == 80
|
| 65 |
+
assert spec.options["subsampling"] == 0
|
| 66 |
+
with pytest.raises(FrozenInstanceError):
|
| 67 |
+
spec.quality = 99 # type: ignore[misc]
|
| 68 |
+
assert CODEC_SPECS["webp_lossless"].options == {"lossless": True, "exact": True, "method": 6}
|
| 69 |
+
assert CODEC_SPECS["jxl_d1"].quality == 90
|
| 70 |
+
assert CODEC_SPECS["jxl_d1"].metadata["libjxl_distance"] == 1.0
|
| 71 |
+
assert "jxl:distance" not in str(CODEC_SPECS["jxl_d1"].options)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_all_codec_options_and_nested_registry_objects_are_immutable() -> None:
|
| 75 |
+
expected = {
|
| 76 |
+
"png": {},
|
| 77 |
+
"webp_lossless": {"lossless": True, "exact": True, "method": 6},
|
| 78 |
+
"jpeg_q100": {"quality": 100, "subsampling": 0, "optimize": False},
|
| 79 |
+
"jpeg_q80": {"quality": 80, "subsampling": 0, "optimize": False},
|
| 80 |
+
"webp_q80": {"quality": 80, "lossless": False, "method": 6, "exact": True},
|
| 81 |
+
"avif_q70": {"quality": 70, "subsampling": "4:4:4", "speed": 6, "range": "full", "codec": "aom", "max_threads": 1, "autotiling": False},
|
| 82 |
+
"jxl_d1": {"quality": 90},
|
| 83 |
+
}
|
| 84 |
+
assert {name: dict(spec.options) for name, spec in CODEC_SPECS.items()} == expected
|
| 85 |
+
assert all(spec.grayscale is True for spec in CODEC_SPECS.values())
|
| 86 |
+
for spec in CODEC_SPECS.values():
|
| 87 |
+
with pytest.raises(TypeError):
|
| 88 |
+
spec.options["quality"] = 1 # type: ignore[index]
|
| 89 |
+
with pytest.raises(TypeError):
|
| 90 |
+
spec.metadata["changed"] = True # type: ignore[index]
|
| 91 |
+
entry = CONDITION_REGISTRY["png_baseline"]
|
| 92 |
+
assert isinstance(entry, ConditionSpec)
|
| 93 |
+
with pytest.raises(FrozenInstanceError):
|
| 94 |
+
entry.mode = "tampered" # type: ignore[misc]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@pytest.mark.parametrize("codec", ("png", "webp_lossless", "jpeg_q100", "jpeg_q80", "webp_q80", "avif_q70"))
|
| 98 |
+
def test_rgb_encode_decode_uses_fixed_contract(codec: str) -> None:
|
| 99 |
+
available = {item.name: item for item in preflight_codecs()}
|
| 100 |
+
if not available[codec].available:
|
| 101 |
+
pytest.skip(available[codec].reason or "codec unavailable")
|
| 102 |
+
payload = encode_rgb(rgb_image(), CODEC_SPECS[codec])
|
| 103 |
+
decoded = decode_rgb(payload, CODEC_SPECS[codec])
|
| 104 |
+
assert isinstance(payload, bytes)
|
| 105 |
+
assert decoded.mode == "RGB"
|
| 106 |
+
assert decoded.size == (2, 2)
|
| 107 |
+
if codec in {"png", "webp_lossless"}:
|
| 108 |
+
np.testing.assert_array_equal(np.asarray(decoded), np.asarray(rgb_image()))
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def test_grayscale_plane_round_trip_for_lossless_png() -> None:
|
| 112 |
+
payload = encode_l_plane(gray_plane(), CODEC_SPECS["png"])
|
| 113 |
+
decoded = decode_l_plane(payload, CODEC_SPECS["png"])
|
| 114 |
+
np.testing.assert_array_equal(decoded, gray_plane())
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def test_l_plane_adapter_passes_single_channel_image_to_encoder(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 118 |
+
import pixelmodel_robustness.cross_codec as module
|
| 119 |
+
|
| 120 |
+
modes: list[str] = []
|
| 121 |
+
original_save = Image.Image.save
|
| 122 |
+
|
| 123 |
+
def capture_save(self: Image.Image, *args: object, **kwargs: object) -> object:
|
| 124 |
+
modes.append(self.mode)
|
| 125 |
+
return original_save(self, *args, **kwargs)
|
| 126 |
+
|
| 127 |
+
monkeypatch.setattr(Image.Image, "save", capture_save)
|
| 128 |
+
encode_l_plane(gray_plane(), CODEC_SPECS["png"])
|
| 129 |
+
assert modes[-1] == "L"
|
| 130 |
+
assert module.decode_l_plane(encode_l_plane(gray_plane(), CODEC_SPECS["png"]), CODEC_SPECS["png"]).dtype == np.uint8
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def test_jxl_l_plane_writes_l_mode_source_png(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 134 |
+
import pixelmodel_robustness.cross_codec as module
|
| 135 |
+
|
| 136 |
+
source_modes: list[str] = []
|
| 137 |
+
seen_argv: list[list[str]] = []
|
| 138 |
+
|
| 139 |
+
def fake_jxl_run(argv: list[str]) -> object:
|
| 140 |
+
seen_argv.append(argv)
|
| 141 |
+
with Image.open(argv[2]) as source:
|
| 142 |
+
source_modes.append(source.mode)
|
| 143 |
+
Image.new("L", (4, 4)).save(argv[-1], format="PNG")
|
| 144 |
+
return type("Completed", (), {"returncode": 0, "stdout": "", "stderr": ""})()
|
| 145 |
+
|
| 146 |
+
monkeypatch.setattr(module, "_jxl_run", fake_jxl_run)
|
| 147 |
+
module._jxl_encode(Image.fromarray(gray_plane(), "L"))
|
| 148 |
+
assert source_modes == ["L"]
|
| 149 |
+
assert seen_argv[0][0:2] == ["-quality", "90"]
|
| 150 |
+
assert seen_argv[0][2].endswith("source.png")
|
| 151 |
+
assert seen_argv[0][3].endswith("payload.jxl")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def test_protected_high_is_exact_and_payload_is_combined_bytes() -> None:
|
| 155 |
+
values = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16)
|
| 156 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 157 |
+
assert isinstance(artifact, ProtectedArtifact)
|
| 158 |
+
decoded, metadata = decode_protected_high(artifact, (2, 3), values)
|
| 159 |
+
np.testing.assert_array_equal(decoded.view(np.uint16) >> 8, values.view(np.uint16) >> 8)
|
| 160 |
+
assert metadata["decoded_high_exact"] is True
|
| 161 |
+
assert metadata["high_mode"] == metadata["low_mode"] == "L"
|
| 162 |
+
assert metadata["dimensions"] == [2, 3]
|
| 163 |
+
assert metadata["combined_payload_bytes"] == len(artifact.payload)
|
| 164 |
+
assert metadata["combined_payload_sha256"]
|
| 165 |
+
with Image.open(BytesIO(artifact.low_codec_bytes)) as low_image:
|
| 166 |
+
assert metadata["low_mode"] == low_image.mode
|
| 167 |
+
assert metadata["high_encoded_bytes"] == len(artifact.high_png_bytes)
|
| 168 |
+
assert metadata["low_encoded_bytes"] == len(artifact.low_codec_bytes)
|
| 169 |
+
assert metadata["high_encoded_sha256"]
|
| 170 |
+
assert metadata["low_encoded_sha256"]
|
| 171 |
+
assert metadata["decoded_low_sha256"]
|
| 172 |
+
assert metadata["high_dimensions"] == metadata["low_dimensions"] == [2, 3]
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
@pytest.mark.parametrize("mode", ["RGB", "P"])
|
| 176 |
+
def test_protected_high_rejects_non_l_high_plane(mode: str) -> None:
|
| 177 |
+
values = np.array([0.0, 1.5, -2.25, 3.125, -4.5, 5.0], dtype=np.float16)
|
| 178 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 179 |
+
high = np.asarray(Image.open(BytesIO(artifact.high_png_bytes)).convert("L"), dtype=np.uint8)
|
| 180 |
+
stream = BytesIO()
|
| 181 |
+
tampered_image = np.stack([high] * 3, axis=-1) if mode == "RGB" else high
|
| 182 |
+
Image.fromarray(tampered_image, mode).save(stream, format="PNG")
|
| 183 |
+
tampered = ProtectedArtifact(stream.getvalue(), artifact.low_codec_bytes, artifact.low_codec)
|
| 184 |
+
with pytest.raises(ValueError, match="high plane mode"):
|
| 185 |
+
decode_protected_high(tampered, (2, 3), values)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def test_protected_high_rejects_high_shape_tamper() -> None:
|
| 189 |
+
values = np.arange(6, dtype=np.float16)
|
| 190 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 191 |
+
stream = BytesIO()
|
| 192 |
+
Image.fromarray(np.zeros((1, 6), dtype=np.uint8), "L").save(stream, format="PNG")
|
| 193 |
+
tampered = ProtectedArtifact(stream.getvalue(), artifact.low_codec_bytes, artifact.low_codec)
|
| 194 |
+
with pytest.raises(ValueError, match="dimensions"):
|
| 195 |
+
decode_protected_high(tampered, (2, 3), values)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def test_protected_high_zero_pads_to_capacity_and_checks_full_high_plane() -> None:
|
| 199 |
+
values = np.array([1.5, -2.25, 3.125], dtype=np.float16)
|
| 200 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 201 |
+
decoded, metadata = decode_protected_high(artifact, (2, 3), values)
|
| 202 |
+
expected_high = np.zeros(6, dtype=np.uint16)
|
| 203 |
+
expected_high[:3] = values.view(np.uint16)
|
| 204 |
+
np.testing.assert_array_equal(decoded.view(np.uint16) >> 8, expected_high >> 8)
|
| 205 |
+
assert decoded.size == 6
|
| 206 |
+
assert metadata["source_value_count"] == 3
|
| 207 |
+
with pytest.raises(ValueError, match="capacity"):
|
| 208 |
+
encode_protected_high(np.zeros(7, dtype=np.float16), (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def test_zero_repair_changes_only_nonfinite_fp16_words() -> None:
|
| 212 |
+
raw = np.array([0.0, -0.0, np.nan, np.inf, -np.inf, 2.0], dtype=np.float16)
|
| 213 |
+
result = repair_nonfinite_zero(raw, {"layer": (0, len(raw))})
|
| 214 |
+
assert np.isfinite(result.values).all()
|
| 215 |
+
np.testing.assert_array_equal(result.values.view(np.uint16)[[0, 1, 5]], raw.view(np.uint16)[[0, 1, 5]])
|
| 216 |
+
assert result.repaired_value_count == 3
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def test_jxl_failure_preserves_subprocess_diagnostics(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 220 |
+
import pixelmodel_robustness.cross_codec as module
|
| 221 |
+
|
| 222 |
+
def failed_run(*args: object, **kwargs: object) -> object:
|
| 223 |
+
class Completed:
|
| 224 |
+
returncode = 7
|
| 225 |
+
stdout = "ImageMagick 7.1.2"
|
| 226 |
+
stderr = "libjxl unavailable"
|
| 227 |
+
|
| 228 |
+
failed_run.args = args # type: ignore[attr-defined]
|
| 229 |
+
failed_run.kwargs = kwargs # type: ignore[attr-defined]
|
| 230 |
+
return Completed()
|
| 231 |
+
|
| 232 |
+
monkeypatch.setattr(module.subprocess, "run", failed_run)
|
| 233 |
+
with pytest.raises(JxlCodecError, match="returncode=7") as error:
|
| 234 |
+
module._jxl_run(["/tmp/in.png", "/tmp/out.jxl"])
|
| 235 |
+
assert error.value.stderr == "libjxl unavailable"
|
| 236 |
+
assert error.value.argv == ("/etc/profiles/per-user/scratchbrulee/bin/magick", "/tmp/in.png", "/tmp/out.jxl")
|
| 237 |
+
assert failed_run.kwargs["shell"] is False # type: ignore[attr-defined]
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def test_jxl_preflight_reports_version_and_argv_without_silent_fallback() -> None:
|
| 241 |
+
result = {item.name: item for item in preflight_codecs()}["jxl_d1"]
|
| 242 |
+
assert isinstance(result.available, bool)
|
| 243 |
+
if result.available:
|
| 244 |
+
assert result.version
|
| 245 |
+
assert result.argv
|
| 246 |
+
assert result.metadata["quality"] == 90
|
| 247 |
+
assert result.metadata["libjxl_distance"] == 1.0
|
| 248 |
+
assert result.metadata["probe_q90_bytes"] > 0
|
| 249 |
+
assert result.metadata["probe_q50_bytes"] > 0
|
| 250 |
+
assert result.metadata["probe_q90_sha256"] != result.metadata["probe_q50_sha256"]
|
| 251 |
+
assert result.metadata["probe_dimensions"] == (8, 8)
|
| 252 |
+
assert result.metadata["probe_q50_dimensions"] == (8, 8)
|
| 253 |
+
assert "ImageMagick" in result.version
|
| 254 |
+
assert result.metadata["imagemagick_full_version"] == result.version
|
| 255 |
+
assert result.metadata["libjxl_delegate_version"] == "0.11.2"
|
| 256 |
+
assert result.metadata["argv_template"] == (
|
| 257 |
+
"/etc/profiles/per-user/scratchbrulee/bin/magick", "-quality", "90", "<source>", "<target>"
|
| 258 |
+
)
|
| 259 |
+
with pytest.raises(TypeError):
|
| 260 |
+
result.metadata["argv_template"][0] = "tampered" # type: ignore[index]
|
| 261 |
+
else:
|
| 262 |
+
assert result.reason
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def test_encoded_bytes_are_readable_without_temp_artifact() -> None:
|
| 266 |
+
payload = encode_rgb(rgb_image(), CODEC_SPECS["png"])
|
| 267 |
+
with Image.open(BytesIO(payload)) as image:
|
| 268 |
+
assert image.format == "PNG"
|
| 269 |
+
assert isinstance(payload, bytes)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def test_zero_repair_does_not_mutate_and_preserves_all_finite_fp16_bits() -> None:
|
| 273 |
+
raw = np.array([0.0, -0.0, np.nextafter(np.float16(0), np.float16(1)), np.finfo(np.float16).max, np.nan], dtype=np.float16)
|
| 274 |
+
before = raw.view(np.uint16).copy()
|
| 275 |
+
result = repair_nonfinite_zero(raw, {"layer": (0, len(raw))})
|
| 276 |
+
np.testing.assert_array_equal(raw.view(np.uint16), before)
|
| 277 |
+
np.testing.assert_array_equal(result.values.view(np.uint16)[:4], before[:4])
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def test_preflight_diagnostics_are_structured_for_jxl_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 281 |
+
import pixelmodel_robustness.cross_codec as module
|
| 282 |
+
|
| 283 |
+
def failed_run(*args: object, **kwargs: object) -> object:
|
| 284 |
+
return type("Completed", (), {"returncode": 9, "stdout": "version out", "stderr": "format err"})()
|
| 285 |
+
|
| 286 |
+
monkeypatch.setattr(module.subprocess, "run", failed_run)
|
| 287 |
+
result = module._jxl_version()
|
| 288 |
+
assert result.available is False
|
| 289 |
+
assert result.diagnostics == {
|
| 290 |
+
"argv": (module.MAGICK_PATH, "-version"), "stdout": "version out", "stderr": "format err", "returncode": 9,
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def test_non_jxl_encode_does_not_run_global_or_jxl_preflight(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 295 |
+
import pixelmodel_robustness.cross_codec as module
|
| 296 |
+
|
| 297 |
+
monkeypatch.setattr(module, "preflight_codecs", lambda: (_ for _ in ()).throw(AssertionError("global preflight")))
|
| 298 |
+
monkeypatch.setattr(module, "_jxl_version", lambda: (_ for _ in ()).throw(AssertionError("JXL probe")))
|
| 299 |
+
payload = encode_rgb(rgb_image(), CODEC_SPECS["png"])
|
| 300 |
+
assert payload.startswith(b"\x89PNG")
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def test_avif_raw_decode_is_rgb_when_available() -> None:
|
| 304 |
+
status = {item.name: item for item in preflight_codecs()}["avif_q70"]
|
| 305 |
+
if not status.available:
|
| 306 |
+
pytest.skip(status.reason or "AVIF unavailable")
|
| 307 |
+
decoded = decode_rgb(encode_rgb(rgb_image(), CODEC_SPECS["avif_q70"]), CODEC_SPECS["avif_q70"])
|
| 308 |
+
assert decoded.mode == "RGB"
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def test_jxl_preflight_rejects_unparseable_libjxl_version(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 312 |
+
import pixelmodel_robustness.cross_codec as module
|
| 313 |
+
|
| 314 |
+
def fake_run(args: list[str]) -> object:
|
| 315 |
+
if args == ["-version"]:
|
| 316 |
+
return type("Completed", (), {"returncode": 0, "stdout": "ImageMagick Delegates: jxl", "stderr": ""})()
|
| 317 |
+
if args == ["-list", "format"]:
|
| 318 |
+
return type("Completed", (), {"returncode": 8, "stdout": "format out", "stderr": "format err"})()
|
| 319 |
+
raise AssertionError(args)
|
| 320 |
+
|
| 321 |
+
monkeypatch.setattr(module, "_jxl_run", fake_run)
|
| 322 |
+
result = module._jxl_version()
|
| 323 |
+
assert result.available is False
|
| 324 |
+
assert result.reason
|
| 325 |
+
assert result.diagnostics == {
|
| 326 |
+
"argv": (module.MAGICK_PATH, "-list", "format"), "stdout": "format out", "stderr": "format err", "returncode": 8,
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def test_protected_high_rejects_l_mode_non_png_formats() -> None:
|
| 331 |
+
values = np.arange(6, dtype=np.float16)
|
| 332 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 333 |
+
for image_format in ("BMP", "TIFF"):
|
| 334 |
+
stream = BytesIO()
|
| 335 |
+
Image.fromarray(np.zeros((2, 3), dtype=np.uint8), "L").save(stream, format=image_format)
|
| 336 |
+
tampered = ProtectedArtifact(stream.getvalue(), artifact.low_codec_bytes, artifact.low_codec)
|
| 337 |
+
with pytest.raises(ValueError, match="format"):
|
| 338 |
+
decode_protected_high(tampered, (2, 3), values)
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def test_protected_low_codec_has_immutable_lossy_allowlist() -> None:
|
| 342 |
+
import pixelmodel_robustness.cross_codec as module
|
| 343 |
+
|
| 344 |
+
assert module.LOSSY_CODEC_NAMES == ("jpeg_q100", "jpeg_q80", "webp_q80", "avif_q70", "jxl_d1")
|
| 345 |
+
values = np.arange(6, dtype=np.float16)
|
| 346 |
+
for name in ("png", "webp_lossless"):
|
| 347 |
+
with pytest.raises(ValueError, match="lossy"):
|
| 348 |
+
encode_protected_high(values, (2, 3), CODEC_SPECS[name])
|
| 349 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 350 |
+
with pytest.raises(ValueError, match="lossy"):
|
| 351 |
+
decode_protected_high(replace(artifact, low_codec="png"), (2, 3), values)
|
| 352 |
+
with pytest.raises(TypeError):
|
| 353 |
+
module.LOSSY_CODEC_NAMES[0] = "png" # type: ignore[index]
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def test_jxl_probe_failure_preserves_probe_diagnostics(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 357 |
+
import pixelmodel_robustness.cross_codec as module
|
| 358 |
+
|
| 359 |
+
def fake_run(args: list[str]) -> object:
|
| 360 |
+
if args == ["-version"]:
|
| 361 |
+
return type("Completed", (), {"returncode": 0, "stdout": "ImageMagick Delegates: jxl", "stderr": "version err"})()
|
| 362 |
+
if args == ["-list", "format"]:
|
| 363 |
+
return type("Completed", (), {"returncode": 0, "stdout": "JXL* rw+ JPEG XL (libjxl 0.11.2)", "stderr": "format err"})()
|
| 364 |
+
raise AssertionError(args)
|
| 365 |
+
|
| 366 |
+
expected = module.JxlCodecError(
|
| 367 |
+
"probe failed", argv=[module.MAGICK_PATH, "-quality", "90", "source", "target"],
|
| 368 |
+
stdout="probe out", stderr="probe err", returncode=13,
|
| 369 |
+
)
|
| 370 |
+
monkeypatch.setattr(module, "_jxl_run", fake_run)
|
| 371 |
+
monkeypatch.setattr(module, "_jxl_encode", lambda *args, **kwargs: (_ for _ in ()).throw(expected))
|
| 372 |
+
result = module._jxl_version()
|
| 373 |
+
assert result.available is False
|
| 374 |
+
assert result.diagnostics["probe"] == {
|
| 375 |
+
"argv": expected.argv, "stdout": expected.stdout, "stderr": expected.stderr, "returncode": expected.returncode,
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def test_preflight_diagnostics_are_immutable_for_pillow_results() -> None:
|
| 380 |
+
result = preflight_codecs()[0]
|
| 381 |
+
with pytest.raises(TypeError):
|
| 382 |
+
result.diagnostics["changed"] = True # type: ignore[index]
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
def test_condition_registry_has_exact_full_semantics() -> None:
|
| 386 |
+
expected = (
|
| 387 |
+
("png_baseline", "png", "raw", None, False),
|
| 388 |
+
("webp_lossless", "webp_lossless", "raw", None, False),
|
| 389 |
+
("jpeg_q100_raw", "jpeg_q100", "raw", None, False),
|
| 390 |
+
("jpeg_q100_repair_zero", "jpeg_q100", "repair_zero", "zero", False),
|
| 391 |
+
("jpeg_q100_protected_high", "jpeg_q100", "protected_high", None, True),
|
| 392 |
+
("jpeg_q80_raw", "jpeg_q80", "raw", None, False),
|
| 393 |
+
("jpeg_q80_repair_zero", "jpeg_q80", "repair_zero", "zero", False),
|
| 394 |
+
("jpeg_q80_protected_high", "jpeg_q80", "protected_high", None, True),
|
| 395 |
+
("webp_q80_raw", "webp_q80", "raw", None, False),
|
| 396 |
+
("webp_q80_repair_zero", "webp_q80", "repair_zero", "zero", False),
|
| 397 |
+
("webp_q80_protected_high", "webp_q80", "protected_high", None, True),
|
| 398 |
+
("avif_q70_raw", "avif_q70", "raw", None, False),
|
| 399 |
+
("avif_q70_repair_zero", "avif_q70", "repair_zero", "zero", False),
|
| 400 |
+
("avif_q70_protected_high", "avif_q70", "protected_high", None, True),
|
| 401 |
+
("jxl_d1_raw", "jxl_d1", "raw", None, False),
|
| 402 |
+
("jxl_d1_repair_zero", "jxl_d1", "repair_zero", "zero", False),
|
| 403 |
+
("jxl_d1_protected_high", "jxl_d1", "protected_high", None, True),
|
| 404 |
+
)
|
| 405 |
+
assert tuple((e.condition_id, e.codec, e.mode, e.repair, e.protected) for e in CONDITION_REGISTRY.values()) == expected
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def test_protected_encode_decode_do_not_mutate_source_bits() -> None:
|
| 409 |
+
values = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16)
|
| 410 |
+
before = values.view(np.uint16).copy()
|
| 411 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 412 |
+
decode_protected_high(artifact, (2, 3), values)
|
| 413 |
+
np.testing.assert_array_equal(values.view(np.uint16), before)
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def test_expected_formats_and_forged_codec_spec_are_binding() -> None:
|
| 417 |
+
import pixelmodel_robustness.cross_codec as module
|
| 418 |
+
|
| 419 |
+
expected = {
|
| 420 |
+
"png": "PNG", "webp_lossless": "WEBP", "jpeg_q100": "JPEG", "jpeg_q80": "JPEG",
|
| 421 |
+
"webp_q80": "WEBP", "avif_q70": "AVIF", "jxl_d1": "JXL",
|
| 422 |
+
}
|
| 423 |
+
assert dict(module.EXPECTED_FORMAT_BY_CODEC) == expected
|
| 424 |
+
with pytest.raises(TypeError):
|
| 425 |
+
module.EXPECTED_FORMAT_BY_CODEC["png"] = "JPEG" # type: ignore[index]
|
| 426 |
+
forged = replace(CODEC_SPECS["jpeg_q80"], format="PNG")
|
| 427 |
+
with pytest.raises(ValueError, match="semantic"):
|
| 428 |
+
encode_protected_high(np.arange(6, dtype=np.float16), (2, 3), forged)
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
@pytest.mark.parametrize("codec", ("jpeg_q100", "jpeg_q80", "webp_q80", "avif_q70"))
|
| 432 |
+
def test_non_jxl_decode_l_plane_rejects_payload_format_mismatch(codec: str) -> None:
|
| 433 |
+
png_payload = encode_l_plane(gray_plane(), CODEC_SPECS["png"])
|
| 434 |
+
with pytest.raises(ValueError, match="format"):
|
| 435 |
+
decode_l_plane(png_payload, CODEC_SPECS[codec])
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
@pytest.mark.parametrize("codec", ("jpeg_q100", "jpeg_q80", "webp_q80", "avif_q70", "jxl_d1"))
|
| 439 |
+
def test_protected_decode_rejects_png_bytes_for_every_lossy_codec(codec: str) -> None:
|
| 440 |
+
values = np.arange(6, dtype=np.float16)
|
| 441 |
+
artifact = encode_protected_high(values, (2, 3), CODEC_SPECS["jpeg_q80"])
|
| 442 |
+
png_payload = encode_l_plane(np.zeros((2, 3), dtype=np.uint8), CODEC_SPECS["png"])
|
| 443 |
+
tampered = replace(artifact, low_codec=codec, low_codec_bytes=png_payload)
|
| 444 |
+
with pytest.raises((ValueError, JxlCodecError)):
|
| 445 |
+
decode_protected_high(tampered, (2, 3), values)
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
@pytest.mark.parametrize("codec", ("jpeg_q100", "jpeg_q80", "webp_q80", "avif_q70", "jxl_d1"))
|
| 449 |
+
def test_decode_rgb_rejects_png_payload_for_every_lossy_codec(codec: str) -> None:
|
| 450 |
+
png_payload = encode_rgb(rgb_image(), CODEC_SPECS["png"])
|
| 451 |
+
with pytest.raises((ValueError, JxlCodecError)):
|
| 452 |
+
decode_rgb(png_payload, CODEC_SPECS[codec])
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def test_verified_preflight_token_skips_jxl_probe(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 456 |
+
import pixelmodel_robustness.cross_codec as module
|
| 457 |
+
token = {"jxl_d1": module.CodecPreflight("jxl_d1", True, "fixture", None, ("fixture",), {})}
|
| 458 |
+
monkeypatch.setattr(module, "_jxl_version", lambda: (_ for _ in ()).throw(AssertionError("probe bypassed")))
|
| 459 |
+
monkeypatch.setattr(module, "_jxl_encode", lambda image: b"jxl-payload")
|
| 460 |
+
monkeypatch.setattr(module, "_jxl_decode", lambda payload: Image.new("RGB", (2, 2)))
|
| 461 |
+
assert module.encode_rgb(rgb_image(), CODEC_SPECS["jxl_d1"], verified_preflight=token) == b"jxl-payload"
|
| 462 |
+
assert module.decode_rgb(b"jxl-payload", CODEC_SPECS["jxl_d1"], verified_preflight=token).mode == "RGB"
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def test_verified_preflight_token_rejects_missing_or_unavailable_codec() -> None:
|
| 466 |
+
import pixelmodel_robustness.cross_codec as module
|
| 467 |
+
with pytest.raises(RuntimeError, match="unavailable"):
|
| 468 |
+
module.encode_rgb(rgb_image(), CODEC_SPECS["jxl_d1"], verified_preflight={})
|
| 469 |
+
token = {"jxl_d1": module.CodecPreflight("jxl_d1", False, None, "missing", (), {})}
|
| 470 |
+
with pytest.raises(RuntimeError, match="missing"):
|
| 471 |
+
module.encode_rgb(rgb_image(), CODEC_SPECS["jxl_d1"], verified_preflight=token)
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
def test_decode_uses_canonical_format_for_forged_and_unknown_specs() -> None:
|
| 475 |
+
png_payload = encode_rgb(rgb_image(), CODEC_SPECS["png"])
|
| 476 |
+
forged = replace(CODEC_SPECS["jpeg_q80"], format="PNG")
|
| 477 |
+
unknown = CodecSpec("unknown_codec", "PNG", None, {}, {})
|
| 478 |
+
for spec in (forged, unknown):
|
| 479 |
+
with pytest.raises(ValueError, match="canonical|unknown"):
|
| 480 |
+
decode_rgb(png_payload, spec)
|
| 481 |
+
with pytest.raises(ValueError, match="canonical|unknown"):
|
| 482 |
+
decode_l_plane(png_payload, spec)
|
tests/test_cross_codec_experiment.py
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Cross-codec Task 2 runnerの厳格TDD契約。"""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
from argparse import Namespace
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pytest
|
| 11 |
+
from PIL import Image
|
| 12 |
+
|
| 13 |
+
import pixelmodel_robustness.cross_codec as codecs
|
| 14 |
+
from pixelmodel_robustness.prompts import CATEGORIES
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@pytest.fixture
|
| 18 |
+
def fixture_inputs(tmp_path: Path) -> dict[str, Path]:
|
| 19 |
+
values = np.arange(16, dtype=np.float16)
|
| 20 |
+
pixels = np.zeros((4, 4, 3), dtype=np.uint8)
|
| 21 |
+
bits = values.view(np.uint16)
|
| 22 |
+
pixels.reshape(-1, 3)[:, 0] = (bits >> 8).astype(np.uint8)
|
| 23 |
+
pixels.reshape(-1, 3)[:, 1] = bits.astype(np.uint8)
|
| 24 |
+
image = tmp_path / "model.png"
|
| 25 |
+
Image.fromarray(pixels, "RGB").save(image)
|
| 26 |
+
manifest = tmp_path / "model_png.json"
|
| 27 |
+
manifest.write_text(json.dumps({"total_parameters": 16, "params": [{"name": "a", "numel": 8}, {"name": "b", "numel": 8}]}), encoding="utf-8")
|
| 28 |
+
config = tmp_path / "config.json"
|
| 29 |
+
config.write_text(json.dumps({"architecture": "fixture"}), encoding="utf-8")
|
| 30 |
+
model = tmp_path / "model.safetensors"
|
| 31 |
+
model.write_bytes(b"fixture")
|
| 32 |
+
prompts = tmp_path / "prompts.json"
|
| 33 |
+
rows = []
|
| 34 |
+
for index, category in enumerate(CATEGORIES):
|
| 35 |
+
rows.append({"prompt_id": f"first-{category}", "category": category, "prompt": f"first {category}", "seed": index, "initial_latent_seed": index})
|
| 36 |
+
for index in range(92):
|
| 37 |
+
category = CATEGORIES[index % len(CATEGORIES)]
|
| 38 |
+
rows.append({"prompt_id": f"p-{index}", "category": category, "prompt": f"prompt {index}", "seed": 100 + index, "initial_latent_seed": 100 + index})
|
| 39 |
+
prompts.write_text(json.dumps(rows), encoding="utf-8")
|
| 40 |
+
return {"image": image, "manifest": manifest, "config": config, "model": model, "prompts": prompts}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def make_args(inputs: dict[str, Path], output: Path, **overrides: object) -> Namespace:
|
| 44 |
+
values = {"profile": "pilot", "image": str(inputs["image"]), "manifest": str(inputs["manifest"]), "config": str(inputs["config"]), "model": str(inputs["model"]), "prompts_manifest": str(inputs["prompts"]), "prompts": 8, "steps": 50, "cfg": 6.0, "device": "mps", "output": str(output), "conditions": "", "recover_lock": False, "fixture": True, "fake_runtime": False, "vae": "vae", "clip": "clip", "pilot_manifest": None}
|
| 45 |
+
values.update(overrides)
|
| 46 |
+
return Namespace(**values)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def preflight() -> tuple[codecs.CodecPreflight, ...]:
|
| 50 |
+
return tuple(codecs.CodecPreflight(name, True, "fixture", None, ("fixture", name), {"fixture": True}) for name in codecs.CODEC_SPECS)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def payload_factory(image: Image.Image, spec: codecs.CodecSpec) -> tuple[bytes, Image.Image]:
|
| 54 |
+
from io import BytesIO
|
| 55 |
+
stream = BytesIO()
|
| 56 |
+
image.convert("RGB").save(stream, format="PNG")
|
| 57 |
+
payload = stream.getvalue()
|
| 58 |
+
return payload, Image.open(BytesIO(payload)).convert("RGB")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class Runtime:
|
| 62 |
+
def generate(self, prompt: str, seed: int) -> tuple[Image.Image, float]:
|
| 63 |
+
return Image.new("RGB", (4, 4), seed % 255), 0.5
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def runtime_factory(args: Namespace, device: str) -> tuple[object, object]:
|
| 67 |
+
return (lambda path: object()), Runtime()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_pilot_selection_is_category_order_and_full_is_exact() -> None:
|
| 71 |
+
from pixelmodel_robustness.cross_codec_experiment import select_prompts
|
| 72 |
+
prompts = [{"prompt_id": f"{category}-{index}-{duplicate}", "category": category, "prompt": f"{category}-{index}-{duplicate}", "seed": index, "initial_latent_seed": index} for index, category in enumerate(CATEGORIES) for duplicate in range(2)]
|
| 73 |
+
selected = select_prompts(prompts, "pilot")
|
| 74 |
+
assert [item["prompt_id"] for item in selected] == [next(item["prompt_id"] for item in prompts if item["category"] == category) for category in CATEGORIES]
|
| 75 |
+
with pytest.raises(ValueError, match="exactly 100"):
|
| 76 |
+
select_prompts(prompts, "full")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def test_cli_contract_rejects_before_output(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 80 |
+
from pixelmodel_robustness.cross_codec_experiment import build_parser, run
|
| 81 |
+
args = build_parser().parse_args(["--profile", "pilot", "--image", str(fixture_inputs["image"]), "--manifest", str(fixture_inputs["manifest"]), "--config", str(fixture_inputs["config"]), "--model", str(fixture_inputs["model"]), "--prompts-manifest", str(fixture_inputs["prompts"]), "--output", str(tmp_path / "out"), "--prompts", "7"])
|
| 82 |
+
with pytest.raises(ValueError, match="8"):
|
| 83 |
+
run(args)
|
| 84 |
+
assert not Path(args.output).exists()
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def test_fixture_e2e_has_complete_rows_and_artifacts(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 88 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 89 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 90 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 91 |
+
weights = [row for row in rows if row["row_type"] == "weight"]
|
| 92 |
+
layers = [row for row in rows if row["row_type"] == "layer"]
|
| 93 |
+
generations = [row for row in rows if row["row_type"] == "generation"]
|
| 94 |
+
assert len(weights) == 17 and len(layers) == 34 and len(generations) == 136
|
| 95 |
+
assert all(row["attempted"] and row["completed"] and row["success"] for row in generations if not row["blocked"])
|
| 96 |
+
assert all((result / row["sample_path"]).is_file() and row["sample_bytes"] == (result / row["sample_path"]).stat().st_size and row["sample_hash"] == hashlib.sha256((result / row["sample_path"]).read_bytes()).hexdigest() for row in generations if row["success"])
|
| 97 |
+
baseline = next(row for row in weights if row["condition"] == "png_baseline")
|
| 98 |
+
assert (result / baseline["materialized"]["path"]).read_bytes() == fixture_inputs["image"].read_bytes()
|
| 99 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is True
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def test_save_sample_supports_pil_chw_and_rejects_nonfinite(tmp_path: Path) -> None:
|
| 103 |
+
from pixelmodel_robustness.cross_codec_experiment import _save_sample
|
| 104 |
+
class Tensor:
|
| 105 |
+
def __init__(self, value: np.ndarray): self.value = value
|
| 106 |
+
def detach(self): return self
|
| 107 |
+
def cpu(self): return self
|
| 108 |
+
def clamp(self, low: float, high: float): return Tensor(np.clip(self.value, low, high))
|
| 109 |
+
def permute(self, *axes: int): return Tensor(np.transpose(self.value, axes))
|
| 110 |
+
def numpy(self): return self.value
|
| 111 |
+
path = tmp_path / "nested" / "sample.png"
|
| 112 |
+
artifact = _save_sample(Tensor(np.ones((3, 4, 4), dtype=np.float32)), path, base=tmp_path)
|
| 113 |
+
assert path.is_file() and artifact["sha256"] == hashlib.sha256(path.read_bytes()).hexdigest()
|
| 114 |
+
with pytest.raises((ValueError, FloatingPointError)):
|
| 115 |
+
_save_sample(Tensor(np.full((3, 4, 4), np.nan, dtype=np.float32)), tmp_path / "bad.png", base=tmp_path)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def test_loader_called_once_per_condition_and_baseline_path_exists(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 119 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 120 |
+
calls: list[str] = []
|
| 121 |
+
def loader(path: str) -> object:
|
| 122 |
+
calls.append(path)
|
| 123 |
+
assert Path(path).is_file()
|
| 124 |
+
return object()
|
| 125 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=lambda a, d: (loader, Runtime()))
|
| 126 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 127 |
+
nonblocked = {row["condition"] for row in rows if row["row_type"] == "weight" and not row["blocked"]}
|
| 128 |
+
assert len(calls) == len(nonblocked)
|
| 129 |
+
assert any(path.endswith("png_baseline.png") for path in calls)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def test_model_loader_failure_is_one_attempt_and_gate_fails(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 133 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 134 |
+
calls: list[str] = []
|
| 135 |
+
def loader(path: str) -> object:
|
| 136 |
+
calls.append(path)
|
| 137 |
+
raise ValueError("load failed")
|
| 138 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=lambda a, d: (loader, Runtime()))
|
| 139 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 140 |
+
assert len(calls) == 17
|
| 141 |
+
assert all(not row["attempted"] and row["blocked"] and row["failure_stage"] == "model_load" and row["sample_path"] is None for row in rows if row["row_type"] == "generation")
|
| 142 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is False
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def test_raw_block_repair_count_and_shared_payload(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 146 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 147 |
+
def candidate(condition: str, values: np.ndarray) -> np.ndarray:
|
| 148 |
+
result = values.copy()
|
| 149 |
+
if condition == "jpeg_q100_raw": result[:3] = [np.nan, np.inf, -np.inf]
|
| 150 |
+
if condition == "jpeg_q80_raw": result[:3] = [np.nan, np.inf, -np.inf]
|
| 151 |
+
return result
|
| 152 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory, candidate_factory=candidate)
|
| 153 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 154 |
+
weights = {row["condition"]: row for row in rows if row["row_type"] == "weight"}
|
| 155 |
+
assert weights["jpeg_q100_raw"]["blocked"] is True
|
| 156 |
+
assert weights["jpeg_q100_repair_zero"]["repaired_count"] == 3
|
| 157 |
+
assert weights["jpeg_q100_raw"]["payload_sha256"] == weights["jpeg_q100_repair_zero"]["payload_sha256"]
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def test_payload_storage_metrics_and_protected_artifacts(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 161 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 162 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 163 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 164 |
+
weights = {row["condition"]: row for row in rows if row["row_type"] == "weight"}
|
| 165 |
+
for row in weights.values():
|
| 166 |
+
assert {"payload_bytes", "payload_over_png", "png_over_payload", "size_saving_percent"} <= row.keys()
|
| 167 |
+
protected = weights["jpeg_q100_protected_high"]
|
| 168 |
+
assert {"high", "low"} <= protected["protected"].keys()
|
| 169 |
+
assert protected["payload_bytes"] == protected["protected"]["high"]["bytes"] + protected["protected"]["low"]["bytes"]
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def test_strict_rows_and_condition_completion_reject_tamper(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 173 |
+
from pixelmodel_robustness.cross_codec_experiment import condition_complete, load_rows_strict, run
|
| 174 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 175 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 176 |
+
rows = load_rows_strict(result / "results" / "raw_rows.jsonl", manifest["run_identity_hash"])
|
| 177 |
+
sample = next(row for row in rows if row["row_type"] == "generation" and row["success"])
|
| 178 |
+
(result / sample["sample_path"]).write_bytes(b"tampered")
|
| 179 |
+
assert condition_complete(rows, sample["condition"], set(manifest["prompt_ids"]), {"a", "b"}, result) is False
|
| 180 |
+
with pytest.raises(ValueError, match="identity"):
|
| 181 |
+
load_rows_strict(result / "results" / "raw_rows.jsonl", "wrong")
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def test_strict_rows_reject_duplicate_canonical_identity(tmp_path: Path) -> None:
|
| 185 |
+
from pixelmodel_robustness.cross_codec_experiment import load_rows_strict
|
| 186 |
+
path = tmp_path / "rows.jsonl"
|
| 187 |
+
row = {"row_type": "weight", "stage": "weight", "condition": "png_baseline", "prompt_id": None, "run_identity_hash": "identity"}
|
| 188 |
+
path.write_text(json.dumps(row) + "\n" + json.dumps(row) + "\n", encoding="utf-8")
|
| 189 |
+
with pytest.raises(ValueError, match="duplicate"):
|
| 190 |
+
load_rows_strict(path, "identity")
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def test_resume_skips_completed_and_rejects_completed_resume(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 194 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 195 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 196 |
+
args = make_args(fixture_inputs, result)
|
| 197 |
+
with pytest.raises(ValueError, match="completed"):
|
| 198 |
+
run(args, preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def test_full_gate_validation_rejects_tampered_gate_before_output(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 202 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 203 |
+
pilot = run(make_args(fixture_inputs, tmp_path / "pilot"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 204 |
+
gate = pilot / "pilot_gate.json"
|
| 205 |
+
value = json.loads(gate.read_text())
|
| 206 |
+
value["prompt_ids"] = ["tampered"]
|
| 207 |
+
gate.write_text(json.dumps(value), encoding="utf-8")
|
| 208 |
+
args = make_args(fixture_inputs, tmp_path / "full", profile="full", prompts=100, fixture=False, pilot_manifest=str(gate))
|
| 209 |
+
with pytest.raises(ValueError, match="pilot gate"):
|
| 210 |
+
run(args, preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 211 |
+
assert not Path(args.output).exists()
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def test_invalid_prompt_category_and_duplicate_ids_rejected_before_output(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 215 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 216 |
+
prompts = json.loads(fixture_inputs["prompts"].read_text())
|
| 217 |
+
prompts[0]["category"] = "invalid"
|
| 218 |
+
fixture_inputs["prompts"].write_text(json.dumps(prompts), encoding="utf-8")
|
| 219 |
+
with pytest.raises(ValueError, match="category"):
|
| 220 |
+
run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def test_preflight_identity_order_and_duplicates_rejected(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 224 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 225 |
+
statuses = list(preflight())
|
| 226 |
+
statuses[0], statuses[1] = statuses[1], statuses[0]
|
| 227 |
+
with pytest.raises(RuntimeError, match="preflight"):
|
| 228 |
+
run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=lambda: tuple(statuses))
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def test_resource_sample_is_measured_and_three_violation_guard(fixture_inputs: dict[str, Path], tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
| 232 |
+
from pixelmodel_robustness import cross_codec_experiment as experiment
|
| 233 |
+
snapshot = experiment.sample_resource(tmp_path)
|
| 234 |
+
assert snapshot["rss_bytes"] > 0 and snapshot["system_available_percent"] > 0 and snapshot["disk_free_bytes"] > 0
|
| 235 |
+
monkeypatch.setattr(experiment, "sample_resource", lambda output: {"rss_bytes": 13 * 1024**3, "system_available_percent": 5.0, "disk_free_bytes": 1})
|
| 236 |
+
with pytest.raises(experiment.ResourceGuardError):
|
| 237 |
+
experiment.run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight)
|
| 238 |
+
assert not (tmp_path / "out").exists()
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def test_generation_failure_has_no_fake_sample(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 242 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 243 |
+
class Failing(Runtime):
|
| 244 |
+
def generate(self, prompt: str, seed: int): raise FloatingPointError("nonfinite image")
|
| 245 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=lambda a, d: (lambda path: object(), Failing()))
|
| 246 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 247 |
+
assert all(not row["success"] and row["sample_path"] is None and row["clip_score"] is None and row["first_error"] for row in rows if row["row_type"] == "generation")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def test_generation_row_schema_and_layer_rows_have_no_prompt_hack(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 251 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 252 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 253 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 254 |
+
generation = next(row for row in rows if row["row_type"] == "generation")
|
| 255 |
+
assert {"category", "prompt", "seed", "initial_latent_seed", "attempt_count", "completed", "elapsed_seconds", "failure_stage", "failure_class", "first_error", "sample_path", "sample_hash", "sample_bytes"} <= generation.keys()
|
| 256 |
+
assert all(not str(row.get("prompt_id", "")).startswith("layer:") for row in rows if row["row_type"] == "layer")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def test_full_fixture_gate_binding_unit_does_not_create_gate(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 260 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 261 |
+
pilot = run(make_args(fixture_inputs, tmp_path / "pilot"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 262 |
+
gate = pilot / "pilot_gate.json"
|
| 263 |
+
args = make_args(fixture_inputs, tmp_path / "full", profile="full", prompts=100, fixture=False, pilot_manifest=str(gate))
|
| 264 |
+
with pytest.raises(ValueError):
|
| 265 |
+
run(args, preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 266 |
+
assert not (Path(args.output) / "pilot_gate.json").exists()
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def test_preflight_unavailable_fails_before_output(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 270 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 271 |
+
def unavailable() -> tuple[codecs.CodecPreflight, ...]:
|
| 272 |
+
return tuple(codecs.CodecPreflight(name, name != "avif_q70", "fixture", None if name != "avif_q70" else "missing", ("fixture", name), {}) for name in codecs.CODEC_SPECS)
|
| 273 |
+
with pytest.raises(RuntimeError, match="preflight"):
|
| 274 |
+
run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=unavailable)
|
| 275 |
+
assert not (tmp_path / "out").exists()
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
@pytest.mark.parametrize("artifact_key", ["sample", "payload", "materialized", "protected_high", "protected_low"])
|
| 279 |
+
def test_validate_pilot_gate_rehashes_every_persisted_artifact(fixture_inputs: dict[str, Path], tmp_path: Path, artifact_key: str) -> None:
|
| 280 |
+
from pixelmodel_robustness.cross_codec_experiment import run, validate_pilot_gate
|
| 281 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 282 |
+
rows_path = result / "results" / "raw_rows.jsonl"
|
| 283 |
+
rows = [json.loads(line) for line in rows_path.read_text().splitlines()]
|
| 284 |
+
if artifact_key == "sample":
|
| 285 |
+
target = next(row for row in rows if row["row_type"] == "generation" and row["success"])
|
| 286 |
+
path = result / target["sample_path"]
|
| 287 |
+
elif artifact_key == "payload":
|
| 288 |
+
target = next(row for row in rows if row["row_type"] == "weight")
|
| 289 |
+
path = result / target["payload"]["path"]
|
| 290 |
+
elif artifact_key == "materialized":
|
| 291 |
+
target = next(row for row in rows if row["row_type"] == "weight" and row["materialized"])
|
| 292 |
+
path = result / target["materialized"]["path"]
|
| 293 |
+
else:
|
| 294 |
+
target = next(row for row in rows if row["row_type"] == "weight" and row["protected"])
|
| 295 |
+
path = result / target["protected"]["high" if artifact_key.endswith("high") else "low"]["path"]
|
| 296 |
+
path.write_bytes(path.read_bytes() + b"tamper")
|
| 297 |
+
with pytest.raises(ValueError, match="pilot gate"):
|
| 298 |
+
validate_pilot_gate(result / "pilot_gate.json")
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
@pytest.mark.parametrize("status", ["running", "resource_guard_failed"])
|
| 302 |
+
@pytest.mark.parametrize("tree_key", ["payload", "protected", "materialized"])
|
| 303 |
+
def test_resume_revalidates_existing_weight_artifact_tree(fixture_inputs: dict[str, Path], tmp_path: Path, status: str, tree_key: str) -> None:
|
| 304 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 305 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 306 |
+
manifest_path = result / "manifest.json"
|
| 307 |
+
manifest = json.loads(manifest_path.read_text())
|
| 308 |
+
manifest["status"] = status
|
| 309 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 310 |
+
rows_path = result / "results" / "raw_rows.jsonl"
|
| 311 |
+
rows = [json.loads(line) for line in rows_path.read_text().splitlines()]
|
| 312 |
+
weight = next(row for row in rows if row["row_type"] == "weight" and row[tree_key])
|
| 313 |
+
artifact = weight[tree_key]
|
| 314 |
+
if tree_key == "protected":
|
| 315 |
+
artifact = artifact["high"]
|
| 316 |
+
(result / artifact["path"]).write_bytes(b"tampered")
|
| 317 |
+
with pytest.raises(ValueError, match="artifact tamper"):
|
| 318 |
+
run(make_args(fixture_inputs, result, recover_lock=True), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def test_codec_encode_failure_is_canonical_and_gate_fails(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 322 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 323 |
+
def failing_payload(image: Image.Image, spec: codecs.CodecSpec) -> tuple[bytes, Image.Image]:
|
| 324 |
+
if spec.name == "jpeg_q80":
|
| 325 |
+
raise OSError("encoder unavailable")
|
| 326 |
+
return payload_factory(image, spec)
|
| 327 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=failing_payload, runtime_factory=runtime_factory)
|
| 328 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 329 |
+
failed = [row for row in rows if row["row_type"] == "weight" and row["condition"].startswith("jpeg_q80")]
|
| 330 |
+
assert len(failed) == 3 and all(row["status"] == "codec_pipeline_failed" and row["blocked"] for row in failed)
|
| 331 |
+
assert all(row["failure_stage"] == "encode" for row in rows if row["row_type"] == "generation" and row["condition"].startswith("jpeg_q80"))
|
| 332 |
+
assert json.loads((result / "manifest.json").read_text())["status"] == "completed_with_pipeline_failures"
|
| 333 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is False
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def test_codec_decode_failure_is_canonical_and_gate_fails(fixture_inputs: dict[str, Path], tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
| 337 |
+
import pixelmodel_robustness.cross_codec as module
|
| 338 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 339 |
+
original = module.decode_rgb
|
| 340 |
+
def failing_decode(payload: bytes, spec: codecs.CodecSpec, **kwargs: object) -> Image.Image:
|
| 341 |
+
if spec.name == "jpeg_q80":
|
| 342 |
+
raise ValueError("decoder rejected payload")
|
| 343 |
+
return original(payload, spec, **kwargs)
|
| 344 |
+
monkeypatch.setattr(module, "decode_rgb", failing_decode)
|
| 345 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, runtime_factory=runtime_factory)
|
| 346 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 347 |
+
failed = [row for row in rows if row["row_type"] == "generation" and row["condition"].startswith("jpeg_q80")]
|
| 348 |
+
assert len(failed) == 8 * 3 and all(not row["attempted"] and row["failure_stage"] == "decode" for row in failed)
|
| 349 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is False
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def test_resource_preflight_is_persisted_once(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 353 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 354 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 355 |
+
rows = [json.loads(line) for line in (result / "results" / "resource_monitor.jsonl").read_text().splitlines()]
|
| 356 |
+
preflight_rows = [row for row in rows if row["row_type"] == "resource_guard" and row["stage"] == "preflight"]
|
| 357 |
+
assert len(preflight_rows) == 1 and preflight_rows[0]["rss_bytes"] > 0
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def test_protected_payload_is_read_back_before_generation(fixture_inputs: dict[str, Path], tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
| 361 |
+
import pixelmodel_robustness.cross_codec as module
|
| 362 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 363 |
+
calls = 0
|
| 364 |
+
original = module.decode_protected_high
|
| 365 |
+
def counted(*args: object, **kwargs: object):
|
| 366 |
+
nonlocal calls
|
| 367 |
+
calls += 1
|
| 368 |
+
return original(*args, **kwargs)
|
| 369 |
+
monkeypatch.setattr(module, "decode_protected_high", counted)
|
| 370 |
+
run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 371 |
+
assert calls == 5
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def test_scientific_gate_identity_projection_is_immutable_and_profile_neutral() -> None:
|
| 375 |
+
from pixelmodel_robustness.cross_codec_experiment import SCIENTIFIC_GATE_IDENTITY_KEYS, scientific_identity_hash
|
| 376 |
+
assert isinstance(SCIENTIFIC_GATE_IDENTITY_KEYS, tuple)
|
| 377 |
+
base = {key: f"value-{key}" for key in SCIENTIFIC_GATE_IDENTITY_KEYS}
|
| 378 |
+
base["mode"] = "real"
|
| 379 |
+
pilot = {**base, "profile": "pilot", "prompt_ids": ["p1"], "prompt_selection_hash": "pilot", "completed_at": "old"}
|
| 380 |
+
full = {**base, "profile": "full", "prompt_ids": ["p1", "p2"], "prompt_selection_hash": "full", "completed_at": "new"}
|
| 381 |
+
assert scientific_identity_hash(pilot) == scientific_identity_hash(full)
|
| 382 |
+
changed = {**full, "code_hashes": "tampered"}
|
| 383 |
+
assert scientific_identity_hash(full) != scientific_identity_hash(changed)
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def test_manifest_binds_jpeg_repair_module_hash_to_run_identity(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 387 |
+
from pixelmodel_robustness import jpeg_repair
|
| 388 |
+
from pixelmodel_robustness.cross_codec_experiment import run, scientific_identity_hash, sha256_file
|
| 389 |
+
|
| 390 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 391 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 392 |
+
expected = sha256_file(jpeg_repair.__file__)
|
| 393 |
+
|
| 394 |
+
assert manifest["run_identity"]["code_hashes"]["jpeg_repair"] == expected
|
| 395 |
+
assert manifest["scientific_identity_hash"] == scientific_identity_hash(manifest["run_identity"])
|
| 396 |
+
changed = {**manifest["run_identity"], "code_hashes": {**manifest["run_identity"]["code_hashes"], "jpeg_repair": "tampered"}}
|
| 397 |
+
assert scientific_identity_hash(changed) != manifest["scientific_identity_hash"]
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def test_webp_lossless_pixel_mismatch_is_pipeline_failure(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 401 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 402 |
+
def mismatching_payload(image: Image.Image, spec: codecs.CodecSpec) -> tuple[bytes, Image.Image]:
|
| 403 |
+
payload, decoded = payload_factory(image, spec)
|
| 404 |
+
if spec.name == "webp_lossless":
|
| 405 |
+
decoded = decoded.copy()
|
| 406 |
+
decoded.putpixel((0, 0), (255, 0, 0))
|
| 407 |
+
return payload, decoded
|
| 408 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=mismatching_payload, runtime_factory=runtime_factory)
|
| 409 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 410 |
+
affected = [row for row in rows if row["condition"] == "webp_lossless"]
|
| 411 |
+
assert len([row for row in affected if row["row_type"] == "weight"]) == 1
|
| 412 |
+
assert all(row["failure_stage"] == "decode_invariant" for row in affected)
|
| 413 |
+
assert json.loads((result / "manifest.json").read_text())["status"] == "completed_with_pipeline_failures"
|
| 414 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is False
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def test_protected_decode_failure_is_condition_local(fixture_inputs: dict[str, Path], tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
| 418 |
+
import pixelmodel_robustness.cross_codec as module
|
| 419 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 420 |
+
original = module.decode_protected_high
|
| 421 |
+
def failing_decode(artifact: object, shape: object, values: object, **kwargs: object):
|
| 422 |
+
if getattr(artifact, "low_codec", None) == "jpeg_q80":
|
| 423 |
+
raise RuntimeError("protected decoder failed")
|
| 424 |
+
return original(artifact, shape, values, **kwargs)
|
| 425 |
+
monkeypatch.setattr(module, "decode_protected_high", failing_decode)
|
| 426 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 427 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 428 |
+
failed_weights = [row for row in rows if row["row_type"] == "weight" and row["status"] == "codec_pipeline_failed"]
|
| 429 |
+
assert [row["condition"] for row in failed_weights] == ["jpeg_q80_protected_high"]
|
| 430 |
+
assert all(row["failure_stage"] == "protected_invariant" for row in failed_weights)
|
| 431 |
+
assert json.loads((result / "pilot_gate.json").read_text())["pass"] is False
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def test_incomplete_failed_generation_rejects_any_sample_artifact_field(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 435 |
+
from pixelmodel_robustness.cross_codec_experiment import condition_complete, load_rows_strict, run
|
| 436 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 437 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 438 |
+
rows = load_rows_strict(result / "results" / "raw_rows.jsonl", manifest["run_identity_hash"])
|
| 439 |
+
target = next(row for row in rows if row["row_type"] == "generation")
|
| 440 |
+
target["success"] = False
|
| 441 |
+
target["attempted"] = True
|
| 442 |
+
target["completed"] = True
|
| 443 |
+
target["failure_stage"] = "generation"
|
| 444 |
+
target["sample_bytes"] = 1
|
| 445 |
+
assert condition_complete(rows, target["condition"], set(manifest["prompt_ids"]), set(manifest["layer_ids"]), result) is False
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def test_completed_manifest_records_raw_rows_bytes(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 449 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 450 |
+
|
| 451 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 452 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 453 |
+
raw = result / "results" / "raw_rows.jsonl"
|
| 454 |
+
assert manifest["raw_rows_bytes"] == raw.stat().st_size
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def test_resource_monitor_is_bound_and_gate_rejects_resource_tamper(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 458 |
+
from pixelmodel_robustness.cross_codec_experiment import run, validate_pilot_gate
|
| 459 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 460 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 461 |
+
metadata = manifest["resource_monitor"]
|
| 462 |
+
assert metadata["binding_method"] == "run_completion" and metadata["bytes"] > 0 and metadata["sha256"]
|
| 463 |
+
resource = result / metadata["path"]
|
| 464 |
+
resource.write_bytes(resource.read_bytes() + b'{}\n')
|
| 465 |
+
with pytest.raises(ValueError, match="pilot gate|resource"):
|
| 466 |
+
validate_pilot_gate(result / "pilot_gate.json")
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
def test_resource_monitor_migration_preserves_data_and_is_idempotent(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 470 |
+
from pixelmodel_robustness.cross_codec_experiment import (
|
| 471 |
+
bind_completed_resource_monitor,
|
| 472 |
+
run,
|
| 473 |
+
validate_pilot_gate,
|
| 474 |
+
validate_resource_monitor,
|
| 475 |
+
)
|
| 476 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 477 |
+
manifest_path = result / "manifest.json"
|
| 478 |
+
manifest = json.loads(manifest_path.read_text())
|
| 479 |
+
original_identity = {key: manifest[key] for key in ("source_sha256", "run_identity", "run_identity_hash", "scientific_identity_hash")}
|
| 480 |
+
manifest.pop("resource_monitor")
|
| 481 |
+
manifest["mode"] = "real"
|
| 482 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 483 |
+
tracked = [result / "results" / "raw_rows.jsonl", result / "results" / "resource_monitor.jsonl"]
|
| 484 |
+
tracked += [path for path in result.rglob("*") if path.is_file() and ("payloads" in path.parts or "samples" in path.parts)]
|
| 485 |
+
before = {str(path.relative_to(result)): (path.stat().st_size, hashlib.sha256(path.read_bytes()).hexdigest()) for path in tracked}
|
| 486 |
+
bind_completed_resource_monitor(result, fixture_inputs["image"])
|
| 487 |
+
bind_completed_resource_monitor(result, fixture_inputs["image"])
|
| 488 |
+
after = {str(path.relative_to(result)): (path.stat().st_size, hashlib.sha256(path.read_bytes()).hexdigest()) for path in tracked}
|
| 489 |
+
updated = json.loads(manifest_path.read_text())
|
| 490 |
+
assert before == after
|
| 491 |
+
assert {key: updated[key] for key in original_identity} == original_identity
|
| 492 |
+
assert updated["resource_monitor"]["binding_method"] == "post_run_verified"
|
| 493 |
+
gate_data = validate_pilot_gate(result / "pilot_gate.json")
|
| 494 |
+
assert gate_data["gate"]["resource_monitor"] == updated["resource_monitor"]
|
| 495 |
+
actual = validate_resource_monitor(result, updated, [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()])
|
| 496 |
+
assert all(updated["resource_monitor"][key] == actual[key] for key in actual)
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def test_migration_gate_rejects_forged_resource_annotations(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 500 |
+
from pixelmodel_robustness.cross_codec_experiment import (
|
| 501 |
+
_hash,
|
| 502 |
+
bind_completed_resource_monitor,
|
| 503 |
+
run,
|
| 504 |
+
sha256_file,
|
| 505 |
+
validate_pilot_gate,
|
| 506 |
+
)
|
| 507 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 508 |
+
manifest_path = result / "manifest.json"
|
| 509 |
+
manifest = json.loads(manifest_path.read_text())
|
| 510 |
+
manifest["mode"] = "real"
|
| 511 |
+
manifest.pop("resource_monitor")
|
| 512 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 513 |
+
bind_completed_resource_monitor(result, fixture_inputs["image"])
|
| 514 |
+
gate_path = result / "pilot_gate.json"
|
| 515 |
+
gate = json.loads(gate_path.read_text())
|
| 516 |
+
gate["resource_monitor"]["binding_method"] = "unknown"
|
| 517 |
+
gate_path.write_text(json.dumps(gate), encoding="utf-8")
|
| 518 |
+
with pytest.raises(ValueError, match="pilot gate|tampered"):
|
| 519 |
+
validate_pilot_gate(gate_path)
|
| 520 |
+
manifest = json.loads(manifest_path.read_text())
|
| 521 |
+
manifest["resource_monitor"]["binding_method"] = "unknown"
|
| 522 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 523 |
+
gate = json.loads(gate_path.read_text())
|
| 524 |
+
gate["resource_monitor"] = manifest["resource_monitor"]
|
| 525 |
+
gate["manifest_sha256"] = sha256_file(manifest_path)
|
| 526 |
+
gate["gate_hash"] = _hash({key: value for key, value in gate.items() if key != "gate_hash"})
|
| 527 |
+
gate_path.write_text(json.dumps(gate), encoding="utf-8")
|
| 528 |
+
with pytest.raises(ValueError, match="resource|pilot gate"):
|
| 529 |
+
validate_pilot_gate(gate_path)
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
def test_bound_resource_rejects_invalid_post_run_annotations(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 533 |
+
from pixelmodel_robustness.cross_codec_experiment import _validate_bound_resource, run, validate_resource_monitor
|
| 534 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 535 |
+
manifest = json.loads((result / "manifest.json").read_text())
|
| 536 |
+
rows = [json.loads(line) for line in (result / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 537 |
+
actual = validate_resource_monitor(result, manifest, rows)
|
| 538 |
+
for annotation in ({"bound_at": "not-utc", "binding_code_hash": "a" * 64}, {"bound_at": "2026-01-01T00:00:00+00:00", "binding_code_hash": "A" * 64}):
|
| 539 |
+
recorded = {**actual, "binding_method": "post_run_verified", **annotation}
|
| 540 |
+
with pytest.raises((ValueError, TypeError), match="bound_at|binding_code_hash"):
|
| 541 |
+
_validate_bound_resource(recorded, actual)
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
def test_resource_monitor_migration_rejects_wrong_source(fixture_inputs: dict[str, Path], tmp_path: Path) -> None:
|
| 545 |
+
from pixelmodel_robustness.cross_codec_experiment import bind_completed_resource_monitor, run
|
| 546 |
+
result = run(make_args(fixture_inputs, tmp_path / "out"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 547 |
+
manifest_path = result / "manifest.json"
|
| 548 |
+
manifest = json.loads(manifest_path.read_text())
|
| 549 |
+
manifest.pop("resource_monitor")
|
| 550 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 551 |
+
with pytest.raises(ValueError, match="source"):
|
| 552 |
+
bind_completed_resource_monitor(result, tmp_path / "wrong.png")
|
tests/test_cross_codec_report.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import csv
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from types import MappingProxyType
|
| 8 |
+
from typing import ClassVar
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pytest
|
| 12 |
+
from PIL import Image
|
| 13 |
+
|
| 14 |
+
import cross_codec_report as report
|
| 15 |
+
from pixelmodel_robustness import cross_codec
|
| 16 |
+
from pixelmodel_robustness.cross_codec import CONDITION_IDS
|
| 17 |
+
from pixelmodel_robustness.cross_codec_experiment import _hash, scientific_identity_hash, validate_resource_monitor
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _artifact(root: Path, relative: str, data: bytes) -> dict[str, object]:
|
| 21 |
+
path = root / relative
|
| 22 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 23 |
+
path.write_bytes(data)
|
| 24 |
+
return {"path": relative, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _run(tmp_path: Path, *, status: str = "completed", mode: str = "real", prompt_count: int = 2, prompt_projection: str = "base") -> Path:
|
| 28 |
+
run = tmp_path / "run"
|
| 29 |
+
run.mkdir()
|
| 30 |
+
source = np.zeros((4, 4, 3), dtype=np.uint8)
|
| 31 |
+
Image.fromarray(source, "RGB").save(run / "source.png")
|
| 32 |
+
_artifact(run, "source.png", (run / "source.png").read_bytes())
|
| 33 |
+
prompts = [
|
| 34 |
+
{"prompt_id": f"p-{i}", "category": f"cat-{i}", "prompt": f"prompt {i}", "seed": i, "initial_latent_seed": i + 10}
|
| 35 |
+
for i in range(prompt_count)
|
| 36 |
+
]
|
| 37 |
+
rows: list[dict[str, object]] = []
|
| 38 |
+
for index, condition in enumerate(CONDITION_IDS):
|
| 39 |
+
family = condition.replace("_repair_zero", "_raw").replace("_protected_high", "_raw")
|
| 40 |
+
payload = _artifact(run, f"payloads/{family}.bin", bytes([CONDITION_IDS.index(family) + 1, 2, 3]))
|
| 41 |
+
weight: dict[str, object] = {
|
| 42 |
+
"row_type": "weight", "stage": "weight", "condition": condition, "run_identity_hash": "run-hash",
|
| 43 |
+
"status": "completed", "failure_stage": None, "payload": payload, "protected": None,
|
| 44 |
+
"payload_bytes": payload["bytes"], "payload_sha256": payload["sha256"], "source_bytes": 3,
|
| 45 |
+
"payload_definition": "source payload; protected=high+low; materialized excluded",
|
| 46 |
+
"nan_count": index % 2, "posinf_count": 0, "neginf_count": 0,
|
| 47 |
+
"sign_mismatch_count": index, "exponent_mismatch_count": index * 2, "mantissa_mismatch_count": index * 3,
|
| 48 |
+
"repaired_count": 1 if "repair" in condition else 0, "mse": float(index), "mae": float(index) / 2,
|
| 49 |
+
"cosine": 1.0, "finite_pair_label": "all_elements", "observed": True,
|
| 50 |
+
}
|
| 51 |
+
if "protected_high" in condition:
|
| 52 |
+
high = _artifact(run, f"payloads/{condition}-high.png", b"high")
|
| 53 |
+
low = _artifact(run, f"payloads/{condition}-low.bin", b"low")
|
| 54 |
+
weight["protected"] = {"high": high, "low": low, "bytes": 7, "sha256": hashlib.sha256(b"highlow").hexdigest()}
|
| 55 |
+
weight["payload"] = {"high": high, "low": low, "bytes": 7, "sha256": hashlib.sha256(b"highlow").hexdigest()}
|
| 56 |
+
weight["payload_bytes"] = 7
|
| 57 |
+
weight["payload_sha256"] = hashlib.sha256(b"highlow").hexdigest()
|
| 58 |
+
rows.append(weight)
|
| 59 |
+
rows.append({"row_type": "layer", "stage": "layer", "condition": condition, "layer": "layer0", "prompt_id": None, "run_identity_hash": "run-hash", "finite": True, "mse": 0.0, "mae": 0.0, "cosine": 1.0})
|
| 60 |
+
for prompt in prompts:
|
| 61 |
+
success = not (condition == "jpeg_q80_raw" and prompt["prompt_id"] == "p-1")
|
| 62 |
+
sample = None
|
| 63 |
+
score = None
|
| 64 |
+
if success:
|
| 65 |
+
sample_path = f"samples/{condition}/{prompt['prompt_id']}.png"
|
| 66 |
+
sample = _artifact(run, sample_path, b"sample")
|
| 67 |
+
score = 0.5 + index / 100 + int(prompt["seed"]) / 1000
|
| 68 |
+
rows.append({
|
| 69 |
+
"row_type": "generation", "stage": "generation", "condition": condition,
|
| 70 |
+
"prompt_id": prompt["prompt_id"], "category": prompt["category"], "prompt": prompt["prompt"],
|
| 71 |
+
"seed": prompt["seed"], "initial_latent_seed": prompt["initial_latent_seed"],
|
| 72 |
+
"run_identity_hash": "run-hash", "attempted": True, "completed": True,
|
| 73 |
+
"success": success, "blocked": False, "failure_stage": None if success else "generation",
|
| 74 |
+
"first_error": None if success else "CLIP failed", "clip_score": score,
|
| 75 |
+
"sample": sample, "sample_path": sample["path"] if sample else None,
|
| 76 |
+
"sample_hash": sample["sha256"] if sample else None, "sample_bytes": sample["bytes"] if sample else None,
|
| 77 |
+
})
|
| 78 |
+
raw = run / "results" / "raw_rows.jsonl"
|
| 79 |
+
raw.parent.mkdir()
|
| 80 |
+
raw.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in rows), encoding="utf-8")
|
| 81 |
+
resource = run / "results" / "resource_monitor.jsonl"
|
| 82 |
+
resource_rows = [{"row_type": "resource_guard", "stage": "preflight", "condition": None, "prompt_id": None, "run_identity_hash": "placeholder", "rss_bytes": 1, "system_available_percent": 90.0, "mps_allocated_bytes": None, "mps_driver_allocated_bytes": None, "violation_reason": None}]
|
| 83 |
+
resource_rows += [{"row_type": "resource", "stage": "resource", "condition": row["condition"], "prompt_id": row["prompt_id"], "run_identity_hash": "placeholder", "rss_bytes": 1, "system_available_percent": 90.0, "mps_allocated_bytes": None, "mps_driver_allocated_bytes": None, "violation_reason": None} for row in rows if row["row_type"] == "generation" and row["attempted"] and not row["blocked"]]
|
| 84 |
+
resource.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in resource_rows), encoding="utf-8")
|
| 85 |
+
prompt_ids = [p["prompt_id"] for p in prompts]
|
| 86 |
+
selected_prompts = prompts if prompt_projection == "base" else [{**prompt, "paired_seed": prompt["seed"]} for prompt in prompts]
|
| 87 |
+
prompt_hash = _hash(selected_prompts)
|
| 88 |
+
identity = {"source_sha256": hashlib.sha256((run / "source.png").read_bytes()).hexdigest(), "source_bytes": (run / "source.png").stat().st_size, "model_sha256": None, "config_sha256": "config", "prompt_sha256": "prompts", "upstream_revision": "upstream", "code_hashes": {}, "runtime_git": {}, "runtime_platform": {}, "package_versions": {}, "vae": {}, "clip": {}, "requested_device": "mps", "resolved_device": "mps", "mode": mode, "steps": 50, "cfg": 6.0, "profile": "pilot", "prompt_ids": prompt_ids, "prompt_selection_hash": prompt_hash, "condition_registry_hash": cross_codec.semantic_hash(cross_codec.CONDITION_REGISTRY), "codec_preflight": []}
|
| 89 |
+
run_hash = _hash(identity)
|
| 90 |
+
for row in rows: row["run_identity_hash"] = run_hash
|
| 91 |
+
raw.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in rows), encoding="utf-8")
|
| 92 |
+
resource_rows = [{**row, "run_identity_hash": run_hash} for row in resource_rows]
|
| 93 |
+
resource.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in resource_rows), encoding="utf-8")
|
| 94 |
+
resource_metadata = validate_resource_monitor(run, {"run_identity_hash": run_hash}, rows)
|
| 95 |
+
manifest = {
|
| 96 |
+
"status": status, "mode": mode, "profile": "pilot", "run_identity": identity,
|
| 97 |
+
"run_identity_hash": run_hash, "scientific_identity_hash": scientific_identity_hash(identity), "raw_rows_sha256": hashlib.sha256(raw.read_bytes()).hexdigest(),
|
| 98 |
+
"raw_rows_bytes": raw.stat().st_size, "source_sha256": identity["source_sha256"], "source_bytes": identity["source_bytes"],
|
| 99 |
+
"condition_registry_hash": cross_codec.semantic_hash(cross_codec.CONDITION_REGISTRY), "conditions": list(CONDITION_IDS), "prompt_ids": prompt_ids, "layer_ids": ["layer0"],
|
| 100 |
+
"bootstrap": {"seed": 20260727, "resamples": 10000}, "prompt_selection_hash": prompt_hash, "resource_monitor": {**resource_metadata, "binding_method": "run_completion"},
|
| 101 |
+
}
|
| 102 |
+
(run / "manifest.json").write_text(json.dumps(manifest, sort_keys=True), encoding="utf-8")
|
| 103 |
+
return run
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _csv(path: Path) -> list[dict[str, str]]:
|
| 107 |
+
with path.open(newline="", encoding="utf-8") as stream:
|
| 108 |
+
return list(csv.DictReader(stream))
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def test_condition_registry_is_exactly_ordered() -> None:
|
| 112 |
+
assert len(report.CONDITION_IDS) == 17
|
| 113 |
+
assert report.CONDITION_IDS == CONDITION_IDS
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def test_bootstrap_is_sorted_and_known_fixture() -> None:
|
| 117 |
+
pairs = [("p-2", 2.0), ("p-0", 0.0), ("p-1", 1.0)]
|
| 118 |
+
assert report.paired_bootstrap(pairs, seed=1, samples=1000) == report.paired_bootstrap(list(reversed(pairs)), seed=1, samples=1000)
|
| 119 |
+
result = report.paired_bootstrap(pairs, seed=1, samples=1000)
|
| 120 |
+
assert result["n"] == 3 and result["mean_delta"] == 1.0 and result["ci_low"] <= 1.0 <= result["ci_high"]
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def test_bootstrap_empty_is_na() -> None:
|
| 124 |
+
assert report.paired_bootstrap([], seed=1, samples=10)["n"] == 0
|
| 125 |
+
assert report.paired_bootstrap([], seed=1, samples=10)["ci_low"] is None
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def test_generate_exact_csv_schemas_and_counts(tmp_path: Path) -> None:
|
| 129 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 130 |
+
assert {p.name for p in output.iterdir()} >= {"codec_conditions.csv", "weight_metrics.csv", "generation_metrics.csv", "storage_metrics.csv", "summary.md", "final_conclusion.md", "index.html", "report_manifest.json"}
|
| 131 |
+
assert len(_csv(output / "codec_conditions.csv")) == len(_csv(output / "weight_metrics.csv")) == len(_csv(output / "storage_metrics.csv")) == 17
|
| 132 |
+
assert len(_csv(output / "generation_metrics.csv")) == 34
|
| 133 |
+
assert list(_csv(output / "codec_conditions.csv")[0]) == list(report.CONDITION_COLUMNS)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def test_failed_clip_is_na_not_zero(tmp_path: Path) -> None:
|
| 137 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 138 |
+
failed = next(row for row in _csv(output / "generation_metrics.csv") if row["condition_id"] == "jpeg_q80_raw" and row["prompt_id"] == "p-1")
|
| 139 |
+
assert failed["clip_score"] in {"", "N/A"} and failed["success"] == "0"
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def test_condition_aggregate_has_fixed_denominator_and_failure_stage(tmp_path: Path) -> None:
|
| 143 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 144 |
+
summary = _csv(output / "condition_summary.csv")
|
| 145 |
+
row = next(item for item in summary if item["condition_id"] == "jpeg_q80_raw")
|
| 146 |
+
assert row["attempted"] == "2" and row["completed"] == "2" and row["denominator"] == "2" and row["first_failure_stage"] == "generation"
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def test_payload_protected_raw_repair_share_and_materialized_exclusion(tmp_path: Path) -> None:
|
| 150 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 151 |
+
rows = _csv(output / "storage_metrics.csv")
|
| 152 |
+
assert all(row["payload_definition"].startswith("encoded payload") for row in rows)
|
| 153 |
+
assert all(row["payload_bytes"] != "" for row in rows)
|
| 154 |
+
assert report.validate_protected_payload_rows(rows)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def test_grid_has_thirteen_columns_and_q80_grid(tmp_path: Path) -> None:
|
| 158 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 159 |
+
assert report.GRID_COLUMNS == ("PNG", "JPEG Q100 Raw", "JPEG Q100 Repair", "JPEG Q100 Protected", "WebP Q80 Raw", "WebP Q80 Repair", "WebP Q80 Protected", "AVIF Q70 Raw", "AVIF Q70 Repair", "AVIF Q70 Protected", "JXL d1 Raw", "JXL d1 Repair", "JXL d1 Protected")
|
| 160 |
+
for name in ("generation_comparison_grid.png", "generation_comparison_grid_jpeg_q80.png"):
|
| 161 |
+
assert (output / "plots" / name).is_file()
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def test_all_plots_are_decodable_and_nonempty(tmp_path: Path) -> None:
|
| 165 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 166 |
+
for path in (output / "plots").glob("*.png"):
|
| 167 |
+
with Image.open(path) as image:
|
| 168 |
+
assert image.width >= 400 and image.height >= 200
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def test_identity_binds_required_inputs(tmp_path: Path) -> None:
|
| 172 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 173 |
+
manifest = json.loads((output / "report_manifest.json").read_text())
|
| 174 |
+
for key in ("run_identity_hash", "raw_rows_sha256", "report_code_hash", "condition_registry_hash", "profile", "prompt_selection_hash"):
|
| 175 |
+
assert key in manifest["report_identity"]
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def test_same_identity_is_deterministic_except_generated_at(tmp_path: Path) -> None:
|
| 179 |
+
run = _run(tmp_path)
|
| 180 |
+
first = report.generate_report(run, fixture=True)
|
| 181 |
+
before = {str(p.relative_to(first)): p.read_bytes() for p in first.rglob("*") if p.is_file() and p.name != "report_manifest.json"}
|
| 182 |
+
second = report.generate_report(run, fixture=True)
|
| 183 |
+
after = {str(p.relative_to(second)): p.read_bytes() for p in second.rglob("*") if p.is_file() and p.name != "report_manifest.json"}
|
| 184 |
+
assert before == after
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def test_identity_mismatch_rejected_without_force(tmp_path: Path) -> None:
|
| 188 |
+
run = _run(tmp_path)
|
| 189 |
+
report.generate_report(run, fixture=True)
|
| 190 |
+
value = json.loads((run / "manifest.json").read_text())
|
| 191 |
+
value["prompt_selection_hash"] = "changed"
|
| 192 |
+
(run / "manifest.json").write_text(json.dumps(value), encoding="utf-8")
|
| 193 |
+
with pytest.raises(ValueError, match="identity|hash"):
|
| 194 |
+
report.generate_report(run, fixture=True)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def test_force_report_allows_replacement(tmp_path: Path) -> None:
|
| 198 |
+
run = _run(tmp_path)
|
| 199 |
+
report.generate_report(run, fixture=True, report_options={"version": "A"})
|
| 200 |
+
assert report.generate_report(run, fixture=True, report_options={"version": "B"}, force_report=True).is_dir()
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def test_report_manifest_contains_relative_rehashed_artifacts(tmp_path: Path) -> None:
|
| 204 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 205 |
+
manifest = json.loads((output / "report_manifest.json").read_text())
|
| 206 |
+
for artifact in manifest["artifacts"]:
|
| 207 |
+
path = output / artifact["path"]
|
| 208 |
+
assert not path.is_absolute() or str(path).startswith(str(output))
|
| 209 |
+
assert path.stat().st_size == artifact["bytes"]
|
| 210 |
+
assert hashlib.sha256(path.read_bytes()).hexdigest() == artifact["sha256"]
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def test_raw_tamper_rejected(tmp_path: Path) -> None:
|
| 214 |
+
run = _run(tmp_path)
|
| 215 |
+
(run / "results" / "raw_rows.jsonl").write_text("tampered\n", encoding="utf-8")
|
| 216 |
+
with pytest.raises(ValueError, match="raw"):
|
| 217 |
+
report.generate_report(run, fixture=True)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def test_completed_pipeline_failure_is_rejected(tmp_path: Path) -> None:
|
| 221 |
+
run = _run(tmp_path, status="completed_with_pipeline_failures")
|
| 222 |
+
with pytest.raises(ValueError, match="pipeline|scientific"):
|
| 223 |
+
report.generate_report(run, fixture=True)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def test_fixture_mode_is_not_scientific_conclusion(tmp_path: Path) -> None:
|
| 227 |
+
run = _run(tmp_path, mode="fixture")
|
| 228 |
+
output = report.generate_report(run, fixture=True)
|
| 229 |
+
text = (output / "final_conclusion.md").read_text()
|
| 230 |
+
assert "fixture" in text.lower() and "not final" in text.lower()
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def test_summary_and_conclusion_have_observations_questions_and_fid_na(tmp_path: Path) -> None:
|
| 234 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 235 |
+
summary = (output / "summary.md").read_text()
|
| 236 |
+
conclusion = (output / "final_conclusion.md").read_text()
|
| 237 |
+
assert "Observations" in summary and "Conclusions" not in summary
|
| 238 |
+
assert "Questions" in conclusion and "FID" in conclusion and "N/A" in conclusion
|
| 239 |
+
assert all(f"Question {i}" in conclusion for i in range(1, 7))
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def test_weight_map_helper_uses_injected_decoder() -> None:
|
| 243 |
+
values = np.array([0, 1, 2, 3], dtype=np.uint8)
|
| 244 |
+
decoded = report.compute_weight_error_map(values, values + 1, decoder=lambda payload: payload)
|
| 245 |
+
assert decoded.shape == (2, 2) and float(decoded.mean()) == 1.0
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def test_missing_sample_is_annotated_not_synthesized(tmp_path: Path) -> None:
|
| 249 |
+
run = _run(tmp_path)
|
| 250 |
+
sample = run / "samples/png_baseline/p-0.png"
|
| 251 |
+
sample.unlink()
|
| 252 |
+
with pytest.raises(ValueError, match="sample|artifact|condition_complete"):
|
| 253 |
+
report.generate_report(run, fixture=True)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def test_cli_parser_has_required_options() -> None:
|
| 257 |
+
parser = report.build_parser()
|
| 258 |
+
args = parser.parse_args(["--run-dir", "run", "--force-report"])
|
| 259 |
+
assert args.run_dir == "run" and args.force_report is True and args.output is None
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def test_paired_quality_csv_has_exact_order_and_png_zero(tmp_path: Path) -> None:
|
| 263 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 264 |
+
rows = _csv(output / "paired_quality_metrics.csv")
|
| 265 |
+
assert [row["condition_id"] for row in rows] == list(CONDITION_IDS)
|
| 266 |
+
assert list(rows[0]) == list(report.PAIRED_COLUMNS)
|
| 267 |
+
assert rows[0]["paired_n"] == "2" and rows[0]["mean_delta"] == "0.0" and rows[0]["ci_low"] == "0.0" and rows[0]["ci_high"] == "0.0"
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def test_required_artifact_allowlist_and_public_manifest_verifier(tmp_path: Path) -> None:
|
| 271 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 272 |
+
assert {item["path"] for item in json.loads((output / "report_manifest.json").read_text())["artifacts"]} == report.REQUIRED_ARTIFACTS
|
| 273 |
+
assert report.verify_report_manifest(output)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def test_report_records_and_rehashes_source_and_resource_provenance(tmp_path: Path) -> None:
|
| 277 |
+
run = _run(tmp_path)
|
| 278 |
+
output = report.generate_report(run, fixture=True)
|
| 279 |
+
manifest = json.loads((output / "report_manifest.json").read_text())
|
| 280 |
+
provenance = manifest["input_provenance"]
|
| 281 |
+
assert provenance["source_sha256"] == json.loads((run / "manifest.json").read_text())["source_sha256"]
|
| 282 |
+
assert provenance["source_bytes"] == json.loads((run / "manifest.json").read_text())["source_bytes"]
|
| 283 |
+
assert "source_path" not in provenance and "source_path" not in manifest["report_identity"]
|
| 284 |
+
assert provenance["baseline_source_path"] == "payloads/png_baseline.bin"
|
| 285 |
+
assert provenance["baseline_source_sha256"] and provenance["baseline_source_bytes"] == 3
|
| 286 |
+
assert provenance["resource_monitor_path"] == "results/resource_monitor.jsonl"
|
| 287 |
+
assert provenance["resource_monitor_sha256"] and provenance["resource_monitor_bytes"] > 0
|
| 288 |
+
assert report.verify_report_manifest(output)
|
| 289 |
+
(run / provenance["baseline_source_path"]).write_bytes(b"tampered")
|
| 290 |
+
assert report.verify_report_manifest(output) is False
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def test_storage_ratios_use_baseline_payload_bytes_not_scientific_source_bytes(tmp_path: Path) -> None:
|
| 294 |
+
run = _run(tmp_path)
|
| 295 |
+
manifest_path = run / "manifest.json"
|
| 296 |
+
manifest = json.loads(manifest_path.read_text())
|
| 297 |
+
manifest["source_bytes"] = 10
|
| 298 |
+
manifest_path.write_text(json.dumps(manifest, sort_keys=True), encoding="utf-8")
|
| 299 |
+
rows = [json.loads(line) for line in (run / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 300 |
+
target = next(row for row in rows if row.get("row_type") == "weight" and row.get("condition") == "jpeg_q100_raw")
|
| 301 |
+
payload = run / target["payload"]["path"]
|
| 302 |
+
payload.write_bytes(b"123456")
|
| 303 |
+
payload_hash = hashlib.sha256(payload.read_bytes()).hexdigest()
|
| 304 |
+
for row in rows:
|
| 305 |
+
if row.get("row_type") == "weight" and row.get("condition") in {"jpeg_q100_raw", "jpeg_q100_repair_zero"}:
|
| 306 |
+
row["payload_bytes"] = 6
|
| 307 |
+
row["payload_sha256"] = payload_hash
|
| 308 |
+
row["payload"]["bytes"] = 6
|
| 309 |
+
row["payload"]["sha256"] = payload_hash
|
| 310 |
+
raw = run / "results" / "raw_rows.jsonl"
|
| 311 |
+
raw.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in rows), encoding="utf-8")
|
| 312 |
+
manifest["raw_rows_sha256"] = hashlib.sha256(raw.read_bytes()).hexdigest()
|
| 313 |
+
manifest["raw_rows_bytes"] = raw.stat().st_size
|
| 314 |
+
manifest_path.write_text(json.dumps(manifest, sort_keys=True), encoding="utf-8")
|
| 315 |
+
output = report.generate_report(run, fixture=True)
|
| 316 |
+
storage = {row["condition_id"]: row for row in _csv(output / "storage_metrics.csv")}
|
| 317 |
+
assert storage["png_baseline"]["source_bytes"] == "3"
|
| 318 |
+
assert storage["png_baseline"]["payload_over_png"] == "1.0"
|
| 319 |
+
assert storage["jpeg_q100_raw"]["payload_over_png"] == "2.0"
|
| 320 |
+
assert storage["jpeg_q100_raw"]["png_over_payload"] == "0.5"
|
| 321 |
+
assert storage["jpeg_q100_raw"]["size_saving_percent"] == "-100.0"
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def test_report_rejects_run_dir_as_output(tmp_path: Path) -> None:
|
| 325 |
+
run = _run(tmp_path)
|
| 326 |
+
with pytest.raises(ValueError, match="run directory"):
|
| 327 |
+
report.generate_report(run, run, fixture=True)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def test_task2_fixture_run_integrates_with_report(tmp_path: Path) -> None:
|
| 331 |
+
from test_cross_codec_experiment import fixture_inputs, make_args, payload_factory, preflight, runtime_factory
|
| 332 |
+
(tmp_path / "task2").mkdir(parents=True)
|
| 333 |
+
inputs = fixture_inputs.__wrapped__(tmp_path / "task2")
|
| 334 |
+
from pixelmodel_robustness.cross_codec_experiment import run
|
| 335 |
+
run_dir = run(make_args(inputs, tmp_path / "task2-run"), preflight_factory=preflight, payload_factory=payload_factory, runtime_factory=runtime_factory)
|
| 336 |
+
output = report.generate_report(run_dir, fixture=True)
|
| 337 |
+
assert len(_csv(output / "generation_metrics.csv")) == 17 * 8
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def test_actual_exponent_map_is_nonzero_and_plot_calls_helper() -> None:
|
| 341 |
+
source = np.array([0x3C, 0x00, 0, 0], dtype=np.uint8)
|
| 342 |
+
candidate = source.copy(); candidate[0] ^= 0x7C
|
| 343 |
+
mapping = report.compute_exponent_density_map(source, candidate)
|
| 344 |
+
assert float(np.nansum(mapping)) > 0.0
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _rewrite_raw(run: Path, mutator: object) -> None:
|
| 348 |
+
raw = run / "results" / "raw_rows.jsonl"
|
| 349 |
+
rows = [json.loads(line) for line in raw.read_text().splitlines()]
|
| 350 |
+
mutator(rows)
|
| 351 |
+
raw.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in rows), encoding="utf-8")
|
| 352 |
+
manifest_path = run / "manifest.json"
|
| 353 |
+
manifest = json.loads(manifest_path.read_text())
|
| 354 |
+
manifest["raw_rows_sha256"] = hashlib.sha256(raw.read_bytes()).hexdigest()
|
| 355 |
+
manifest["raw_rows_bytes"] = raw.stat().st_size
|
| 356 |
+
resource_path = run / "results" / "resource_monitor.jsonl"
|
| 357 |
+
resource_rows = [json.loads(line) for line in resource_path.read_text().splitlines()]
|
| 358 |
+
expected = {(row["condition"], str(row["prompt_id"])) for row in rows if row.get("row_type") == "generation" and row.get("attempted") is True and not row.get("blocked")}
|
| 359 |
+
resource_rows = [row for row in resource_rows if row.get("row_type") == "resource_guard" or (row.get("condition"), str(row.get("prompt_id"))) in expected]
|
| 360 |
+
resource_path.write_text("".join(json.dumps(row, sort_keys=True) + "\n" for row in resource_rows), encoding="utf-8")
|
| 361 |
+
manifest["resource_monitor"] = {**validate_resource_monitor(run, manifest, rows), "binding_method": "run_completion"}
|
| 362 |
+
manifest_path.write_text(json.dumps(manifest, sort_keys=True), encoding="utf-8")
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
@pytest.mark.parametrize("field", ["payload_bytes", "payload_sha256"])
|
| 366 |
+
def test_top_level_payload_metadata_mismatch_is_rejected(tmp_path: Path, field: str) -> None:
|
| 367 |
+
run = _run(tmp_path)
|
| 368 |
+
rows = [json.loads(line) for line in (run / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 369 |
+
weight = next(row for row in rows if row["row_type"] == "weight" and row["condition"] == "jpeg_q100_raw")
|
| 370 |
+
weight[field] = weight[field] + 1 if field == "payload_bytes" else "bad"
|
| 371 |
+
with pytest.raises(ValueError, match="payload metadata"):
|
| 372 |
+
report._verify_payload_contract(run, rows)
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def test_protected_nested_payload_mismatch_is_rejected(tmp_path: Path) -> None:
|
| 376 |
+
run = _run(tmp_path)
|
| 377 |
+
rows = [json.loads(line) for line in (run / "results" / "raw_rows.jsonl").read_text().splitlines()]
|
| 378 |
+
weight = next(row for row in rows if row["row_type"] == "weight" and row["condition"] == "jpeg_q100_protected_high")
|
| 379 |
+
weight["payload"]["bytes"] = 6
|
| 380 |
+
with pytest.raises(ValueError, match="payload metadata|protected"):
|
| 381 |
+
report._verify_payload_contract(run, rows)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def test_all_blocked_has_explicit_n_zero_na_text(tmp_path: Path) -> None:
|
| 385 |
+
run = _run(tmp_path)
|
| 386 |
+
def block(rows: list[dict[str, object]]) -> None:
|
| 387 |
+
for row in rows:
|
| 388 |
+
if row.get("row_type") == "generation":
|
| 389 |
+
row.update(success=False, blocked=True, completed=True, clip_score=None, sample=None, sample_path=None, sample_hash=None, sample_bytes=None, failure_stage="blocked")
|
| 390 |
+
_rewrite_raw(run, block)
|
| 391 |
+
output = report.generate_report(run, fixture=True)
|
| 392 |
+
paired = _csv(output / "paired_quality_metrics.csv")
|
| 393 |
+
assert all(row["paired_n"] == "0" and row["mean_delta"] == "" and row["ci_low"] == "" and row["ci_high"] == "" and row["na_reason"] == "no successful finite prompt intersection" for row in paired)
|
| 394 |
+
summary = (output / "summary.md").read_text()
|
| 395 |
+
assert "N/A (no successful finite prompt intersection; N=0)" in summary and "None" not in summary
|
| 396 |
+
assert "None" not in (output / "final_conclusion.md").read_text()
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def test_lossless_sample_sha_rates_are_condition_specific(tmp_path: Path) -> None:
|
| 400 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 401 |
+
rows = _csv(output / "condition_summary.csv")
|
| 402 |
+
by = {row["condition_id"]: row for row in rows}
|
| 403 |
+
assert by["png_baseline"]["sample_sha_match_rate"] == "1.0"
|
| 404 |
+
assert by["webp_lossless"]["sample_sha_match_rate"] == "1.0"
|
| 405 |
+
assert all(by[condition]["sample_sha_match_rate"] == "N/A" for condition in CONDITION_IDS if condition not in {"png_baseline", "webp_lossless"})
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def test_hypothesis_status_uses_observed_association_rule() -> None:
|
| 409 |
+
summaries = [{"condition_id": name, "nan_count": 1, "success": 0} for name in ("jpeg_q100_raw", "jpeg_q80_raw", "webp_q80_raw", "avif_q70_raw", "jxl_d1_raw")]
|
| 410 |
+
summaries += [{"condition_id": name, "success": 0} for name in ("jpeg_q100_repair_zero", "jpeg_q80_repair_zero", "webp_q80_repair_zero", "avif_q70_repair_zero", "jxl_d1_repair_zero")]
|
| 411 |
+
summaries += [{"condition_id": name, "success": 1, "exponent_mismatch_count": 0} for name in ("jpeg_q100_protected_high", "jpeg_q80_protected_high", "webp_q80_protected_high", "avif_q70_protected_high", "jxl_d1_protected_high")]
|
| 412 |
+
assert report.hypothesis_status(summaries) == "supported (5/5 associated lossy raw codecs)"
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
def test_json_normalize_handles_immutable_nested_preflight_metadata() -> None:
|
| 416 |
+
value = MappingProxyType({"dims": (8, 8), "nested": MappingProxyType({"x": 1})})
|
| 417 |
+
assert report._json_normalize(value) == {"dims": [8, 8], "nested": {"x": 1}}
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def test_jxl_preflight_json_normalization_invokes_verified_decode(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
| 421 |
+
payload = _artifact(tmp_path, "payload.jxl", b"jxl")
|
| 422 |
+
class Preflight:
|
| 423 |
+
name = "jxl_d1"
|
| 424 |
+
version = "1"
|
| 425 |
+
metadata: ClassVar[dict[str, object]] = {"levels": (1, 2)}
|
| 426 |
+
called: dict[str, object] = {}
|
| 427 |
+
monkeypatch.setattr(report.cross_codec, "preflight_codecs", lambda: [Preflight()])
|
| 428 |
+
monkeypatch.setattr(report.cross_codec, "decode_rgb", lambda data, spec, verified_preflight=None: called.update(data=data, verified=verified_preflight) or np.zeros((1, 1, 3), dtype=np.uint8))
|
| 429 |
+
decoded, reason = report._decode_sample_payload(tmp_path, {"payload": payload}, "jxl_d1_raw", {"codec_preflight": [{"name": "jxl_d1", "version": "1", "metadata": {"levels": [1, 2]}}]}, fixture=False)
|
| 430 |
+
assert decoded is not None and reason is None and called["verified"]
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def test_jxl_preflight_changed_identity_rejects(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
| 434 |
+
payload = _artifact(tmp_path, "payload.jxl", b"jxl")
|
| 435 |
+
class Preflight:
|
| 436 |
+
name = "jxl_d1"
|
| 437 |
+
version = "2"
|
| 438 |
+
metadata: ClassVar[dict[str, object]] = {"levels": (1, 2)}
|
| 439 |
+
monkeypatch.setattr(report.cross_codec, "preflight_codecs", lambda: [Preflight()])
|
| 440 |
+
with pytest.raises(ValueError, match="JXL preflight identity mismatch"):
|
| 441 |
+
report._decode_sample_payload(tmp_path, {"payload": payload}, "jxl_d1_raw", {"codec_preflight": [{"name": "jxl_d1", "version": "1", "metadata": {"levels": [1, 2]}}]}, fixture=False)
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def test_pilot_conclusion_uses_concrete_codec_semantics(tmp_path: Path) -> None:
|
| 445 |
+
summaries = []
|
| 446 |
+
for condition in CONDITION_IDS:
|
| 447 |
+
raw = condition.endswith("_raw")
|
| 448 |
+
protected = condition.endswith("_protected_high")
|
| 449 |
+
summaries.append({"condition_id": condition, "success": 0 if raw else 8 if protected or condition in {"png_baseline", "webp_lossless", "jpeg_q100_repair_zero"} else 0, "denominator": 8, "nan_count": 1 if raw else 0, "exponent_mismatch_count": 1 if raw else 0, "payload_over_png": 0.8 if protected else 1.0, "clip_mean": 0.9 if not raw else None, "sample_sha_match_rate": 1.0 if condition in {"png_baseline", "webp_lossless"} else "N/A"})
|
| 450 |
+
paired = [{"condition_id": condition, "paired_n": 8, "mean_delta": 0.01, "ci_low": -0.01, "ci_high": 0.02} for condition in CONDITION_IDS]
|
| 451 |
+
report._documents(tmp_path, {"profile": "pilot"}, summaries, paired, fixture=False)
|
| 452 |
+
conclusion = (tmp_path / "final_conclusion.md").read_text()
|
| 453 |
+
assert "Hypothesis status: supported (5/5 associated lossy raw codecs)" in conclusion
|
| 454 |
+
assert "JPEG is not uniquely fragile in pilot" in conclusion
|
| 455 |
+
assert "Repair Zero recovered only JPEG Q100 (1/5)" in conclusion
|
| 456 |
+
assert "WebP Lossless" in conclusion and "WebP Q80 Protected" in conclusion
|
| 457 |
+
assert "JXL" in conclusion and "Question 6" in conclusion
|
| 458 |
+
assert "partially supported (1/5)" not in conclusion
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
def test_full_conclusion_uses_dynamic_scope_and_full_run_language(tmp_path: Path) -> None:
|
| 462 |
+
summaries = []
|
| 463 |
+
for condition in CONDITION_IDS:
|
| 464 |
+
raw = condition.endswith("_raw")
|
| 465 |
+
protected = condition.endswith("_protected_high")
|
| 466 |
+
success = 100 if condition == "png_baseline" or protected or condition == "jpeg_q100_repair_zero" else 0
|
| 467 |
+
summaries.append({
|
| 468 |
+
"condition_id": condition,
|
| 469 |
+
"success": success,
|
| 470 |
+
"denominator": 100,
|
| 471 |
+
"nan_count": 1 if raw else 0,
|
| 472 |
+
"exponent_mismatch_count": 1 if raw else 0,
|
| 473 |
+
"payload_over_png": 0.8 if protected else 1.0,
|
| 474 |
+
"clip_mean": 0.9 if success else None,
|
| 475 |
+
"sample_sha_match_rate": 1.0 if condition in {"png_baseline", "webp_lossless"} else "N/A",
|
| 476 |
+
})
|
| 477 |
+
paired = [{"condition_id": condition, "paired_n": 100, "mean_delta": 0.01, "ci_low": -0.01, "ci_high": 0.02} for condition in CONDITION_IDS]
|
| 478 |
+
|
| 479 |
+
report._documents(tmp_path, {"profile": "full"}, summaries, paired, fixture=False)
|
| 480 |
+
conclusion = (tmp_path / "final_conclusion.md").read_text()
|
| 481 |
+
|
| 482 |
+
assert "JPEG is not uniquely fragile in full run" in conclusion
|
| 483 |
+
assert "all had 0/100 blocked" in conclusion
|
| 484 |
+
assert "other four 0/100" in conclusion
|
| 485 |
+
assert "protected candidates maintained baseline success" in conclusion
|
| 486 |
+
assert "Overall observed best" in conclusion
|
| 487 |
+
assert "pilot" not in conclusion.lower()
|
| 488 |
+
assert "limited to this pilot" not in conclusion
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def test_report_options_bind_identity_and_force_replacement(tmp_path: Path) -> None:
|
| 492 |
+
run = _run(tmp_path)
|
| 493 |
+
output = report.generate_report(run, fixture=True, report_options={"label": "A"})
|
| 494 |
+
first = json.loads((output / "report_manifest.json").read_text())["report_identity_hash"]
|
| 495 |
+
with pytest.raises(ValueError, match="identity"):
|
| 496 |
+
report.generate_report(run, fixture=True, report_options={"label": "B"})
|
| 497 |
+
report.generate_report(run, fixture=True, report_options={"label": "B"}, force_report=True)
|
| 498 |
+
second = json.loads((output / "report_manifest.json").read_text())["report_identity_hash"]
|
| 499 |
+
assert first != second
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def test_manifest_verifier_rejects_traversal_sha_tamper_and_duplicate(tmp_path: Path) -> None:
|
| 503 |
+
output = report.generate_report(_run(tmp_path), fixture=True)
|
| 504 |
+
manifest_path = output / "report_manifest.json"
|
| 505 |
+
manifest = json.loads(manifest_path.read_text())
|
| 506 |
+
manifest["artifacts"][0]["path"] = "../escape"
|
| 507 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 508 |
+
assert report.verify_report_manifest(output) is False
|
| 509 |
+
(tmp_path / "again").mkdir()
|
| 510 |
+
output = report.generate_report(_run(tmp_path / "again"), fixture=True)
|
| 511 |
+
manifest_path = output / "report_manifest.json"
|
| 512 |
+
manifest = json.loads(manifest_path.read_text())
|
| 513 |
+
manifest["artifacts"][0]["sha256"] = "bad"
|
| 514 |
+
manifest["artifacts"].append(dict(manifest["artifacts"][1]))
|
| 515 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 516 |
+
assert report.verify_report_manifest(output) is False
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def test_same_run_report_manifest_is_byte_deterministic(tmp_path: Path) -> None:
|
| 520 |
+
run = _run(tmp_path)
|
| 521 |
+
output = report.generate_report(run, fixture=True)
|
| 522 |
+
first = {str(path.relative_to(output)): path.read_bytes() for path in output.rglob("*") if path.is_file()}
|
| 523 |
+
report.generate_report(run, fixture=True)
|
| 524 |
+
second = {str(path.relative_to(output)): path.read_bytes() for path in output.rglob("*") if path.is_file()}
|
| 525 |
+
assert first == second
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def test_real_runner_paired_seed_hash_projection_is_resolved(tmp_path: Path) -> None:
|
| 529 |
+
run = _run(tmp_path, prompt_projection="paired")
|
| 530 |
+
output = report.generate_report(run, fixture=True)
|
| 531 |
+
manifest = json.loads((output / "report_manifest.json").read_text())
|
| 532 |
+
assert manifest["report_identity"]["prompt_projection"] == "paired_seed_equals_seed"
|
| 533 |
+
assert manifest["input_provenance"]["prompt_projection"] == "paired_seed_equals_seed"
|
| 534 |
+
assert [row["prompt_id"] for row in _csv(output / "generation_metrics.csv")[:2]] == ["p-0", "p-1"]
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
def test_prompt_projection_rejects_unmatched_hash(tmp_path: Path) -> None:
|
| 538 |
+
prompts = [{"prompt_id": "p-0", "category": "cat", "prompt": "x", "seed": 1, "initial_latent_seed": 2}]
|
| 539 |
+
with pytest.raises(ValueError, match="prompt selection hash"):
|
| 540 |
+
report._resolve_prompt_projection(prompts, "not-a-candidate")
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
def test_prompt_projection_fails_closed_on_ambiguous_hash(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 544 |
+
prompts = [{"prompt_id": "p-0", "category": "cat", "prompt": "x", "seed": 1, "initial_latent_seed": 2}]
|
| 545 |
+
monkeypatch.setattr(report, "_hash", lambda value: "same-hash")
|
| 546 |
+
with pytest.raises(ValueError, match="ambiguous"):
|
| 547 |
+
report._resolve_prompt_projection(prompts, "same-hash")
|
tests/test_jpeg_repair.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""JPEG repair Task 1-2の契約テスト。"""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from io import BytesIO
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pytest
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
from pixelmodel_robustness.jpeg_repair import (
|
| 12 |
+
CLIP_STRATEGIES,
|
| 13 |
+
CONDITION_IDS,
|
| 14 |
+
build_jpeg_registry,
|
| 15 |
+
clip_candidate,
|
| 16 |
+
decode_jpeg_rgb,
|
| 17 |
+
decode_protected,
|
| 18 |
+
diagnostics,
|
| 19 |
+
encode_jpeg_q100,
|
| 20 |
+
encode_protected,
|
| 21 |
+
materialize_fp16_png,
|
| 22 |
+
repair_nonfinite,
|
| 23 |
+
semantic_hash,
|
| 24 |
+
validate_layer_offsets,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _image() -> Image.Image:
|
| 29 |
+
return Image.fromarray(
|
| 30 |
+
np.array([[[0, 1, 2], [20, 40, 60]], [[100, 120, 140], [240, 220, 200]]], dtype=np.uint8),
|
| 31 |
+
"RGB",
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_registry_is_exact_ordered_and_semantically_hashed() -> None:
|
| 36 |
+
registry = build_jpeg_registry()
|
| 37 |
+
assert tuple(entry.condition_id for entry in registry) == CONDITION_IDS
|
| 38 |
+
assert len(registry) == 18
|
| 39 |
+
assert len({entry.parent_raw_sha256 for entry in registry[1:17]}) == 1
|
| 40 |
+
assert semantic_hash(registry) == semantic_hash(build_jpeg_registry())
|
| 41 |
+
assert tuple(entry.condition_id for entry in registry) == CONDITION_IDS
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def test_jpeg_q100_source_is_one_reusable_decode() -> None:
|
| 45 |
+
raw = encode_jpeg_q100(_image())
|
| 46 |
+
first = decode_jpeg_rgb(raw)
|
| 47 |
+
second = decode_jpeg_rgb(raw)
|
| 48 |
+
assert raw.startswith(b"\xff\xd8")
|
| 49 |
+
np.testing.assert_array_equal(first, second)
|
| 50 |
+
assert first.shape == (2, 2, 3)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def test_fp16_flat_materialization_preserves_bits_and_png_contract() -> None:
|
| 54 |
+
values = np.array([0.0, -0.0, 1.5, -2.25, np.float16("inf")], dtype=np.float16)
|
| 55 |
+
png = materialize_fp16_png(values)
|
| 56 |
+
pixels = np.asarray(Image.open(BytesIO(png)).convert("RGB"), dtype=np.uint8).reshape(-1, 3)
|
| 57 |
+
np.testing.assert_array_equal((pixels[: len(values), 0].astype(np.uint16) << 8) | pixels[: len(values), 1], values.view(np.uint16))
|
| 58 |
+
assert pixels[:, 2].tolist() == [0] * len(values)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_finite_repair_materialize_decode_preserves_original_uint16_bits() -> None:
|
| 62 |
+
original = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16)
|
| 63 |
+
repaired = repair_nonfinite(original, "layer_mean", {"layer": (0, len(original))}).values
|
| 64 |
+
png = materialize_fp16_png(repaired)
|
| 65 |
+
pixels = np.asarray(Image.open(BytesIO(png)).convert("RGB"), dtype=np.uint8).reshape(-1, 3)
|
| 66 |
+
decoded_bits = (pixels[: len(original), 0].astype(np.uint16) << 8) | pixels[: len(original), 1].astype(np.uint16)
|
| 67 |
+
np.testing.assert_array_equal(decoded_bits, original.view(np.uint16))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_protected_high_png_is_exact_low_jpeg_only_changes() -> None:
|
| 71 |
+
values = np.array([0.0, -0.0, 1.5, -2.25, 3.125, -4.5], dtype=np.float16)
|
| 72 |
+
artifact = encode_protected(values, (2, 3))
|
| 73 |
+
decoded, metadata = decode_protected(artifact.payload, (2, 3))
|
| 74 |
+
np.testing.assert_array_equal(decoded.view(np.uint16), values.view(np.uint16))
|
| 75 |
+
high = np.asarray(Image.open(BytesIO(artifact.high_png_bytes)).convert("L"), dtype=np.uint8)
|
| 76 |
+
np.testing.assert_array_equal(high.reshape(-1), (values.view(np.uint16) >> 8).astype(np.uint8))
|
| 77 |
+
assert metadata["payload_size_bytes"] == len(artifact.high_png_bytes) + len(artifact.low_jpeg_bytes)
|
| 78 |
+
assert metadata["high_mode"] == metadata["low_mode"] == "L"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_repair_replaces_only_nonfinite_and_reports_signed_zero_bits() -> None:
|
| 82 |
+
raw = np.array([0.0, -0.0, np.nan, np.inf, -np.inf, 2.0], dtype=np.float16)
|
| 83 |
+
result = repair_nonfinite(raw, "layer_mean", {"layer": (0, len(raw))})
|
| 84 |
+
assert result.repaired_value_count == 3
|
| 85 |
+
assert result.nan_count == result.posinf_count == result.neginf_count == 1
|
| 86 |
+
assert np.isfinite(result.values).all()
|
| 87 |
+
np.testing.assert_array_equal(result.values.view(np.uint16)[[0, 1, 5]], raw.view(np.uint16)[[0, 1, 5]])
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def test_zero_repair_accepts_all_nonfinite_layer_and_preserves_finite_bits() -> None:
|
| 91 |
+
raw = np.array([np.nan, np.inf, -np.inf, 1.5, -0.0, np.nan], dtype=np.float16)
|
| 92 |
+
original_bits = raw.view(np.uint16).copy()
|
| 93 |
+
|
| 94 |
+
result = repair_nonfinite(raw, "zero", {"all_nonfinite": (0, 3), "mixed": (3, 6)})
|
| 95 |
+
|
| 96 |
+
assert np.isfinite(result.values).all()
|
| 97 |
+
assert result.repaired_value_count == 4
|
| 98 |
+
assert result.nan_count == 2
|
| 99 |
+
assert result.posinf_count == 1
|
| 100 |
+
assert result.neginf_count == 1
|
| 101 |
+
assert [record.repaired_count for record in result.layer_records] == [3, 1]
|
| 102 |
+
assert [record.nan_count for record in result.layer_records] == [1, 1]
|
| 103 |
+
assert [record.posinf_count for record in result.layer_records] == [1, 0]
|
| 104 |
+
assert [record.neginf_count for record in result.layer_records] == [1, 0]
|
| 105 |
+
finite = np.isfinite(raw)
|
| 106 |
+
np.testing.assert_array_equal(result.values.view(np.uint16)[finite], original_bits[finite])
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_repair_rejects_empty_finite_layer() -> None:
|
| 110 |
+
raw = np.array([np.nan, np.inf], dtype=np.float16)
|
| 111 |
+
with pytest.raises(ValueError, match="finite values"):
|
| 112 |
+
repair_nonfinite(raw, "layer_median", {"empty": (0, 2)})
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@pytest.mark.parametrize("method", ["layer_median", "layer_mean"])
|
| 116 |
+
def test_repair_rejects_empty_finite_layer_for_statistical_methods(method: str) -> None:
|
| 117 |
+
raw = np.array([np.nan, np.inf, -np.inf], dtype=np.float16)
|
| 118 |
+
with pytest.raises(ValueError, match="finite values"):
|
| 119 |
+
repair_nonfinite(raw, method, {"empty": (0, 3)})
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@pytest.mark.parametrize("strategy", CLIP_STRATEGIES)
|
| 123 |
+
def test_clip_strategies_use_candidate_only_and_record_changes(strategy: str) -> None:
|
| 124 |
+
candidate = np.array([-10.0, -1.0, 0.0, 1.0, 10.0], dtype=np.float16)
|
| 125 |
+
result = clip_candidate(candidate, {"layer": (0, 5)}, strategy)
|
| 126 |
+
assert result.values.dtype == np.float16
|
| 127 |
+
assert result.layers[0].strategy == strategy
|
| 128 |
+
assert result.layers[0].changed_count == int(np.count_nonzero(result.values.view(np.uint16) != candidate.view(np.uint16)))
|
| 129 |
+
assert np.isfinite(result.values).all()
|
| 130 |
+
assert result.layers[0].lower <= result.layers[0].upper
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def test_clip_uses_population_std_and_linear_percentile() -> None:
|
| 134 |
+
candidate = np.array([0.0, 1.0, 2.0, 100.0], dtype=np.float16)
|
| 135 |
+
result = clip_candidate(candidate, {"layer": (0, 4)}, "layer_mean_std_6")
|
| 136 |
+
expected = float(np.mean(candidate.astype(np.float64)) + 6 * np.std(candidate.astype(np.float64), ddof=0))
|
| 137 |
+
assert result.layers[0].upper == pytest.approx(expected)
|
| 138 |
+
percentile = clip_candidate(candidate, {"layer": (0, 4)}, "layer_pct_0.1_99.9")
|
| 139 |
+
expected_percentile = float(np.percentile(candidate.astype(np.float64), 99.9, method="linear"))
|
| 140 |
+
assert percentile.layers[0].upper == pytest.approx(expected_percentile)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def test_clip_degenerate_and_threshold_equal_are_unchanged() -> None:
|
| 144 |
+
candidate = np.array([2.0, 2.0], dtype=np.float16)
|
| 145 |
+
result = clip_candidate(candidate, {"one": (0, 2)}, "layer_mean_std_5")
|
| 146 |
+
np.testing.assert_array_equal(result.values.view(np.uint16), candidate.view(np.uint16))
|
| 147 |
+
assert result.layers[0].changed_count == 0
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def test_fp16_bit_diagnostics_and_bounded_histogram() -> None:
|
| 151 |
+
baseline = np.array([1.0, -1.0, 0.0, 1.0], dtype=np.float16)
|
| 152 |
+
candidate = np.array([20.0, -1.0, np.float16("inf"), np.float16("nan")], dtype=np.float16)
|
| 153 |
+
result = diagnostics(baseline, candidate, {"layer": (0, 4)})
|
| 154 |
+
assert result["sign_mismatch_count"] == 0
|
| 155 |
+
assert result["exponent_mismatch_count"] == 3
|
| 156 |
+
assert result["mantissa_mismatch_count"] == 2
|
| 157 |
+
assert result["exponent_to31_count"] == 2
|
| 158 |
+
assert result["finite_extreme_outlier_count"] == 1
|
| 159 |
+
assert set(result["histograms"]) == {"baseline", "candidate", "delta"}
|
| 160 |
+
assert all(len(item["counts"]) <= 64 for item in result["histograms"].values())
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def test_registry_parent_identity_is_only_shared_by_raw_jpeg_conditions() -> None:
|
| 164 |
+
raw = encode_jpeg_q100(_image())
|
| 165 |
+
registry = build_jpeg_registry(raw)
|
| 166 |
+
assert len({entry.parent_raw_sha256 for entry in registry[1:17]}) == 1
|
| 167 |
+
assert registry[0].parent_raw_sha256 != registry[1].parent_raw_sha256
|
| 168 |
+
assert registry[17].parent_raw_sha256 != registry[1].parent_raw_sha256
|
| 169 |
+
assert registry[0].parent_raw_sha256 != registry[17].parent_raw_sha256
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def test_layer_offsets_are_contiguous_and_fail_closed() -> None:
|
| 173 |
+
assert validate_layer_offsets({"a": (0, 2), "b": (2, 4)}, 4) == {"a": (0, 2), "b": (2, 4)}
|
| 174 |
+
with pytest.raises(ValueError, match="contiguous"):
|
| 175 |
+
validate_layer_offsets({"a": (0, 2), "b": (3, 4)}, 4)
|
| 176 |
+
with pytest.raises(ValueError, match="overlap"):
|
| 177 |
+
validate_layer_offsets({"a": (0, 3), "b": (2, 4)}, 4)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def test_diagnostics_extreme_uses_layer_baseline_max_and_reports_finite_stats() -> None:
|
| 181 |
+
baseline = np.array([1.0, 2.0, 0.0, 0.0], dtype=np.float16)
|
| 182 |
+
candidate = np.array([25.0, 2.0, 1.0, 0.0], dtype=np.float16)
|
| 183 |
+
result = diagnostics(baseline, candidate, {"first": (0, 2), "zero": (2, 4)})
|
| 184 |
+
assert result["finite_extreme_outlier_count"] == 2
|
| 185 |
+
assert result["finite_count"] == 4
|
| 186 |
+
assert result["max_abs_weight"] == pytest.approx(25.0)
|
| 187 |
+
assert result["nan_count"] == result["posinf_count"] == result["neginf_count"] == 0
|
| 188 |
+
assert result["layers"][0]["finite_extreme_outlier_count"] == 1
|
| 189 |
+
assert result["layers"][1]["finite_extreme_outlier_count"] == 1
|
| 190 |
+
assert result["layers"][1]["max_abs_weight"] == pytest.approx(1.0)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def test_repair_has_layer_records_and_bounded_repaired_histogram() -> None:
|
| 194 |
+
raw = np.array([np.nan, np.inf, -np.inf, 1.0], dtype=np.float16)
|
| 195 |
+
result = repair_nonfinite(raw, "zero", {"layer": (0, 4)})
|
| 196 |
+
record = result.layer_records[0]
|
| 197 |
+
assert record.layer == "layer"
|
| 198 |
+
assert record.repaired_count == 3
|
| 199 |
+
assert record.nan_count == record.posinf_count == record.neginf_count == 1
|
| 200 |
+
assert sum(result.repaired_histogram["counts"]) == 3
|
| 201 |
+
assert len(result.repaired_histogram["counts"]) <= 64
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def test_clip_has_total_count_and_changed_value_histograms() -> None:
|
| 205 |
+
candidate = np.array([-100.0, -1.0, 0.0, 1.0, 100.0], dtype=np.float16)
|
| 206 |
+
result = clip_candidate(candidate, {"layer": (0, 5)}, "layer_mean_std_5")
|
| 207 |
+
assert result.total_clipped_count == result.layers[0].changed_count
|
| 208 |
+
assert sum(result.changed_pre_histogram["counts"]) == result.total_clipped_count
|
| 209 |
+
assert sum(result.changed_post_histogram["counts"]) == result.total_clipped_count
|
| 210 |
+
assert sum(result.changed_delta_histogram["counts"]) == result.total_clipped_count
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def test_protected_metadata_reports_decoded_planes_and_low_changes() -> None:
|
| 214 |
+
rng = np.random.default_rng(20260727)
|
| 215 |
+
values = rng.integers(0, 65536, size=32 * 32, dtype=np.uint16).view(np.float16)
|
| 216 |
+
artifact = encode_protected(values, (32, 32))
|
| 217 |
+
decoded, metadata = decode_protected(artifact.payload, (32, 32), source_values=values)
|
| 218 |
+
assert metadata["decoded_high_exact"] is True
|
| 219 |
+
assert metadata["decoded_high_sha256"]
|
| 220 |
+
assert metadata["decoded_low_sha256"]
|
| 221 |
+
assert metadata["decoded_low_changed_count"] > 0
|
| 222 |
+
assert 0.0 < metadata["decoded_low_changed_rate"] <= 1.0
|
| 223 |
+
assert metadata["pillow_version"]
|
| 224 |
+
assert metadata["combined_payload_exact"] is True
|
| 225 |
+
assert metadata["high_mode"] == metadata["low_mode"] == "L"
|
| 226 |
+
assert metadata["high_dimensions"] == metadata["low_dimensions"] == [32, 32]
|
| 227 |
+
assert metadata["combined_payload_sha256"]
|
| 228 |
+
assert metadata["combined_payload_bytes"] == metadata["high_bytes"] + metadata["low_bytes"]
|
| 229 |
+
assert "jpeg_codec_version" in metadata
|
| 230 |
+
assert decoded.shape == values.shape
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def test_combined_payload_parser_rejects_invalid_png_chunk_bounds() -> None:
|
| 234 |
+
values = np.arange(16, dtype=np.float16)
|
| 235 |
+
artifact = encode_protected(values, (4, 4))
|
| 236 |
+
broken = artifact.payload[:16] + b"\xff"
|
| 237 |
+
with pytest.raises(ValueError, match="PNG"):
|
| 238 |
+
decode_protected(broken, (4, 4))
|
tests/test_render_visual_tables.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import pytest
|
| 9 |
+
from PIL import Image
|
| 10 |
+
from render_visual_tables import CODECS, load_prompt_ids, load_rows, render
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_load_prompt_ids_accepts_manifest_object(tmp_path: Path) -> None:
|
| 14 |
+
path = tmp_path / "prompts.json"
|
| 15 |
+
path.write_text(json.dumps({"prompt_ids": ["p2", "p1"]}), encoding="utf-8")
|
| 16 |
+
|
| 17 |
+
assert load_prompt_ids(path) == ["p2", "p1"]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_load_rows_fails_closed_when_a_success_has_no_sample(tmp_path: Path) -> None:
|
| 21 |
+
run = tmp_path / "run"
|
| 22 |
+
(run / "results").mkdir(parents=True)
|
| 23 |
+
row = {"row_type": "generation", "condition": "png_baseline", "prompt_id": "p1", "success": True}
|
| 24 |
+
(run / "results/raw_rows.jsonl").write_text(json.dumps(row) + "\n", encoding="utf-8")
|
| 25 |
+
|
| 26 |
+
with pytest.raises(ValueError, match="sample"):
|
| 27 |
+
load_rows(run, ["p1"])
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_load_rows_rejects_raw_rows_symlink(tmp_path: Path) -> None:
|
| 31 |
+
run = tmp_path / "run"
|
| 32 |
+
(run / "results").mkdir(parents=True)
|
| 33 |
+
target = tmp_path / "raw_rows.jsonl"
|
| 34 |
+
target.write_text("", encoding="utf-8")
|
| 35 |
+
(run / "results/raw_rows.jsonl").symlink_to(target)
|
| 36 |
+
|
| 37 |
+
with pytest.raises(ValueError, match="symlink"):
|
| 38 |
+
load_rows(run, ["p1"])
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def test_load_rows_rejects_raw_rows_path_escape(tmp_path: Path) -> None:
|
| 42 |
+
run = tmp_path / "run"
|
| 43 |
+
(run / "results").mkdir(parents=True)
|
| 44 |
+
(run / "results/raw_rows.jsonl").symlink_to(tmp_path / "outside.jsonl")
|
| 45 |
+
(tmp_path / "outside.jsonl").write_text("", encoding="utf-8")
|
| 46 |
+
|
| 47 |
+
with pytest.raises(ValueError, match="escapes run|symlink"):
|
| 48 |
+
load_rows(run, ["p1"])
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_render_rejects_sample_replaced_with_symlink_after_validation(tmp_path: Path) -> None:
|
| 52 |
+
run = tmp_path / "run"
|
| 53 |
+
sample = run / "samples/sample.png"
|
| 54 |
+
sample.parent.mkdir(parents=True)
|
| 55 |
+
Image.new("RGB", (4, 4), "blue").save(sample)
|
| 56 |
+
sample_hash = hashlib.sha256(sample.read_bytes()).hexdigest()
|
| 57 |
+
conditions = {"png_baseline", "webp_lossless"} | {
|
| 58 |
+
condition for codec in CODECS.values() for condition in codec[1:]
|
| 59 |
+
}
|
| 60 |
+
rows = [
|
| 61 |
+
{
|
| 62 |
+
"row_type": "generation",
|
| 63 |
+
"condition": condition,
|
| 64 |
+
"prompt_id": "p1",
|
| 65 |
+
"prompt": "test prompt",
|
| 66 |
+
"category": "test",
|
| 67 |
+
"success": True,
|
| 68 |
+
"sample_path": "samples/sample.png",
|
| 69 |
+
"sample_hash": sample_hash,
|
| 70 |
+
"clip_score": 1.0,
|
| 71 |
+
}
|
| 72 |
+
for condition in sorted(conditions)
|
| 73 |
+
]
|
| 74 |
+
results = run / "results"
|
| 75 |
+
results.mkdir()
|
| 76 |
+
(results / "raw_rows.jsonl").write_text(
|
| 77 |
+
"".join(json.dumps(row) + "\n" for row in rows),
|
| 78 |
+
encoding="utf-8",
|
| 79 |
+
)
|
| 80 |
+
loaded = load_rows(run, ["p1"])
|
| 81 |
+
outside = tmp_path / "outside.png"
|
| 82 |
+
Image.new("RGB", (4, 4), "red").save(outside)
|
| 83 |
+
sample.unlink()
|
| 84 |
+
sample.symlink_to(outside)
|
| 85 |
+
|
| 86 |
+
with pytest.raises(ValueError, match="symlink"):
|
| 87 |
+
render(
|
| 88 |
+
"jpeg_q100",
|
| 89 |
+
CODECS["jpeg_q100"],
|
| 90 |
+
run.resolve(),
|
| 91 |
+
[loaded[("png_baseline", "p1")]],
|
| 92 |
+
loaded,
|
| 93 |
+
tmp_path / "output",
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def test_render_rejects_regular_sample_changed_after_validation(tmp_path: Path) -> None:
|
| 98 |
+
run = tmp_path / "run"
|
| 99 |
+
sample = run / "samples/sample.png"
|
| 100 |
+
sample.parent.mkdir(parents=True)
|
| 101 |
+
Image.new("RGB", (4, 4), "blue").save(sample)
|
| 102 |
+
sample_hash = hashlib.sha256(sample.read_bytes()).hexdigest()
|
| 103 |
+
conditions = {"png_baseline", "webp_lossless"} | {
|
| 104 |
+
condition for codec in CODECS.values() for condition in codec[1:]
|
| 105 |
+
}
|
| 106 |
+
rows = [
|
| 107 |
+
{
|
| 108 |
+
"row_type": "generation",
|
| 109 |
+
"condition": condition,
|
| 110 |
+
"prompt_id": "p1",
|
| 111 |
+
"prompt": "test prompt",
|
| 112 |
+
"category": "test",
|
| 113 |
+
"success": True,
|
| 114 |
+
"sample_path": "samples/sample.png",
|
| 115 |
+
"sample_hash": sample_hash,
|
| 116 |
+
"clip_score": 1.0,
|
| 117 |
+
}
|
| 118 |
+
for condition in sorted(conditions)
|
| 119 |
+
]
|
| 120 |
+
results = run / "results"
|
| 121 |
+
results.mkdir()
|
| 122 |
+
(results / "raw_rows.jsonl").write_text(
|
| 123 |
+
"".join(json.dumps(row) + "\n" for row in rows),
|
| 124 |
+
encoding="utf-8",
|
| 125 |
+
)
|
| 126 |
+
loaded = load_rows(run, ["p1"])
|
| 127 |
+
Image.new("RGB", (4, 4), "red").save(sample)
|
| 128 |
+
|
| 129 |
+
with pytest.raises(ValueError, match="hash mismatch"):
|
| 130 |
+
render(
|
| 131 |
+
"jpeg_q100",
|
| 132 |
+
CODECS["jpeg_q100"],
|
| 133 |
+
run.resolve(),
|
| 134 |
+
[loaded[("png_baseline", "p1")]],
|
| 135 |
+
loaded,
|
| 136 |
+
tmp_path / "output",
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="FIFO is unavailable on this platform")
|
| 141 |
+
def test_render_rejects_fifo_replacement_without_blocking(tmp_path: Path) -> None:
|
| 142 |
+
run = tmp_path / "run"
|
| 143 |
+
sample = run / "samples/sample.png"
|
| 144 |
+
sample.parent.mkdir(parents=True)
|
| 145 |
+
Image.new("RGB", (4, 4), "blue").save(sample)
|
| 146 |
+
sample_hash = hashlib.sha256(sample.read_bytes()).hexdigest()
|
| 147 |
+
conditions = {"png_baseline", "webp_lossless"} | {
|
| 148 |
+
condition for codec in CODECS.values() for condition in codec[1:]
|
| 149 |
+
}
|
| 150 |
+
rows = [
|
| 151 |
+
{
|
| 152 |
+
"row_type": "generation",
|
| 153 |
+
"condition": condition,
|
| 154 |
+
"prompt_id": "p1",
|
| 155 |
+
"prompt": "test prompt",
|
| 156 |
+
"category": "test",
|
| 157 |
+
"success": True,
|
| 158 |
+
"sample_path": "samples/sample.png",
|
| 159 |
+
"sample_hash": sample_hash,
|
| 160 |
+
"clip_score": 1.0,
|
| 161 |
+
}
|
| 162 |
+
for condition in sorted(conditions)
|
| 163 |
+
]
|
| 164 |
+
results = run / "results"
|
| 165 |
+
results.mkdir()
|
| 166 |
+
(results / "raw_rows.jsonl").write_text(
|
| 167 |
+
"".join(json.dumps(row) + "\n" for row in rows),
|
| 168 |
+
encoding="utf-8",
|
| 169 |
+
)
|
| 170 |
+
loaded = load_rows(run, ["p1"])
|
| 171 |
+
sample.unlink()
|
| 172 |
+
os.mkfifo(sample)
|
| 173 |
+
|
| 174 |
+
with pytest.raises(ValueError, match="not a regular file"):
|
| 175 |
+
render(
|
| 176 |
+
"jpeg_q100",
|
| 177 |
+
CODECS["jpeg_q100"],
|
| 178 |
+
run.resolve(),
|
| 179 |
+
[loaded[("png_baseline", "p1")]],
|
| 180 |
+
loaded,
|
| 181 |
+
tmp_path / "output",
|
| 182 |
+
)
|
tests/test_verify_archive.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import csv
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import pytest
|
| 9 |
+
from verify_archive import verify_archive
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _write_archive(root: Path) -> None:
|
| 13 |
+
for relative, content in {
|
| 14 |
+
"images/example.png": b"image",
|
| 15 |
+
"results/metrics.csv": b"condition,success\npng,1\n",
|
| 16 |
+
"samples/example.png": b"sample",
|
| 17 |
+
"weights/model.png": b"weights",
|
| 18 |
+
}.items():
|
| 19 |
+
path = root / relative
|
| 20 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 21 |
+
path.write_bytes(content)
|
| 22 |
+
rows = []
|
| 23 |
+
for relative in ("weights/model.png",):
|
| 24 |
+
path = root / relative
|
| 25 |
+
rows.append(
|
| 26 |
+
{
|
| 27 |
+
"path": relative,
|
| 28 |
+
"bytes": str(path.stat().st_size),
|
| 29 |
+
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
| 30 |
+
}
|
| 31 |
+
)
|
| 32 |
+
with (root / "weights/manifest.csv").open("w", newline="", encoding="utf-8") as handle:
|
| 33 |
+
writer = csv.DictWriter(handle, fieldnames=["path", "bytes", "sha256"])
|
| 34 |
+
writer.writeheader()
|
| 35 |
+
writer.writerows(rows)
|
| 36 |
+
manifest = {
|
| 37 |
+
"files": [
|
| 38 |
+
{
|
| 39 |
+
"path": relative,
|
| 40 |
+
"bytes": (root / relative).stat().st_size,
|
| 41 |
+
"sha256": hashlib.sha256((root / relative).read_bytes()).hexdigest(),
|
| 42 |
+
}
|
| 43 |
+
for relative in (
|
| 44 |
+
"images/example.png",
|
| 45 |
+
"results/metrics.csv",
|
| 46 |
+
"samples/example.png",
|
| 47 |
+
"weights/manifest.csv",
|
| 48 |
+
)
|
| 49 |
+
]
|
| 50 |
+
}
|
| 51 |
+
(root / "results/archive_manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _refresh_manifest(root: Path) -> None:
|
| 55 |
+
manifest_path = root / "results/archive_manifest.json"
|
| 56 |
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
| 57 |
+
manifest.pop("files", None)
|
| 58 |
+
manifest["artifacts"] = []
|
| 59 |
+
for path in sorted(p for directory in ("images", "results", "samples", "weights")
|
| 60 |
+
for p in (root / directory).rglob("*") if p.is_file() and p.name != "archive_manifest.json"):
|
| 61 |
+
relative = path.relative_to(root).as_posix()
|
| 62 |
+
manifest["artifacts"].append({"path": relative, "bytes": path.stat().st_size,
|
| 63 |
+
"sha256": hashlib.sha256(path.read_bytes()).hexdigest()})
|
| 64 |
+
manifest.update({"profile": "test", "prompt_count": 1, "conditions": ["png_baseline", "webp_lossless"]})
|
| 65 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _add_generation_rows(root: Path) -> None:
|
| 69 |
+
sample = root / "samples/example.png"
|
| 70 |
+
rows = [
|
| 71 |
+
{"row_type": "generation", "condition": "png_baseline", "prompt_id": "p1", "success": True,
|
| 72 |
+
"sample_path": "samples/example.png", "sample_bytes": sample.stat().st_size,
|
| 73 |
+
"sample_hash": hashlib.sha256(sample.read_bytes()).hexdigest(), "sample_sha256": None},
|
| 74 |
+
{"row_type": "generation", "condition": "webp_lossless", "prompt_id": "p1", "success": False,
|
| 75 |
+
"sample_path": None, "sample_bytes": None, "sample_hash": None, "sample_sha256": None},
|
| 76 |
+
]
|
| 77 |
+
(root / "results/raw_rows.jsonl").write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8")
|
| 78 |
+
_refresh_manifest(root)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_verify_archive_accepts_hash_and_containment_valid_archive(tmp_path: Path) -> None:
|
| 82 |
+
_write_archive(tmp_path)
|
| 83 |
+
_add_generation_rows(tmp_path)
|
| 84 |
+
|
| 85 |
+
report = verify_archive(tmp_path)
|
| 86 |
+
|
| 87 |
+
assert report["checked_files"] == 7
|
| 88 |
+
assert report["checked_weight_entries"] == 1
|
| 89 |
+
assert report["generation_rows"] == 2
|
| 90 |
+
assert report["successful_samples"] == 1
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def test_verify_archive_rejects_path_escape(tmp_path: Path) -> None:
|
| 94 |
+
_write_archive(tmp_path)
|
| 95 |
+
_add_generation_rows(tmp_path)
|
| 96 |
+
manifest_path = tmp_path / "results/archive_manifest.json"
|
| 97 |
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
| 98 |
+
manifest["artifacts"].append({"path": "../outside", "bytes": 0, "sha256": ""})
|
| 99 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 100 |
+
|
| 101 |
+
with pytest.raises(ValueError, match="containment"):
|
| 102 |
+
verify_archive(tmp_path)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@pytest.mark.parametrize("mutation, message", [
|
| 106 |
+
("missing", "sample"),
|
| 107 |
+
("hash", "SHA-256"),
|
| 108 |
+
("bytes", "byte count"),
|
| 109 |
+
])
|
| 110 |
+
def test_verify_archive_rejects_invalid_success_provenance(tmp_path: Path, mutation: str, message: str) -> None:
|
| 111 |
+
_write_archive(tmp_path)
|
| 112 |
+
_add_generation_rows(tmp_path)
|
| 113 |
+
raw = tmp_path / "results/raw_rows.jsonl"
|
| 114 |
+
row = json.loads(raw.read_text(encoding="utf-8").splitlines()[0])
|
| 115 |
+
if mutation == "missing":
|
| 116 |
+
row["sample_path"] = None
|
| 117 |
+
elif mutation == "hash":
|
| 118 |
+
row["sample_hash"] = "0" * 64
|
| 119 |
+
else:
|
| 120 |
+
row["sample_bytes"] += 1
|
| 121 |
+
raw.write_text(json.dumps(row) + "\n" + raw.read_text(encoding="utf-8").splitlines()[1] + "\n", encoding="utf-8")
|
| 122 |
+
_refresh_manifest(tmp_path)
|
| 123 |
+
|
| 124 |
+
with pytest.raises(ValueError, match=message):
|
| 125 |
+
verify_archive(tmp_path)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def test_verify_archive_rejects_failed_row_with_sample(tmp_path: Path) -> None:
|
| 129 |
+
_write_archive(tmp_path)
|
| 130 |
+
_add_generation_rows(tmp_path)
|
| 131 |
+
raw = tmp_path / "results/raw_rows.jsonl"
|
| 132 |
+
rows = [json.loads(line) for line in raw.read_text(encoding="utf-8").splitlines()]
|
| 133 |
+
rows[1]["sample_path"] = "samples/example.png"
|
| 134 |
+
raw.write_text("\n".join(json.dumps(row) for row in rows) + "\n", encoding="utf-8")
|
| 135 |
+
_refresh_manifest(tmp_path)
|
| 136 |
+
|
| 137 |
+
with pytest.raises(ValueError, match="failed row"):
|
| 138 |
+
verify_archive(tmp_path)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def test_verify_archive_rejects_raw_rows_symlink(tmp_path: Path) -> None:
|
| 142 |
+
_write_archive(tmp_path)
|
| 143 |
+
_add_generation_rows(tmp_path)
|
| 144 |
+
raw = tmp_path / "results/raw_rows.jsonl"
|
| 145 |
+
raw.unlink()
|
| 146 |
+
raw.symlink_to(tmp_path / "outside.jsonl")
|
| 147 |
+
(tmp_path / "outside.jsonl").write_text("", encoding="utf-8")
|
| 148 |
+
|
| 149 |
+
with pytest.raises(ValueError, match="symlink"):
|
| 150 |
+
verify_archive(tmp_path)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def test_verify_archive_rejects_duplicate_manifest_path(tmp_path: Path) -> None:
|
| 154 |
+
_write_archive(tmp_path)
|
| 155 |
+
_add_generation_rows(tmp_path)
|
| 156 |
+
manifest_path = tmp_path / "results/archive_manifest.json"
|
| 157 |
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
| 158 |
+
manifest["artifacts"].append(manifest["artifacts"][0])
|
| 159 |
+
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
| 160 |
+
|
| 161 |
+
with pytest.raises(ValueError, match="duplicate archive path"):
|
| 162 |
+
verify_archive(tmp_path)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def test_verify_archive_rejects_listed_hash_tamper(tmp_path: Path) -> None:
|
| 166 |
+
_write_archive(tmp_path)
|
| 167 |
+
_add_generation_rows(tmp_path)
|
| 168 |
+
path = tmp_path / "samples/example.png"
|
| 169 |
+
path.write_bytes(b"tampeR")
|
| 170 |
+
|
| 171 |
+
with pytest.raises(ValueError, match="SHA-256"):
|
| 172 |
+
verify_archive(tmp_path)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def test_verify_archive_rejects_archived_tree_symlink_and_unlisted_file(tmp_path: Path) -> None:
|
| 176 |
+
_write_archive(tmp_path)
|
| 177 |
+
_add_generation_rows(tmp_path)
|
| 178 |
+
extra = tmp_path / "samples/unlisted.bin"
|
| 179 |
+
extra.write_bytes(b"extra")
|
| 180 |
+
with pytest.raises(ValueError, match="unlisted"):
|
| 181 |
+
verify_archive(tmp_path)
|
| 182 |
+
extra.unlink()
|
| 183 |
+
extra.symlink_to(tmp_path / "weights/model.png")
|
| 184 |
+
|
| 185 |
+
with pytest.raises(ValueError, match="symlink"):
|
| 186 |
+
verify_archive(tmp_path)
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
weights/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Archived codec payloads
|
| 2 |
+
|
| 3 |
+
This directory stores byte-exact encoded payloads from the full run. Raw and Finite Repair Zero share the same encoded codec payload, so identical files are stored once and mapped to both conditions in `manifest.csv`. Protected High Byte stores one deduplicated lossless high-byte PNG plus one low-byte payload per lossy codec.
|
| 4 |
+
|
| 5 |
+
The reconstructed fp16 weight PNGs are not duplicated here: they are deterministic materializations of these payloads and would add substantial redundant storage. The original PixelModel-v4 source assets remain available from [`bench-labs/PixelModel-v4`](https://huggingface.co/bench-labs/PixelModel-v4) at revision `80878fdd6e130d229c2581424a3456b47b979d4b`.
|
| 6 |
+
|
| 7 |
+
All payload sizes and SHA-256 hashes are recorded in `manifest.csv`. Protected combined hashes follow the canonical experiment's high-plus-low payload definition.
|
weights/manifest.csv
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
artifact_path,role,codec,source_conditions,bytes,sha256,combined_payload_bytes,combined_payload_sha256
|
| 2 |
+
weights/codec_payloads/png_baseline.png,codec_payload,PNG baseline,png_baseline,89711808,a7b64d9d69c6ba1e428a5957bf1eae7a60145dc60cf8341300e8bdbcf80b2b31,,
|
| 3 |
+
weights/codec_payloads/webp_lossless.webp,codec_payload,WebP lossless,webp_lossless,67929870,134c0c741635bb91b0f80d386d03e7ae4b4652fe82a640d6ceb8e228eced0934,,
|
| 4 |
+
weights/codec_payloads/jpeg_q100.jpg,codec_payload,JPEG Q100,jpeg_q100_raw;jpeg_q100_repair_zero,156060019,d74c6e5e16cdf93a5afef3a95b08ef8f2d529cef452beeff71c38fe9359d08d6,,
|
| 5 |
+
weights/codec_payloads/jpeg_q80.jpg,codec_payload,JPEG Q80,jpeg_q80_raw;jpeg_q80_repair_zero,50283220,de6fadf8ec9b44b429629418d79d77b8e852a5cfcc43b0cd3035013eb59accc9,,
|
| 6 |
+
weights/codec_payloads/webp_q80.webp,codec_payload,WebP Q80,webp_q80_raw;webp_q80_repair_zero,25810802,9139ab3d637498f7fb40f71d531a674098164a3226057cbd51dfb85be1b7da70,,
|
| 7 |
+
weights/codec_payloads/avif_q70.avif,codec_payload,AVIF Q70,avif_q70_raw;avif_q70_repair_zero,29691217,b83258bd00e22d5e5d21236f5c441971c7c94b246e63d6eb6b41a1e23ce7952a,,
|
| 8 |
+
weights/codec_payloads/jxl_d1.jxl,codec_payload,JPEG XL distance 1,jxl_d1_raw;jxl_d1_repair_zero,39437937,e7ef2f74cc503897f120d3b414c61bdcb198c8c91c012fa65c668f47c119966d,,
|
| 9 |
+
weights/protected/high_byte.png,protected_high_byte,PNG lossless,jpeg_q100_protected_high;jpeg_q80_protected_high;webp_q80_protected_high;avif_q70_protected_high;jxl_d1_protected_high,32686473,82b3031ec79cf1541334e3f454a0af8705da2166f6bde7780196dd1e024df5ab,,
|
| 10 |
+
weights/protected/low_byte/jpeg_q100_low.jpg,protected_low_byte,JPEG Q100,jpeg_q100_protected_high,63199775,99c2c391d129bd1df40e58a25f79ee8b954c7e78fcb1814774b4bf7ac3a6e277,95886248,f660afccf973caa294d8778a4b29596b0ab41266102e822944c5abb6f0982f74
|
| 11 |
+
weights/protected/low_byte/jpeg_q80_low.jpg,protected_low_byte,JPEG Q80,jpeg_q80_protected_high,24229532,d32d9d1e7be1ea666a37fd761e1c74f94445562120313d01ba940711baeeddb1,56916005,30feef679679487aebd1e075cceea5823778c8cdec6643ec9e7296a04fb24000
|
| 12 |
+
weights/protected/low_byte/webp_q80_low.webp,protected_low_byte,WebP Q80,webp_q80_protected_high,23230766,f200066cf1ffbace7aaa9733d3a5c017379037e4e5fa58b8b09b3c6985655fc9,55917239,f04180148fa0decf546c978319c45c683b38d9264d6ccef14a5b0feb697226c1
|
| 13 |
+
weights/protected/low_byte/avif_q70_low.avif,protected_low_byte,AVIF Q70,avif_q70_protected_high,21791910,1698fedd0949bfdcf787576d35a81192e6f1adfc21f3499acb610d67d7a443e4,54478383,cd648cea401a6491c4f0829880ac483b497756266f6824548420606142aa7f62
|
| 14 |
+
weights/protected/low_byte/jxl_d1_low.jxl,protected_low_byte,JPEG XL distance 1,jxl_d1_protected_high,23636365,a58701006f3b535cfc27473f9ae741c7d3d888c1f7741c53d047d91c82d0a050,56322838,637e8752eb53d94ffd2f2ee997292382d67afc405139dc9a50bd4d3061cb6877
|