mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-15 05:50:45 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
380820fa7e |
No files matched your search
+8
-7
@@ -529,9 +529,9 @@ remove-ai-watermarks invisible image.png -o clean.png \
|
||||
--pipeline qwen-zimage --text-manifest verified-lines.json --force
|
||||
```
|
||||
|
||||
Use an explicit `qwen-zimage` or `chroma-zimage` profile with this option. For
|
||||
Google provenance, `auto` resolves to SDXL, whose VAE path has not been calibrated
|
||||
for verified-text restoration and is therefore rejected.
|
||||
The option works with `qwen-zimage`, `sdxl-zimage`, `chroma-zimage`, and `auto`.
|
||||
Each concrete profile reconstructs the donor through its own already loaded VAE;
|
||||
for Google provenance, `auto` therefore keeps the normal SDXL route.
|
||||
|
||||
``verified: true`` may also be set by an automated operator that attests
|
||||
machine-verified geometry: stability-gated detector boxes inside sane caps. Such
|
||||
@@ -556,10 +556,11 @@ container changes remain valid while a resized or edited source fails closed. Th
|
||||
experimental helper
|
||||
`remove_ai_watermarks._internal.text_restoration.source_pixel_sha256` computes it.
|
||||
|
||||
This mode is supported by `qwen-zimage`, `chroma-zimage`, and `auto` at native
|
||||
geometry with `humanize=0`, `unsharp=0`, and adaptive polish disabled. The legacy
|
||||
`sdxl-zimage` profile does not expose a verified-text VAE donor. `all` also accepts the flag,
|
||||
but its manifest must match the pixels entering the invisible stage; if visible-mark
|
||||
This mode is supported by every profile at native geometry with `humanize=0` and
|
||||
`unsharp=0`. An unset adaptive-polish option is forced off so verified text remains
|
||||
the final pixel-writing stage; an explicit request to enable it is rejected. `all`
|
||||
also accepts the flag, but its
|
||||
manifest must match the pixels entering the invisible stage; if visible-mark
|
||||
removal changes those pixels, the hash check rejects the run. One oracle verdict does
|
||||
not certify another manifest, seed, model/runtime version, or output hash.
|
||||
|
||||
|
||||
@@ -1839,8 +1839,8 @@ verification, and geometry-only operators do not need to invent text or script f
|
||||
|
||||
When enabled, the one profile selected before model loading reconstructs the source
|
||||
once through its already loaded VAE, runs the ordinary global and face stages, and
|
||||
calls the shared restoration compositor. Qwen and Chroma implement this donor hook;
|
||||
SDXL does not. The optional Qwen-only fidelity anchor first blends 15% of the VAE
|
||||
calls the shared restoration compositor. Qwen, SDXL, and Chroma implement this donor
|
||||
hook. The optional Qwen-only fidelity anchor first blends 15% of the VAE
|
||||
reconstruction into the clean result; it is off by default because the blend returned
|
||||
detector-visible OpenAI SynthID in the measured poster fixtures. The compositor derives
|
||||
binary source and candidate silhouettes, groups nearby lines, uses LaMa for the initial
|
||||
@@ -1854,8 +1854,9 @@ extend 8% above and 25% below. The anchored-component gate covers clipped leadin
|
||||
flourishes, trailing punctuation, icons, and descenders without walking into
|
||||
disconnected decoration or background texture.
|
||||
|
||||
The stage is deliberately narrower than the engine: it rejects `sdxl-zimage`, tiles,
|
||||
resolution caps, humanize, unsharp, and adaptive polish. Those combinations change
|
||||
The stage is deliberately narrower than the engine: it rejects tiles, resolution
|
||||
caps, humanize, unsharp, and explicitly enabled adaptive polish. An unset polish
|
||||
value resolves to off in this mode, including for SDXL, because those combinations change
|
||||
geometry or final pixels after the verified layer and have no measured oracle result.
|
||||
It remains opt-in because annotations are manual and provider verdicts apply only to
|
||||
the exact tested output hashes, not to the mechanism in general.
|
||||
@@ -1879,6 +1880,22 @@ the `chroma_text_restoration_study.py` harness (kept outside this repository)
|
||||
under the gitignored
|
||||
`out/text-restoration-engine-study/` directory.
|
||||
|
||||
The 2026-09-15 rerun added the SDXL donor implementation and exercised all three
|
||||
profiles on the same three manifests. SDXL improved text-box MAE from 11.319 to
|
||||
7.970 and from 10.704 to 7.657 on the two OpenAI posters, while PSNR rose from
|
||||
25.83 to 29.24 dB and from 26.17 to 29.04 dB. On the tracked Google CJK carrier
|
||||
(`4affd7f27767a445db6abf741355743ba8d95108ad922c9fff045feed8492236`, already
|
||||
recorded as SynthID-positive), SDXL at the calibrated 0.50 strength improved
|
||||
text-box MAE from 10.738 to 8.564 and PSNR from 23.93 to 24.00 dB. The exact
|
||||
restored output
|
||||
`035e4919e1d84129a347006c98f19eb4431eb5772788f6d2097b94a2c6baec84`
|
||||
was submitted to the Google Gemini oracle after provenance stripping preserved
|
||||
its decoded pixels; the sanitized upload hash was
|
||||
`3e4f06274a7b735f7d06863fd54dc8146f60aeb9cc8315f86294180deb660b82`.
|
||||
The oracle returned `not_detected` on 2026-09-16. This
|
||||
certifies that one carrier, manifest, seed, runtime, and output hash; it does not
|
||||
turn the compositor into a provider-wide guarantee.
|
||||
|
||||
A matched stage-isolation check on the 18-face Gemini portrait grid confirms the
|
||||
division of responsibility. The visible-cleaned, metadata-stripped control and the
|
||||
Z-Image face-only output were both SynthID-positive; Qwen global-only and the full
|
||||
@@ -1986,8 +2003,9 @@ is supported by this pass, so `auto` keeps the measured provenance-cohort table.
|
||||
|
||||
[`_internal/sdxl_zimage_pipeline.py`](../src/remove_ai_watermarks/_internal/sdxl_zimage_pipeline.py)
|
||||
runs the same two-stage recipe on an SDXL global pass. `SdxlZImagePipeline`
|
||||
subclasses `TwoStageZImagePipeline` and implements only the global stage
|
||||
(`_load_global`, `_run_global`), so the face stage is inherited rather than copied
|
||||
subclasses `TwoStageZImagePipeline` and implements the global stage
|
||||
(`_load_global`, `_run_global`) plus the deterministic verified-text VAE donor, so
|
||||
the face stage is inherited rather than copied
|
||||
and cannot drift between the profiles; a test asserts the shared methods are the
|
||||
same objects.
|
||||
|
||||
|
||||
+5
-5
@@ -780,11 +780,11 @@ engine.remove_watermark(
|
||||
|
||||
Install `remove-ai-watermarks[text-restoration]`. The manifest schema and safety
|
||||
constraints are documented in the CLI guide. The engine verifies its decoded RGB
|
||||
hash before loading the diffusion models. Qwen and Chroma reconstruct the donor with
|
||||
the VAE already loaded for the selected profile; no second generative profile runs.
|
||||
The engine rejects SDXL, including `auto` when Google provenance selects it,
|
||||
downscaling, and postprocessing combinations that were not evaluated. Tiling is
|
||||
also rejected because the combined
|
||||
hash before loading the diffusion models. Qwen, SDXL, and Chroma reconstruct the
|
||||
donor with the VAE already loaded for the selected profile; no second generative
|
||||
profile runs. The engine turns an unset `adaptive_polish` off in verified-text mode
|
||||
and rejects explicit postprocessing or downscaling combinations that were not
|
||||
evaluated. Tiling is also rejected because the combined
|
||||
tiled-restoration path has no provider-oracle calibration. `InvisibleOptions` exposes
|
||||
the same field for `remove_all`; after a visible-stage edit, the manifest must be built
|
||||
against the staged pixels rather than the pristine source.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# on it, including the ComfyUI node package. The console script below carries
|
||||
# the same weight, since users have it on PATH.
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.40.4"
|
||||
version = "0.40.5"
|
||||
description = "AI watermark remover for visible, invisible, and provenance marks in images and video"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<3.15"
|
||||
|
||||
@@ -94,6 +94,10 @@ chroma-zimage for Microsoft, qwen-zimage for OpenAI (since CLI 0.39.0), Meta and
|
||||
-- so prefer it over naming an engine when the user has no reason to care. `sdxl-zimage` is the
|
||||
heavier alternative and `chroma-zimage` is the Apache-2.0 global stage.
|
||||
|
||||
Verified-text manifests work with every profile (since CLI 0.40.5). Each profile
|
||||
uses its own already loaded VAE donor, so `auto` keeps the normal SDXL route for a
|
||||
Google image instead of switching engines.
|
||||
|
||||
```bash
|
||||
remove-ai-watermarks all image.png -o clean.png
|
||||
remove-ai-watermarks batch ./images --mode visible --output-dir ./clean
|
||||
|
||||
@@ -32,7 +32,7 @@ _GPU_MARKERS = ("GPU ", "gpu:")
|
||||
# told an agent its CLI was current right before it typed a value that build
|
||||
# rejects. tests/test_agent_skill.py pins it against the package version and
|
||||
# against every "(since CLI X.Y.Z)" annotation in the skill.
|
||||
MIN_CLI_VERSION = (0, 40, 0)
|
||||
MIN_CLI_VERSION = (0, 40, 5)
|
||||
# Markers a build without the pixel stack prints. The first is the current CLI's own
|
||||
# install hint; the rest are what older builds emit before the guard existed, and the
|
||||
# Homebrew formula ships exactly such a build.
|
||||
|
||||
@@ -33,7 +33,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
||||
_warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*")
|
||||
|
||||
|
||||
__version__ = "0.40.4"
|
||||
__version__ = "0.40.5"
|
||||
|
||||
__all__ = [
|
||||
"BatchItemResult",
|
||||
|
||||
@@ -36,7 +36,7 @@ from remove_ai_watermarks._internal.two_stage_pipeline import (
|
||||
_prompt_cache_path,
|
||||
_store_prompt_payload,
|
||||
_target_size,
|
||||
edge_pad_to_grid,
|
||||
diffusers_vae_roundtrip,
|
||||
requested_steps,
|
||||
)
|
||||
|
||||
@@ -182,28 +182,12 @@ class ChromaZImagePipeline(TwoStageZImagePipeline):
|
||||
|
||||
def _vae_roundtrip(self, image: Image.Image) -> Image.Image:
|
||||
"""Reconstruct source pixels through the already loaded Chroma VAE."""
|
||||
import torch
|
||||
|
||||
pipe = self._load_global()
|
||||
source_width, source_height = image.size
|
||||
padded = edge_pad_to_grid(image, _LATENT_GRID)
|
||||
|
||||
tensor = pipe.image_processor.preprocess(
|
||||
padded,
|
||||
height=padded.height,
|
||||
width=padded.width,
|
||||
).to(device=self.device, dtype=self.torch_dtype)
|
||||
with torch.inference_mode():
|
||||
encoded = pipe.vae.encode(tensor)
|
||||
if hasattr(encoded, "latent_dist"):
|
||||
# A restoration donor must be deterministic. Diffusers samples this
|
||||
# distribution for img2img noise initialization; its mode is the
|
||||
# faithful VAE reconstruction needed here.
|
||||
latents = encoded.latent_dist.mode()
|
||||
elif hasattr(encoded, "latents"):
|
||||
latents = encoded.latents
|
||||
else:
|
||||
raise AttributeError("Chroma VAE encoder output contains no latents")
|
||||
decoded = pipe.vae.decode(latents, return_dict=False)[0]
|
||||
reconstructed = pipe.image_processor.postprocess(decoded, output_type="pil")[0]
|
||||
return reconstructed.crop((0, 0, source_width, source_height)).convert("RGB")
|
||||
return diffusers_vae_roundtrip(
|
||||
pipe,
|
||||
image,
|
||||
grid=_LATENT_GRID,
|
||||
device=self.device,
|
||||
dtype=self.torch_dtype,
|
||||
profile_name=self.profile_name,
|
||||
)
|
||||
@@ -28,6 +28,7 @@ from remove_ai_watermarks._internal.two_stage_pipeline import (
|
||||
TwoStageZImagePipeline,
|
||||
_target_size,
|
||||
build_canny_control_image,
|
||||
diffusers_vae_roundtrip,
|
||||
requested_steps,
|
||||
)
|
||||
from remove_ai_watermarks._internal.watermark_profiles import (
|
||||
@@ -120,3 +121,15 @@ class SdxlZImagePipeline(TwoStageZImagePipeline):
|
||||
if result.size != image.size:
|
||||
result = result.resize(image.size, Image.Resampling.LANCZOS)
|
||||
return result.convert("RGB")
|
||||
|
||||
def _vae_roundtrip(self, image: Image.Image) -> Image.Image:
|
||||
"""Reconstruct source pixels through the already loaded SDXL VAE."""
|
||||
pipe = self._load_global()
|
||||
return diffusers_vae_roundtrip(
|
||||
pipe,
|
||||
image,
|
||||
grid=_LATENT_GRID,
|
||||
device=self.device,
|
||||
dtype=self.torch_dtype,
|
||||
profile_name=self.profile_name,
|
||||
)
|
||||
@@ -96,6 +96,41 @@ def edge_pad_to_grid(image: Image.Image, grid: int) -> Image.Image:
|
||||
)
|
||||
|
||||
|
||||
def diffusers_vae_roundtrip(
|
||||
pipe: Any,
|
||||
image: Image.Image,
|
||||
*,
|
||||
grid: int,
|
||||
device: Any,
|
||||
dtype: Any,
|
||||
profile_name: str,
|
||||
) -> Image.Image:
|
||||
"""Reconstruct pixels deterministically through a Diffusers pipeline VAE."""
|
||||
import torch
|
||||
|
||||
source_width, source_height = image.size
|
||||
padded = edge_pad_to_grid(image, grid)
|
||||
tensor = pipe.image_processor.preprocess(
|
||||
padded,
|
||||
height=padded.height,
|
||||
width=padded.width,
|
||||
).to(device=device, dtype=dtype)
|
||||
with torch.inference_mode():
|
||||
encoded = pipe.vae.encode(tensor)
|
||||
if hasattr(encoded, "latent_dist"):
|
||||
# A restoration donor must be deterministic. Diffusers samples this
|
||||
# distribution for img2img noise initialization; its mode is the
|
||||
# faithful VAE reconstruction needed here.
|
||||
latents = encoded.latent_dist.mode()
|
||||
elif hasattr(encoded, "latents"):
|
||||
latents = encoded.latents
|
||||
else:
|
||||
raise AttributeError(f"{profile_name} VAE encoder output contains no latents")
|
||||
decoded = pipe.vae.decode(latents, return_dict=False)[0]
|
||||
reconstructed = pipe.image_processor.postprocess(decoded, output_type="pil")[0]
|
||||
return reconstructed.crop((0, 0, source_width, source_height)).convert("RGB")
|
||||
|
||||
|
||||
def resolve_face_model_residency(
|
||||
requested: bool | None,
|
||||
*,
|
||||
|
||||
@@ -250,8 +250,6 @@ class WatermarkRemover:
|
||||
self.model_profile = resolved
|
||||
self.torch_dtype = _dtype_for_profile(resolved)
|
||||
self._qwen_zimage_pipeline = None
|
||||
if text_manifest is not None and self.model_profile == SDXL_ZIMAGE_PROFILE:
|
||||
raise ValueError("Verified text restoration is not supported by the sdxl-zimage profile")
|
||||
if text_manifest is not None and tile:
|
||||
raise ValueError("Verified text restoration is not calibrated with tiled diffusion")
|
||||
if fidelity_anchor and self.model_profile != QWEN_ZIMAGE_PROFILE:
|
||||
|
||||
@@ -318,9 +318,8 @@ _text_manifest_option = click.option(
|
||||
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
||||
default=None,
|
||||
help=(
|
||||
"Experimental verified-text restoration manifest. Requires qwen-zimage or "
|
||||
"chroma-zimage, the text-restoration extra, native untiled geometry, and no "
|
||||
"postprocessing."
|
||||
"Experimental verified-text restoration manifest. Requires the text-restoration "
|
||||
"extra, native untiled geometry, and no postprocessing."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ from typing import TYPE_CHECKING, Any
|
||||
from ._internal.watermark_profiles import (
|
||||
DEFAULT_PROFILE,
|
||||
REMOVAL_MODULES,
|
||||
SDXL_ZIMAGE_PROFILE,
|
||||
resolve_adaptive_polish,
|
||||
resolve_effective_profile,
|
||||
resolve_seed,
|
||||
@@ -192,9 +191,9 @@ class InvisibleEngine:
|
||||
def preload(self, *, global_only: bool = False) -> None:
|
||||
"""Eagerly load the pipeline so download progress is visible.
|
||||
|
||||
For ``qwen-zimage``, ``global_only=True`` loads the mandatory Qwen stage
|
||||
and leaves the optional Z-Image and SAM face stack lazy until a face is
|
||||
detected. Other profiles have no optional stage and ignore the flag.
|
||||
For every concrete profile, ``global_only=True`` loads its mandatory
|
||||
global stage and leaves the shared Z-Image and SAM face stack lazy until
|
||||
a face is detected.
|
||||
"""
|
||||
self._remover.preload(global_only=global_only)
|
||||
|
||||
@@ -269,13 +268,18 @@ class InvisibleEngine:
|
||||
|
||||
seed = resolve_seed(seed)
|
||||
effective_profile = resolve_effective_profile(self._remover.configured_profile, vendor)
|
||||
adaptive_polish = resolve_adaptive_polish(adaptive_polish, effective_profile)
|
||||
# Verified text must be the final pixel-writing stage. SDXL normally enables
|
||||
# adaptive polish by profile default, so an unset value becomes False here;
|
||||
# an explicit True remains an error below instead of being silently ignored.
|
||||
adaptive_polish = (
|
||||
False
|
||||
if text_manifest is not None and adaptive_polish is None
|
||||
else resolve_adaptive_polish(adaptive_polish, effective_profile)
|
||||
)
|
||||
|
||||
if fidelity_anchor and text_manifest is None:
|
||||
raise ValueError("fidelity_anchor requires a text manifest")
|
||||
if text_manifest is not None:
|
||||
if effective_profile == SDXL_ZIMAGE_PROFILE:
|
||||
raise ValueError("--text-manifest is not supported by the sdxl-zimage profile")
|
||||
if tile:
|
||||
raise ValueError("--text-manifest is not calibrated with --tile")
|
||||
if max_resolution != 0:
|
||||
|
||||
@@ -64,7 +64,6 @@ class TestVerifiedTextMode:
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text("{}", encoding="utf-8")
|
||||
cases = (
|
||||
("sdxl-zimage", {}, "not supported by the sdxl-zimage profile"),
|
||||
("qwen-zimage", {"tile": True}, "not calibrated with --tile"),
|
||||
("qwen-zimage", {"max_resolution": 1024}, "max-resolution 0"),
|
||||
("qwen-zimage", {"humanize": 1.0}, "humanize=0"),
|
||||
@@ -87,19 +86,6 @@ class TestVerifiedTextMode:
|
||||
fidelity_anchor=True,
|
||||
)
|
||||
|
||||
def test_auto_rejects_google_text_manifest_before_model_loading(self, tmp_path):
|
||||
import pytest
|
||||
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text("{}", encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="not supported by the sdxl-zimage profile"):
|
||||
self._engine("auto").remove_watermark(
|
||||
tmp_path / "unused.png",
|
||||
text_manifest=manifest,
|
||||
vendor="google",
|
||||
)
|
||||
|
||||
def test_loads_and_forwards_verified_manifest(self, tmp_path, monkeypatch):
|
||||
import json
|
||||
|
||||
@@ -137,9 +123,10 @@ class TestVerifiedTextMode:
|
||||
engine._remover.remove_watermark = fake_remove
|
||||
monkeypatch.setattr(region_eraser, "lama_available", lambda: True)
|
||||
|
||||
engine.remove_watermark(source, output, text_manifest=manifest)
|
||||
engine.remove_watermark(source, output, text_manifest=manifest, vendor="google")
|
||||
|
||||
assert seen["text_manifest"].lines[0].text == "Exact"
|
||||
assert seen["vendor"] == "google"
|
||||
# Leak-safe default since 0.27.1: the global 15% donor blend is OFF unless
|
||||
# explicitly requested (measured to return detector-visible OpenAI SynthID
|
||||
# on poster-scale manifests; see docs/text-protection-research.md).
|
||||
|
||||
@@ -996,6 +996,70 @@ def test_sdxl_zimage_inherits_the_face_stage_rather_than_copying_it():
|
||||
assert SdxlZImagePipeline._load_global is not QwenZImagePipeline._load_global
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pipeline_module", "pipeline_class", "grid"),
|
||||
[
|
||||
("sdxl_zimage_pipeline", "SdxlZImagePipeline", 8),
|
||||
("chroma_zimage_pipeline", "ChromaZImagePipeline", 16),
|
||||
],
|
||||
)
|
||||
def test_diffusers_vae_roundtrip_uses_deterministic_mode_and_restores_native_size(
|
||||
monkeypatch, pipeline_module, pipeline_class, grid
|
||||
):
|
||||
"""Diffusers donors pad without distorting geometry and never sample."""
|
||||
import importlib
|
||||
|
||||
import torch
|
||||
|
||||
seen: dict[str, object] = {}
|
||||
padded_width = 40 if grid == 8 else 48
|
||||
padded_height = 24 if grid == 8 else 32
|
||||
latent = torch.ones(1, 4, padded_height // 8, padded_width // 8)
|
||||
|
||||
class LatentDistribution:
|
||||
def mode(self):
|
||||
seen["mode_calls"] = int(seen.get("mode_calls", 0)) + 1
|
||||
return latent
|
||||
|
||||
def sample(self, *_args, **_kwargs):
|
||||
raise AssertionError("A verified-text donor must not sample VAE latents")
|
||||
|
||||
class FakeVae:
|
||||
@staticmethod
|
||||
def encode(tensor):
|
||||
seen["encoded_shape"] = tuple(tensor.shape)
|
||||
return type("Encoded", (), {"latent_dist": LatentDistribution()})()
|
||||
|
||||
@staticmethod
|
||||
def decode(received, *, return_dict):
|
||||
assert received is latent
|
||||
assert return_dict is False
|
||||
return (torch.zeros(1, 3, padded_height, padded_width),)
|
||||
|
||||
class FakeProcessor:
|
||||
@staticmethod
|
||||
def preprocess(image, *, height, width):
|
||||
seen["preprocess"] = (image.size, height, width)
|
||||
return torch.zeros(1, 3, height, width)
|
||||
|
||||
@staticmethod
|
||||
def postprocess(decoded, *, output_type):
|
||||
assert output_type == "pil"
|
||||
return [Image.new("RGB", (decoded.shape[3], decoded.shape[2]))]
|
||||
|
||||
pipe = type("FakePipe", (), {"vae": FakeVae(), "image_processor": FakeProcessor()})()
|
||||
module = importlib.import_module(f"remove_ai_watermarks._internal.{pipeline_module}")
|
||||
pipeline = getattr(module, pipeline_class)(device="cpu", torch_dtype=torch.float32)
|
||||
monkeypatch.setattr(pipeline, "_load_global", lambda: pipe)
|
||||
|
||||
result = pipeline._vae_roundtrip(Image.new("RGB", (35, 19)))
|
||||
|
||||
assert seen["preprocess"] == ((padded_width, padded_height), padded_height, padded_width)
|
||||
assert seen["encoded_shape"] == (1, 3, padded_height, padded_width)
|
||||
assert seen["mode_calls"] == 1
|
||||
assert result.size == (35, 19)
|
||||
|
||||
|
||||
def test_chroma_zimage_strength_uses_measured_flat_floors():
|
||||
"""The chroma floors come from the four-cohort oracle calibration; they must not
|
||||
drift from their derivation in watermark_profiles."""
|
||||
@@ -1163,8 +1227,9 @@ def test_chroma_zimage_floors_to_its_own_latent_grid():
|
||||
assert chroma_target_size(3, 3) == (16, 16)
|
||||
|
||||
|
||||
def test_watermark_remover_accepts_text_manifest_for_chroma_but_not_sdxl(tmp_path, monkeypatch):
|
||||
"""Chroma owns a VAE donor; SDXL still fails at the public boundary."""
|
||||
@pytest.mark.parametrize("profile", ["chroma-zimage", "sdxl-zimage"])
|
||||
def test_watermark_remover_accepts_text_manifest_for_every_diffusers_profile(tmp_path, monkeypatch, profile):
|
||||
"""Both Diffusers profiles forward reviewed text to their VAE donor."""
|
||||
from remove_ai_watermarks._internal.text_restoration import VerifiedTextLine, VerifiedTextManifest
|
||||
from remove_ai_watermarks._internal.watermark_remover import WatermarkRemover
|
||||
|
||||
@@ -1173,22 +1238,18 @@ def test_watermark_remover_accepts_text_manifest_for_chroma_but_not_sdxl(tmp_pat
|
||||
Image.new("RGB", (96, 80)).save(source)
|
||||
manifest = VerifiedTextManifest("0" * 64, 96, 80, (VerifiedTextLine((4, 4, 20, 16), "x", "alphabetic"),))
|
||||
|
||||
chroma = WatermarkRemover(device="cuda", pipeline="chroma-zimage")
|
||||
chroma_runtime = MagicMock()
|
||||
chroma_runtime.run.return_value = Image.new("RGB", (96, 80))
|
||||
monkeypatch.setattr(chroma, "_load_qwen_zimage_pipeline", lambda: chroma_runtime)
|
||||
remover = WatermarkRemover(device="cuda", pipeline=profile)
|
||||
runtime = MagicMock()
|
||||
runtime.run.return_value = Image.new("RGB", (96, 80))
|
||||
monkeypatch.setattr(remover, "_load_qwen_zimage_pipeline", lambda: runtime)
|
||||
|
||||
chroma.remove_watermark(source, text_manifest=manifest)
|
||||
remover.remove_watermark(source, text_manifest=manifest)
|
||||
|
||||
assert chroma_runtime.run.call_count == 1
|
||||
assert chroma_runtime.run.call_args.kwargs["text_manifest"] is manifest
|
||||
assert runtime.run.call_count == 1
|
||||
assert runtime.run.call_args.kwargs["text_manifest"] is manifest
|
||||
with pytest.raises(ValueError, match=r"fidelity anchor.*qwen-zimage"):
|
||||
chroma.remove_watermark(source, text_manifest=manifest, fidelity_anchor=True)
|
||||
assert chroma_runtime.run.call_count == 1
|
||||
|
||||
sdxl = WatermarkRemover(device="cuda", pipeline="sdxl-zimage")
|
||||
with pytest.raises(ValueError, match="not supported by the sdxl-zimage profile"):
|
||||
sdxl.remove_watermark(source, text_manifest=manifest)
|
||||
remover.remove_watermark(source, text_manifest=manifest, fidelity_anchor=True)
|
||||
assert runtime.run.call_count == 1
|
||||
|
||||
|
||||
def test_watermark_remover_dispatches_to_chroma_pipeline(monkeypatch):
|
||||
@@ -1229,58 +1290,6 @@ def test_chroma_adaptive_polish_defaults_off():
|
||||
assert resolve_adaptive_polish(True, "chroma-zimage") is True
|
||||
|
||||
|
||||
def test_chroma_vae_roundtrip_uses_deterministic_mode_and_restores_native_size(monkeypatch):
|
||||
"""The Chroma donor pads without distorting geometry and never samples."""
|
||||
import torch
|
||||
|
||||
from remove_ai_watermarks._internal.chroma_zimage_pipeline import ChromaZImagePipeline
|
||||
|
||||
seen: dict[str, object] = {}
|
||||
latent = torch.ones(1, 4, 4, 6)
|
||||
|
||||
class LatentDistribution:
|
||||
def mode(self):
|
||||
seen["mode_calls"] = int(seen.get("mode_calls", 0)) + 1
|
||||
return latent
|
||||
|
||||
def sample(self, *_args, **_kwargs):
|
||||
raise AssertionError("A verified-text donor must not sample VAE latents")
|
||||
|
||||
class FakeVae:
|
||||
@staticmethod
|
||||
def encode(tensor):
|
||||
seen["encoded_shape"] = tuple(tensor.shape)
|
||||
return type("Encoded", (), {"latent_dist": LatentDistribution()})()
|
||||
|
||||
@staticmethod
|
||||
def decode(received, *, return_dict):
|
||||
assert received is latent
|
||||
assert return_dict is False
|
||||
return (torch.zeros(1, 3, 32, 48),)
|
||||
|
||||
class FakeProcessor:
|
||||
@staticmethod
|
||||
def preprocess(image, *, height, width):
|
||||
seen["preprocess"] = (image.size, height, width)
|
||||
return torch.zeros(1, 3, height, width)
|
||||
|
||||
@staticmethod
|
||||
def postprocess(decoded, *, output_type):
|
||||
assert output_type == "pil"
|
||||
return [Image.new("RGB", (decoded.shape[3], decoded.shape[2]))]
|
||||
|
||||
pipe = type("FakePipe", (), {"vae": FakeVae(), "image_processor": FakeProcessor()})()
|
||||
pipeline = ChromaZImagePipeline(device="cpu", torch_dtype=torch.float32)
|
||||
monkeypatch.setattr(pipeline, "_load_global", lambda: pipe)
|
||||
|
||||
result = pipeline._vae_roundtrip(Image.new("RGB", (35, 19)))
|
||||
|
||||
assert seen["preprocess"] == ((48, 32), 32, 48)
|
||||
assert seen["encoded_shape"] == (1, 3, 32, 48)
|
||||
assert seen["mode_calls"] == 1
|
||||
assert result.size == (35, 19)
|
||||
|
||||
|
||||
def test_chroma_google_uses_one_content_agnostic_floor():
|
||||
"""Face detection alone cannot safely select a lower Google floor.
|
||||
|
||||
|
||||
@@ -800,7 +800,7 @@ name = "cuda-bindings"
|
||||
version = "13.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "cuda-pathfinder" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" },
|
||||
@@ -833,43 +833,43 @@ wheels = [
|
||||
|
||||
[package.optional-dependencies]
|
||||
cublas = [
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cudart = [
|
||||
{ name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cufft = [
|
||||
{ name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cufile = [
|
||||
{ name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cupti = [
|
||||
{ name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
curand = [
|
||||
{ name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cusolver = [
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
cusparse = [
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
nvjitlink = [
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
nvrtc = [
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
nvtx = [
|
||||
{ name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1066,7 +1066,7 @@ wheels = [
|
||||
|
||||
[package.optional-dependencies]
|
||||
http = [
|
||||
{ name = "aiohttp", marker = "python_full_version < '3.13'" },
|
||||
{ name = "aiohttp" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1198,8 +1198,8 @@ name = "httpcore2"
|
||||
version = "2.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "h11" },
|
||||
{ name = "truststore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" }
|
||||
wheels = [
|
||||
@@ -1461,15 +1461,15 @@ name = "lightning"
|
||||
version = "2.6.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" },
|
||||
{ name = "lightning-utilities", marker = "python_full_version < '3.13'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.13'" },
|
||||
{ name = "pytorch-lightning", marker = "python_full_version < '3.13'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torch", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torchmetrics", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tqdm", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "fsspec", extra = ["http"] },
|
||||
{ name = "lightning-utilities" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pytorch-lightning" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "torch" },
|
||||
{ name = "torchmetrics" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/39/b8/fbf1469d917c3ed3b22e00ab3a852fb9a5f85b9dc7f7060eedc3449098e7/lightning-2.6.6.tar.gz", hash = "sha256:1c6329e241f8a85f29fedead89c1ee138b36caa08de60c260c6a7eaee81c8295", size = 659902, upload-time = "2026-09-10T09:41:03.009Z" }
|
||||
wheels = [
|
||||
@@ -1481,8 +1481,8 @@ name = "lightning-utilities"
|
||||
version = "0.15.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "packaging" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" }
|
||||
wheels = [
|
||||
@@ -1896,7 +1896,7 @@ name = "nvidia-cublas"
|
||||
version = "13.1.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-cuda-nvrtc" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
|
||||
@@ -1935,7 +1935,7 @@ name = "nvidia-cudnn-cu13"
|
||||
version = "9.24.0.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-cublas" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863, upload-time = "2026-07-02T16:23:39.248Z" },
|
||||
@@ -1947,7 +1947,7 @@ name = "nvidia-cufft"
|
||||
version = "12.0.0.61"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
|
||||
@@ -1977,9 +1977,9 @@ name = "nvidia-cusolver"
|
||||
version = "12.0.4.66"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-cublas" },
|
||||
{ name = "nvidia-cusparse" },
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
|
||||
@@ -1991,7 +1991,7 @@ name = "nvidia-cusparse"
|
||||
version = "12.6.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
|
||||
@@ -2146,7 +2146,7 @@ resolution-markers = [
|
||||
"(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" }
|
||||
wheels = [
|
||||
@@ -2175,7 +2175,7 @@ resolution-markers = [
|
||||
"(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
|
||||
{ name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/e8/c2959c0f969ea4ae96b55ad10c108c0410de4e8b3550d9941a4cfdd52703/opencv_python-4.14.0.94.tar.gz", hash = "sha256:afadc76a38a07713a4c2b4702da917cf0ed489be57e2a40a34e670189731de7d", size = 96400225, upload-time = "2026-07-28T19:23:09.546Z" }
|
||||
wheels = [
|
||||
@@ -2206,7 +2206,7 @@ resolution-markers = [
|
||||
"(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" }
|
||||
wheels = [
|
||||
@@ -2235,7 +2235,7 @@ resolution-markers = [
|
||||
"(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
|
||||
{ name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/8a/51ff1c6287be0b6cd3070cf095875da4ff7d6eda38507e6ada5608786a78/opencv_python_headless-4.14.0.94.tar.gz", hash = "sha256:4afa2ea1214453648be88259f035712454faa9039b686de7753569ba8eec1577", size = 96405664, upload-time = "2026-07-28T19:23:48.758Z" }
|
||||
wheels = [
|
||||
@@ -3179,14 +3179,14 @@ name = "pytorch-lightning"
|
||||
version = "2.6.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "fsspec", extra = ["http"], marker = "python_full_version < '3.13'" },
|
||||
{ name = "lightning-utilities", marker = "python_full_version < '3.13'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.13'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torch", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torchmetrics", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tqdm", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "fsspec", extra = ["http"] },
|
||||
{ name = "lightning-utilities" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "torch" },
|
||||
{ name = "torchmetrics" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/97/e4/5025cb42311b4f8cc6d1b12cd81399ca433159f8e3831ab229c5a6c5177e/pytorch_lightning-2.6.6.tar.gz", hash = "sha256:52c52b20522dd3d3feb3bb3aae2dbe3f291e8e836d4111a74cfd72e9df018834", size = 662507, upload-time = "2026-09-10T09:41:16.684Z" }
|
||||
wheels = [
|
||||
@@ -3205,7 +3205,7 @@ resolution-markers = [
|
||||
"(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" }
|
||||
wheels = [
|
||||
@@ -3281,7 +3281,7 @@ resolution-markers = [
|
||||
"(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.12.*'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/84/a9e80e33eb114e78bd2f1829a90d72d8d4d660109c721a8aa2f0d58d7592/pywavelets-1.10.0.tar.gz", hash = "sha256:f3cb8640225a8f3fdcbc3f6f3bd112c78927c9407cbfa65a5ff303990a6a3e32", size = 3948163, upload-time = "2026-09-07T17:15:15.637Z" }
|
||||
@@ -3449,7 +3449,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "remove-ai-watermarks"
|
||||
version = "0.40.4"
|
||||
version = "0.40.5"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "c2pa-python" },
|
||||
@@ -4067,10 +4067,10 @@ name = "torchmetrics"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lightning-utilities", marker = "python_full_version < '3.13'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torch", marker = "python_full_version < '3.13'" },
|
||||
{ name = "lightning-utilities" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "packaging" },
|
||||
{ name = "torch" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" }
|
||||
wheels = [
|
||||
@@ -4165,13 +4165,13 @@ name = "trustmark"
|
||||
version = "0.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "einops", marker = "python_full_version < '3.13'" },
|
||||
{ name = "lightning", marker = "python_full_version < '3.13'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "omegaconf", marker = "python_full_version < '3.13'" },
|
||||
{ name = "six", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torch", marker = "python_full_version < '3.13'" },
|
||||
{ name = "torchvision", marker = "python_full_version < '3.13'" },
|
||||
{ name = "einops" },
|
||||
{ name = "lightning" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "omegaconf" },
|
||||
{ name = "six" },
|
||||
{ name = "torch" },
|
||||
{ name = "torchvision" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/41/32e84596af94e8696c55060d55eb9eed9d99722a673d12212a1c0c4ef1d2/trustmark-0.9.2.tar.gz", hash = "sha256:bcaa3bc38ead1c0558df81932672f34e306efaed31389da57dbed100e236b28a", size = 63780, upload-time = "2026-09-08T10:58:11.601Z" }
|
||||
|
||||
|
||||
Reference in new issue
Block a user