diff --git a/docs/controlnet-removal-pipeline-research.md b/docs/controlnet-removal-pipeline-research.md index d750310..f5f10ed 100644 --- a/docs/controlnet-removal-pipeline-research.md +++ b/docs/controlnet-removal-pipeline-research.md @@ -521,7 +521,7 @@ held by its masked ArcFace vector -- no original pixel copied. drift, NOT a perfect face swap. Set expectations; PuLID/InstantID are the higher-fidelity (heavier) paths if needed. - **Value scales with strength:** at low strength (OpenAI 0.10) faces barely drift, so FaceID is - marginal; at the higher strength a hard vendor (Google 0.30) needs, FaceID earns its keep. + marginal; at the higher strength then used for a hard vendor (Google 0.30), FaceID earned its keep. ### Build plan (staged) diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 934bde7..05288a9 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -177,13 +177,14 @@ path has not yet answered. The two profiles resolve an unset strength differently, because different things were measured for each. -`qwen-zimage` reads it from image area, through the resolution-adaptive denoise -curve. The vendor is deliberately ignored for openai/unknown content: the curve, -not the issuer, is what was calibrated. One measured exception (0.27.2): -Google-provenance content takes the flat `QWEN_ZIMAGE_GOOGLE_STRENGTH` 0.30 floor -- the curve's 0.154 top left a 4.33 MP fixture oracle-detected on the full -production path (2026-08-18), while 0.30 anchors measured clean on two fixtures -in two accounts. +`qwen-zimage` reads unknown content from the resolution-adaptive denoise curve. +Measured provider cohorts instead take flat operating points: OpenAI `0.07675`, +Google `0.27`, and Microsoft InvisMark `0.15`. OpenAI and Microsoft add one full +observed cross-source boundary spread to the worst clean source; Microsoft's three +first-clean boundaries were `0.04125`, `0.055`, and `0.095`, giving `0.14875` before +rounding up. Google's candidate was separately repeated across three valid sources +and three accounts. The small corpora make these operating points, not universal +thresholds. `sdxl-zimage` reads it from the C2PA issuer, on a flat ladder: diff --git a/docs/module-internals.md b/docs/module-internals.md index bd1f87a..9049b48 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -876,6 +876,11 @@ are CUDA-only. `controlnet`, `sdxl`, `qwen` and `default` were removed rather th kept as a CPU path, and are rejected rather than aliased onward. There is no content-dependent automatic router. +`qwen-zimage` normally resolves global denoise from image area for unknown content. +Measured provider cohorts bypass that curve with flat operating points. The values, +measurement derivations, and corpus limits are canonical in +[`known-limitations.md`](known-limitations.md#strength-is-content-and-seed-dependent). + For serverless cold starts, `InvisibleEngine.preload(global_only=True)` loads the mandatory global stage and YuNet while leaving the optional Z-Image and SAM face stack lazy until a face is detected. The default `preload()` still loads every diff --git a/docs/synthid.md b/docs/synthid.md index 2935af2..a20d385 100644 --- a/docs/synthid.md +++ b/docs/synthid.md @@ -537,8 +537,8 @@ study (section 2.2) gives empirical floors: > were removed, and `OPENAI_STRENGTH` / `GEMINI_STRENGTH` / `UNKNOWN_STRENGTH` went > with them. Everything from here to the end of this section is a record of what was > measured on those profiles, kept because the oracle verdicts are still the evidence -> base. For the strength policy in force now see `module-internals.md`: `qwen-zimage` -> uses `resolution_adaptive_denoise`, `sdxl-zimage` a flat vendor ladder. +> base. For the strength policy in force now see +> [`known-limitations.md`](known-limitations.md#strength-is-content-and-seed-dependent). The default was **vendor-adaptive** (`watermark_profiles.resolve_strength` + `vendor_for_strength`): the tool read the C2PA issuer on the original input and picked @@ -691,9 +691,9 @@ solid at 0.10 but at 0.15 it is near-threshold noise; (2) for reliable removal p a strength with MARGIN above the borderline (controlnet >= 0.20), not exactly on it; (3) **historical engineering conclusion:** this dated run argued for a higher ControlNet strength than the then-current default. That proposal was -later superseded. The current resolver intentionally shares the 0.10/0.15 -ladder between SDXL and ControlNet and uses a separate Qwen ladder; see -`_internal/watermark_profiles.py`. +later superseded. The resolver of that period shared the 0.10/0.15 ladder between +SDXL and ControlNet; the current policies are recorded in +[`known-limitations.md`](known-limitations.md#strength-is-content-and-seed-dependent). Source images are private (faces / product shots), not committed; reproduce on any photoreal + flat-graphic gpt-image pair, varying the seed, and re-checking the oracle. @@ -770,14 +770,14 @@ reproducible verification requires a fixed seed. Watermarking.** arXiv:2505.21620. https://arxiv.org/abs/2505.21620 -**Google floor on qwen-zimage (0.27.2, 2026-08-19).** The bracket above did not +**Google floor on qwen-zimage (0.27.2, remeasured 2026-08-25).** The bracket above did not survive the full production path: on the 4.33 MP CJK-sign fixture (visible-stage sparkle removal -> qwen-zimage seed 0 at the curve's 0.154 top -> resize-back -> metadata strip), the Gemini verifier detected SynthID x3 with a valid pixel-identical stripped control in the same session. Google-provenance content -now takes a flat `QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.30` floor instead of the area -curve - 0.30 anchors measured clean in Gemini on two fixtures (CJK sign + -18-face) at 3/3 checks across two work accounts, and stayed clean with the -vae-glyphs donor layer on top; openai/unknown content keeps the resolution -curve. An explicit strength still wins. Certification artifacts: raiw-app +initially took a flat `0.30` floor instead of the area curve. The later provider +remeasurement established a common pass at `0.25` across three valid sources and +separately repeated `QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.27` across those sources and +three accounts. OpenAI now has its own measured flat operating point; unknown +content keeps the resolution curve. An explicit strength still wins. Certification artifacts: raiw-app `data/certification/text-restoration-2026-08-18/` (the failing re-baseline). diff --git a/docs/text-protection-research.md b/docs/text-protection-research.md index 84775de..a8ba9dd 100644 --- a/docs/text-protection-research.md +++ b/docs/text-protection-research.md @@ -20,8 +20,8 @@ filter applied to every candidate below. ## Problem recap The `invisible` pipeline is SDXL base 1.0 img2img to defeat SynthID. The default -strength has risen over time as Google hardens SynthID (0.05 -> 0.10 -> **~0.30**, the -current threshold for fresh Gemini output); higher strength deforms text more, which is +strength rose over time as Google hardened SynthID (0.05 -> 0.10 -> **~0.30** in +this experiment; the later measured operating point is 0.27); higher strength deforms text more, which is exactly why text protection matters. Text is protected via Differential Diffusion with a per-pixel change map (`preserve` ~0.9) driven by the PP-OCRv3 DB detector (`text_protector.py`). Large text survives; **small text (sub ~8 px strokes) softens or diff --git a/pyproject.toml b/pyproject.toml index 31196fe..d3a4ef8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.31.0" +version = "0.31.1" description = "AI watermark remover for visible, invisible, and provenance marks in images and video" readme = "README.md" requires-python = ">=3.11,<3.15" diff --git a/src/remove_ai_watermarks/__init__.py b/src/remove_ai_watermarks/__init__.py index 069572a..7c01e5d 100644 --- a/src/remove_ai_watermarks/__init__.py +++ b/src/remove_ai_watermarks/__init__.py @@ -32,7 +32,7 @@ _os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error") _warnings.filterwarnings("ignore", message=r".*ImageProcessorFast.*") -__version__ = "0.31.0" +__version__ = "0.31.1" __all__ = [ "BatchSummary", diff --git a/src/remove_ai_watermarks/_internal/watermark_profiles.py b/src/remove_ai_watermarks/_internal/watermark_profiles.py index 1e7468e..1f0ddb9 100644 --- a/src/remove_ai_watermarks/_internal/watermark_profiles.py +++ b/src/remove_ai_watermarks/_internal/watermark_profiles.py @@ -69,17 +69,33 @@ SDXL_ZIMAGE_OPENAI_STRENGTH = 0.15 SDXL_ZIMAGE_GEMINI_STRENGTH = 0.25 SDXL_ZIMAGE_UNKNOWN_STRENGTH = SDXL_ZIMAGE_GEMINI_STRENGTH -# qwen-zimage keeps its resolution curve for openai/unknown content, but Google -# content takes this flat oracle-measured floor instead of the curve (whose top, -# 0.154, left a 4.33 MP CJK-sign fixture detected x3 on the full production path -# with a valid pixel control, 2026-08-18). 0.30 anchors measured clean in Gemini -# across two fixtures and two work accounts (CJK sign + 18-face, 3/3 checks each, -# 2026-08 research) and stayed clean under the vae-glyphs donor layer on top. -QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.30 +# qwen-zimage keeps its resolution curve for unknown content, but measured vendor +# cohorts bypass it. Google remained detectable through 0.24375; 0.25 cleared all +# three valid sources and 0.27 was separately repeated clean across them and three +# accounts, so the independently checked 0.27 candidate is the operating floor. +QWEN_ZIMAGE_GOOGLE_STRENGTH = 0.27 + +# The two OpenAI sources first cleared at 0.06225 and 0.0695. Add one full observed +# cross-source spread (0.00725) to the worst clean boundary: 0.0695 + 0.00725. +QWEN_ZIMAGE_OPENAI_STRENGTH = 0.07675 + +# Microsoft's public detector returned Inconclusive rather than an API-level +# watermark-negative verdict. Three valid Paint sources first cleared at 0.04125, +# 0.055, and 0.095. Add one full observed cross-source spread to the worst clean +# boundary: 0.095 + (0.095 - 0.04125) = 0.14875, rounded up to 0.15. This is a +# measured corpus margin, not a universal InvisMark threshold. +QWEN_ZIMAGE_MICROSOFT_STRENGTH = 0.15 + +_QWEN_ZIMAGE_FLAT_STRENGTH_BY_VENDOR: dict[str, float] = { + "google": QWEN_ZIMAGE_GOOGLE_STRENGTH, + "openai": QWEN_ZIMAGE_OPENAI_STRENGTH, + "microsoft": QWEN_ZIMAGE_MICROSOFT_STRENGTH, +} -# sdxl-zimage picks its strength from the VENDOR (unlike qwen-zimage, which derives it -# from image area). An unlisted or unknown vendor falls back to the Gemini value. +# sdxl-zimage always picks its strength from the vendor. qwen-zimage instead uses +# the vendor only for measured cohorts and image area for unknown content. An +# unlisted or unknown SDXL vendor falls back to the Gemini value. _SDXL_ZIMAGE_STRENGTH_BY_VENDOR: dict[str, float] = { "openai": SDXL_ZIMAGE_OPENAI_STRENGTH, "google": SDXL_ZIMAGE_GEMINI_STRENGTH, @@ -112,7 +128,8 @@ def strength_default_help() -> str: """Describe the live default policy without duplicating its values.""" return ( "profile-adaptive (qwen-zimage uses resolution-adaptive denoise, with a " - f"flat oracle-measured Google floor of {QWEN_ZIMAGE_GOOGLE_STRENGTH}; sdxl-zimage " + f"flat OpenAI {QWEN_ZIMAGE_OPENAI_STRENGTH} / Google {QWEN_ZIMAGE_GOOGLE_STRENGTH} / " + f"Microsoft InvisMark {QWEN_ZIMAGE_MICROSOFT_STRENGTH} floors; sdxl-zimage " f"uses OpenAI {SDXL_ZIMAGE_OPENAI_STRENGTH} / Google {SDXL_ZIMAGE_GEMINI_STRENGTH} / " f"unknown {SDXL_ZIMAGE_UNKNOWN_STRENGTH}, from the C2PA issuer)" ) @@ -131,10 +148,10 @@ def resolve_strength( from the vendor, so it needs ``size``; returning ``None`` for it instead would push that branch onto every caller and move one of the two strength policies outside this module. ``size`` is required for qwen-zimage without an explicit strength. - One measured exception since 0.27.2: Google-provenance content on qwen-zimage - takes the flat ``QWEN_ZIMAGE_GOOGLE_STRENGTH`` floor instead of the area curve - (the curve's 0.154 top left a 4.33 MP fixture oracle-detected on the full - production path; see the constant's comment). + Measured vendor exceptions bypass the area curve: OpenAI and Microsoft use one + additional observed cross-source spread over their worst clean boundaries, while + Google uses a separately repeated cross-source candidate. See the constants' + comments for the exact derivations. """ if strength is not None: return strength @@ -142,23 +159,32 @@ def resolve_strength( return _SDXL_ZIMAGE_STRENGTH_BY_VENDOR.get((vendor or "").casefold(), SDXL_ZIMAGE_UNKNOWN_STRENGTH) if size is None: raise ValueError("qwen-zimage resolves strength from image area, so size is required") - if (vendor or "").casefold() == "google": - return QWEN_ZIMAGE_GOOGLE_STRENGTH + vendor_strength = _QWEN_ZIMAGE_FLAT_STRENGTH_BY_VENDOR.get((vendor or "").casefold()) + if vendor_strength is not None: + return vendor_strength from remove_ai_watermarks._internal.qwen_zimage_pipeline import resolution_adaptive_denoise return resolution_adaptive_denoise(*size) -def vendor_for_strength(image_path: Path) -> Literal["openai", "google"] | None: - """Select the strength cohort using the input's SynthID provenance evidence.""" +def vendor_for_strength(image_path: Path) -> Literal["openai", "google", "microsoft"] | None: + """Select the strength cohort from non-invalid pixel-watermark provenance.""" try: + from remove_ai_watermarks._internal.c2pa import ( + c2pa_info_has_invalid_credential, + c2pa_info_has_invismark, + extract_c2pa_info, + ) from remove_ai_watermarks.metadata import synthid_source - evidence = (synthid_source(image_path) or "").casefold() + info = extract_c2pa_info(image_path) + evidence = (synthid_source(image_path, c2pa_info=info) or "").casefold() except Exception: return None if "google" in evidence: return "google" if "openai" in evidence: return "openai" + if not c2pa_info_has_invalid_credential(info) and c2pa_info_has_invismark(info): + return "microsoft" return None diff --git a/src/remove_ai_watermarks/metadata.py b/src/remove_ai_watermarks/metadata.py index 8d8f528..93d5618 100644 --- a/src/remove_ai_watermarks/metadata.py +++ b/src/remove_ai_watermarks/metadata.py @@ -792,7 +792,7 @@ def _iptc_ai_system_impl(image_path: Path) -> str | None: return iptc_ai_system_in(scan_head(image_path)) -def synthid_source(image_path: Path) -> str | None: +def synthid_source(image_path: Path, *, c2pa_info: dict[str, Any] | None = None) -> str | None: """Return the vendor name(s) when provenance establishes SynthID. This is provenance-based, not a local pixel decode. Google states that all @@ -808,6 +808,8 @@ def synthid_source(image_path: Path) -> str | None: Args: image_path: Path to the image (PNG, JPEG, WebP, or ISOBMFF container). + c2pa_info: Already extracted normalized C2PA info, for callers that also need + another claim from the same manifest. Returns: Comma-joined vendor name(s) (e.g. ``"OpenAI"``) or None. @@ -821,7 +823,7 @@ def synthid_source(image_path: Path) -> str | None: # Prefer the official reader's structured result. A failed asset binding or # signature cannot establish the claim, and the raw fallback below must not # resurrect it from the same damaged manifest bytes. - c2pa = extract_c2pa_info(image_path) + c2pa = extract_c2pa_info(image_path) if c2pa_info is None else c2pa_info if c2pa_info_has_invalid_credential(c2pa): return None vendors = c2pa.get("synthid_vendors") diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 733d59f..9f1093a 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -596,6 +596,19 @@ class TestSynthIDSourceNonPng: ) assert synthid_source(path) == "OpenAI" + def test_preextracted_c2pa_is_reused(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """A caller classifying another claim must not parse the manifest twice.""" + from remove_ai_watermarks._internal import c2pa + + path = self._c2pa_jpeg(tmp_path, "chatgpt-reused.jpg", b"OpenAI") + info = {"synthid_vendors": ["OpenAI"]} + + def unexpected_extract(_path: Path): + pytest.fail("synthid_source reparsed pre-extracted C2PA") + + monkeypatch.setattr(c2pa, "extract_c2pa_info", unexpected_extract) + assert synthid_source(path, c2pa_info=info) == "OpenAI" + def test_legacy_openai_c2pa_without_watermark_action_is_none(self, tmp_path: Path): path = self._c2pa_jpeg(tmp_path, "legacy-chatgpt.jpg", b"OpenAI") assert synthid_source(path) is None diff --git a/tests/test_platform.py b/tests/test_platform.py index bf81d43..ceeea88 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -15,6 +15,7 @@ from remove_ai_watermarks._internal.utils import get_image_format, is_supported_ from remove_ai_watermarks._internal.watermark_profiles import ( PROFILE_CHOICES, QWEN_ZIMAGE_GOOGLE_STRENGTH, + QWEN_ZIMAGE_OPENAI_STRENGTH, REMOVAL_MODULES, SDXL_ZIMAGE_GEMINI_STRENGTH, SDXL_ZIMAGE_OPENAI_STRENGTH, @@ -198,19 +199,19 @@ class TestNoReembeddedWatermark: class TestResolveStrength: - """resolve_strength answers for sdxl-zimage and defers for qwen-zimage.""" + """resolve_strength owns the qwen-zimage and sdxl-zimage policies.""" def test_qwen_zimage_answers_from_the_resolution_curve(self): """The function is total: it owns both policies rather than returning None. - qwen-zimage picks strength from image area, so it takes the size. Returning - None for it would push that branch onto every caller and leave one of the two - strength policies living outside this module. Google content is the one - measured exception since 0.27.2: it takes the flat oracle-measured floor - instead of the curve (the curve's 0.154 top left a 4.33 MP fixture - oracle-detected on the full production path). + qwen-zimage picks unknown content's strength from image area, so it takes the + size. Returning None would push that branch onto every caller and leave one of + the two strength policies living outside this module. Measured providers take + flat corpus-derived operating points instead. """ - assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) + assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx( + QWEN_ZIMAGE_OPENAI_STRENGTH + ) assert resolve_strength(None, None, "qwen-zimage", size=(600, 500)) == pytest.approx(0.084) assert resolve_strength(None, "google", "qwen-zimage", size=(600, 500)) == QWEN_ZIMAGE_GOOGLE_STRENGTH # The floor holds at every size, not only above the curve's top rung. @@ -221,6 +222,12 @@ class TestResolveStrength: with pytest.raises(ValueError, match="size is required"): resolve_strength(None, "openai", "qwen-zimage") + @pytest.mark.parametrize(("vendor", "expected"), [("microsoft", 0.15), ("openai", 0.07675)]) + def test_qwen_zimage_measured_vendors_use_flat_cross_source_margins(self, vendor, expected): + """Measured providers must not fall below their operating points on small files.""" + assert resolve_strength(None, vendor, "qwen-zimage", size=(600, 500)) == pytest.approx(expected) + assert resolve_strength(None, vendor, "qwen-zimage", size=(4000, 3000)) == pytest.approx(expected) + def test_sdxl_zimage_uses_its_flat_vendor_ladder(self): assert SDXL_ZIMAGE_OPENAI_STRENGTH == 0.15 @@ -253,7 +260,7 @@ class TestResolveStrength: class TestVendorForStrength: - """vendor_for_strength normalizes SynthID provenance to openai/google/None.""" + """Normalize supported invisible-watermark provenance to a removal profile.""" @staticmethod def _patch(value): @@ -271,6 +278,22 @@ class TestVendorForStrength: with self._patch("Google"): assert vendor_for_strength(Path("x.png")) == "google" + @pytest.mark.parametrize(("integrity", "expected"), [("valid", "microsoft"), ("invalid", None)]) + def test_only_valid_microsoft_invismark_selects_the_removal_floor(self, integrity, expected): + from remove_ai_watermarks._internal.watermark_profiles import vendor_for_strength + + info = { + "soft_binding_vendors": ["Microsoft InvisMark"], + "c2pa_integrity": integrity, + "c2pa_signature": "valid", + "c2pa_signer_validity": "valid", + } + with ( + self._patch(None), + patch("remove_ai_watermarks._internal.c2pa.extract_c2pa_info", return_value=info), + ): + assert vendor_for_strength(Path("x.png")) == expected + def test_both_issuers_google_wins(self): # The more-robust watermark wins -> safer (higher) strength. from remove_ai_watermarks._internal.watermark_profiles import vendor_for_strength diff --git a/tests/test_qwen_zimage_pipeline.py b/tests/test_qwen_zimage_pipeline.py index 3e4775c..53b7b95 100644 --- a/tests/test_qwen_zimage_pipeline.py +++ b/tests/test_qwen_zimage_pipeline.py @@ -908,6 +908,7 @@ def test_sdxl_zimage_strength_is_vendor_adaptive_and_leaves_other_profiles_alone """An SDXL global pass needs more strength than Qwen, so it gets its own policy.""" from remove_ai_watermarks._internal.watermark_profiles import ( QWEN_ZIMAGE_GOOGLE_STRENGTH, + QWEN_ZIMAGE_OPENAI_STRENGTH, SDXL_ZIMAGE_GEMINI_STRENGTH, SDXL_ZIMAGE_OPENAI_STRENGTH, resolve_strength, @@ -917,11 +918,12 @@ def test_sdxl_zimage_strength_is_vendor_adaptive_and_leaves_other_profiles_alone assert resolve_strength(None, "google", "sdxl-zimage") == pytest.approx(SDXL_ZIMAGE_GEMINI_STRENGTH) # Unknown provenance takes the stricter of the two. assert resolve_strength(None, None, "sdxl-zimage") == pytest.approx(SDXL_ZIMAGE_GEMINI_STRENGTH) - # An explicit value still wins, and qwen-zimage's curve is untouched by this - # ladder - with the one measured exception: Google content takes the flat - # oracle floor (0.27.2), not the area curve. + # An explicit value still wins. qwen-zimage's unknown cohort keeps the curve, + # while measured providers take their flat operating points. assert resolve_strength(0.4, "google", "sdxl-zimage") == pytest.approx(0.4) - assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx(0.154) + assert resolve_strength(None, "openai", "qwen-zimage", size=(2000, 1850)) == pytest.approx( + QWEN_ZIMAGE_OPENAI_STRENGTH + ) assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850)) == pytest.approx( QWEN_ZIMAGE_GOOGLE_STRENGTH ) diff --git a/uv.lock b/uv.lock index 7f475ef..add1ccf 100644 --- a/uv.lock +++ b/uv.lock @@ -794,7 +794,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" }, @@ -827,43 +827,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]] @@ -1309,15 +1309,15 @@ name = "lightning" version = "2.6.5" 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/c9/1d/83be8536bec71a0173e762a9a1fd92a24a5ad0d0f74c59550c3c4e6c103b/lightning-2.6.5.tar.gz", hash = "sha256:16a30310ed69afde3748491feb5d13508908effd70390d2bfc203dc0812a4b4a", size = 659201, upload-time = "2026-05-27T14:33:41.806Z" } wheels = [ @@ -1329,8 +1329,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 = [ @@ -1763,7 +1763,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" }, @@ -1802,7 +1802,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" 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/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1814,7 +1814,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" }, @@ -1844,9 +1844,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" }, @@ -1858,7 +1858,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" }, @@ -1915,8 +1915,8 @@ name = "omegaconf" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "antlr4-python3-runtime", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } wheels = [ @@ -2957,14 +2957,14 @@ name = "pytorch-lightning" version = "2.6.5" 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/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } wheels = [ @@ -3172,7 +3172,7 @@ wheels = [ [[package]] name = "remove-ai-watermarks" -version = "0.31.0" +version = "0.31.1" source = { editable = "." } dependencies = [ { name = "c2pa-python" }, @@ -3725,10 +3725,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 = [ @@ -3823,13 +3823,13 @@ name = "trustmark" version = "0.9.1" 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/87/0a/0a4232030c6a62d12b6a02ae73bdce6e99c8532bc8f05a5a2e6ce103da82/trustmark-0.9.1.tar.gz", hash = "sha256:dc79e3fb070f5d94765acf8868a51f50a612cc05b53223cf1e6b605d4ff1e0ae", size = 63949, upload-time = "2026-04-09T08:59:52.472Z" }