remove: drop all face-restore code (regeneration, not preservation)

Empirical conclusion from the 2026-06-04 - 2026-06-08 Modal cert sweeps:
every face-restore approach we built (GFPGAN-on-cleaned, PhotoMaker-V2,
InstantID txt2img, InstantID img2img-on-cleaned at three parameter
settings) regenerates the face via SDXL diffusion rather than preserves
it. Output face pixels are diffusion-fresh, so the regenerated face
inherits SDXL "clean skin" aesthetic and loses original identity
precision -- it looks MORE AI-generated than the cleaned image, not
less. The cleaned image from the main controlnet 0.20 removal pass is
the least-AI face state we can reach without re-introducing SynthID.

Nothing in the restore family achieves the actual goal (preserve the
original person's face). Keeping them around as opt-in invites users to
ship something that defeats the point. Removing entirely.

Library changes:
- Deleted src/remove_ai_watermarks/instantid_restore.py
- Deleted src/remove_ai_watermarks/photomaker_restore.py
- Deleted tests/test_instantid_restore.py
- Deleted tests/test_photomaker_restore.py
- Removed `instantid` and `photomaker` extras from pyproject.toml
- Removed `[tool.hatch.metadata] allow-direct-references = true` (was
  only needed for the photomaker git+ URL)
- InvisibleEngine.remove_watermark: dropped `restore_faces` +
  `restore_faces_method` params, removed both `_restore_faces_instantid`
  and `_restore_faces_photomaker` private methods, removed dispatch
- CLI: dropped `_restore_faces_options` decorator, all four cmd_*
  signatures lose `restore_faces` + `restore_faces_method`, kwarg passes
  to remove_watermark dropped
- _apply_auto: dropped `restore_faces` from tuple shape (was unused after
  the engine no longer takes it)
- auto_config.AutoConfig: dropped `restore_faces` field; `plan()` no
  longer sets it; `reason` no longer mentions it
- Tests updated accordingly (test_auto_config.TestReason no longer asserts
  "face-restore on" in the reason string)

Docs updated:
- CLAUDE.md: removed the photomaker extras bullet, the Face restore
  trade-off bullet, the instantid_restore.py + photomaker_restore.py
  module bullets; replaced restore mentions in watermark_remover and
  controlnet bullets and prod recipe with the empirical conclusion
- README.md: removed both `--restore-faces` callouts and the install
  snippet; the feature bullet and auto-mode comment updated
- docs/synthid-robust-identity-research.md: added Status-retired notice
  at the top pointing at the 2026-06-08 followup

raiw-app:
- modal_cert.py: dropped `--restore-faces` flag entirely; sweep() no
  longer takes restore_faces; pinned _LIB_SPEC to `[gpu]` extras (no
  `photomaker` / `instantid` extras), points at main

ruff + strict pyright clean; 569 tests pass; 18 restore-specific tests
gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-08 21:21:58 -07:00
parent 567f3ae729
commit 20d7eda96a
14 changed files with 33 additions and 2014 deletions
+5 -9
View File
File diff suppressed because one or more lines are too long
+7 -18
View File
@@ -23,7 +23,7 @@ If this tool saves you time, consider [sponsoring its development](https://githu
- **AI metadata stripping** — EXIF, PNG text chunks, C2PA provenance manifests (PNG / JPEG / AVIF / HEIF / JPEG-XL, **MP4 / MOV / M4V / M4A** at the container level, and **WebM / MP3 / WAV / FLAC / OGG** losslessly via ffmpeg), XMP DigitalSourceType
- **"Made with AI" label removal** — removes the AI-disclosure metadata that platforms read to apply automatic labels (useful for clearing a false-positive label from a human-edited photograph)
- **Analog Humanizer** — optional film grain and chromatic aberration post-processing
- **Text and face preservation (experimental)** — optional `--pipeline controlnet` adds a canny ControlNet that keeps text and face structure sharp through the removal pass (without copying original pixels, so SynthID is still removed). Canny preserves face *structure*, not *identity* (the regenerated face drifts in likeness). The optional `--restore-faces` post-pass (`instantid` default or `photomaker`, both **NON-COMMERCIAL**, off by default) does NOT recover the original face — it **regenerates** it from an ArcFace embedding via SDXL diffusion, which inherently makes the output look more AI-generated than the cleaned image. **For production face preservation, leave restore OFF and use the cleaned image as-is.**
- **Text and face preservation (experimental)** — optional `--pipeline controlnet` adds a canny ControlNet that keeps text and face structure sharp through the removal pass (without copying original pixels, so SynthID is still removed). Canny preserves face *structure*, not *identity* (the regenerated face drifts in likeness). The library does not ship a face-restore extra: every approach evaluated (GFPGAN-on-cleaned, PhotoMaker-V2, InstantID txt2img, InstantID img2img-on-cleaned) regenerated the face via SDXL and made the output look more AI-generated than the cleaned image. The cleaned controlnet output is the least-AI face state achievable without re-introducing SynthID.
- **Batch processing** — process entire directories
- **Detection** — three-stage NCC watermark detection with confidence scoring
- **Provenance detection (`identify`)** — aggregate C2PA issuer, the C2PA soft-binding forensic-watermark vendor (Adobe TrustMark, Digimarc, Imatag, ...), IPTC "Made with AI" plus the IPTC 2025.1 `AISystemUsed` field, embedded SD/ComfyUI params, EXIF/XMP generator tags, the xAI/Grok EXIF signature, the China TC260 AIGC label (XMP, PNG chunk, or EXIF), the HuggingFace `hf-job-id` job marker, the SynthID metadata proxy, the visible marks (Gemini sparkle plus the Doubao "豆包AI生成" / Jimeng "即梦AI" / Samsung Galaxy AI "Contenuti generati dall'AI" text marks), the open SD/SDXL/FLUX invisible watermark, and (with the `trustmark` extra) the open Adobe TrustMark watermark into one origin-platform + watermark-inventory verdict (`--json` for machine output)
@@ -128,7 +128,7 @@ image → encode to latent space (VAE) at native resolution
>
> **`--pipeline controlnet` preserves text and face structure (experimental, opt-in).** It runs the same SDXL img2img scrub but adds a canny ControlNet that conditions the regeneration on the image's edge map, so text and structure stay sharp at the strengths that remove SynthID. The watermark removal still comes from the img2img regeneration (`--strength`); the ControlNet only preserves structure — no original pixels are copied or frozen, so SynthID does not survive. `--controlnet-scale` tunes the preservation strength (higher = closer to the original structure). Runs fp32 on mps/cpu (fp16 only on cuda/xpu, where the fp16-fixed SDXL VAE is loaded automatically).
>
> **`--restore-faces` REGENERATES faces; it does NOT recover original pixels.** Two methods, both **NON-COMMERCIAL**, both off by default: `instantid` (default, the `instantid` extra; InstantID img2img-on-cleaned + ArcFace embedding + landmark ControlNet) and `photomaker` (the `photomaker` extra; PhotoMaker-V2 txt2img + CLIP+ArcFace embedding). Both crop the face region from the cleaned image and run SDXL diffusion conditioned on an ArcFace embedding from the original — the output face pixels are diffusion-fresh so SynthID is not re-introduced, **but the output face inherently looks more AI-generated than the cleaned image**: every pixel is SDXL-decoded from a semantic embedding, gaining the typical "clean skin" gloss and losing the exact original identity. The cleaned image from the main controlnet 0.20 pass is the least-AI state we can reach without re-introducing SynthID; any restore on top of it trades original-look for embedding-driven regeneration. **For production face preservation, leave `--restore-faces` OFF.** Both extras are NON-COMMERCIAL because their ArcFace embedder is InsightFace's antelopev2 pack which is research-only; the empirical case for not shipping them in prod is the AI-look regardless of license (see `docs/synthid-robust-identity-research-2026-06-08.md`).
> **No face-restore extra in the library.** Every ArcFace-based regeneration approach we evaluated (GFPGAN-on-cleaned, PhotoMaker-V2, InstantID txt2img, InstantID img2img-on-cleaned at three parameter sweeps, 2026-06-04 - 2026-06-08 Modal cert sweeps) regenerated the face via SDXL diffusion — the output face pixels were diffusion-fresh (SynthID not re-introduced), but the face inherently looked more AI-generated than the cleaned image (SDXL "clean skin" gloss, lost original identity precision). The cleaned image from the main controlnet 0.20 pass is the least-AI face state we can reach without re-introducing SynthID. Empirical conclusion in `docs/synthid-robust-identity-research-2026-06-08.md`.
SDXL is the default since May 2026: empirically defeats SynthID v2 on Gemini 3 Pro outputs, where the older SD-1.5 pipeline at 768 px did not. The SD-1.5 path was removed once it was verified not to handle v2. Note the scope: this defeats the SynthID *verifier*, which is not the same as being forensically indistinguishable from a real photo. Recent work ([arXiv:2605.09203](https://arxiv.org/abs/2605.09203)) shows watermark-removal pipelines leave detectable traces, so a separate "this image was processed" classifier can still flag the output.
@@ -136,7 +136,7 @@ SDXL is the default since May 2026: empirically defeats SynthID v2 on Gemini 3 P
> **Technical deep-dive:** see [`docs/synthid.md`](docs/synthid.md) for a primary-source-cited breakdown of how SynthID works mechanically (post-hoc encoder/decoder, 136-bit payload, pixel-space embedding), what it empirically survives (JPEG, crop, resize: ~99.98% TPR at 0.1% FPR from arXiv:2510.09263), what removes it, and the forensic-stealth tradeoff (all known removal attacks are detectable at >98% TPR@1%FPR per arXiv:2605.09203).
**Text and face preservation** (experimental, opt-in `--pipeline controlnet`): adds a canny ControlNet so text and face *structure* stay sharp through the removal pass, without copying or freezing any original pixels (so SynthID is still removed). Tune the preservation strength with `--controlnet-scale`. Canny preserves structure but not face *identity*: the regenerated face drifts in likeness. The optional `--restore-faces` post-pass would regenerate the face from an ArcFace embedding, but every shipped method makes the face look more AI-generated (see the callout above) — for production face preservation leave restore OFF.
**Text and face preservation** (experimental, opt-in `--pipeline controlnet`): adds a canny ControlNet so text and face *structure* stay sharp through the removal pass, without copying or freezing any original pixels (so SynthID is still removed). Tune the preservation strength with `--controlnet-scale`. Canny preserves structure but not face *identity*: the regenerated face drifts in likeness. The library does not ship a face-restore extra (see the callout above).
**Analog Humanizer**: optional film grain and chromatic aberration injection that mimics a photo of a screen, raising the bar for AI-generated image classifiers. (It frustrates generic classifiers but does not guarantee forensic invisibility — see the [arXiv:2605.09203](https://arxiv.org/abs/2605.09203) note above.)
@@ -214,17 +214,6 @@ After installation the `remove-ai-watermarks` command is available system-wide.
> pip install -e ".[trustmark]" # or: uv pip install -e ".[trustmark]"
> ```
>
> To regenerate face identity after invisible removal (the `--restore-faces`
> PhotoMaker-V2 post-pass, experimental and opt-in, **NON-COMMERCIAL** because
> PhotoMaker-V2 pulls non-commercial InsightFace model packs at runtime), install
> the `photomaker` extra. The PhotoMaker-V2 adapter weights and InsightFace face
> packs download on first use. Do NOT install this extra in a commercial / paid
> service:
>
> ```bash
> pip install -e ".[photomaker]" # or: uv pip install -e ".[photomaker]"
> ```
>
> For sharper upscaling of small inputs before diffusion (`--upscaler esrgan`,
> Real-ESRGAN), install the `esrgan` extra. It loads via spandrel (MIT, no basicsr);
> the Real-ESRGAN weights (BSD-3-Clause) download on first use:
@@ -306,10 +295,10 @@ remove-ai-watermarks invisible image.png -o clean.png --humanize 4.0 --unsharp 0
# Strength is vendor-adaptive by default (OpenAI 0.10 / Google 0.15); override
# with --strength. To preserve text/face structure, use --pipeline controlnet
# Or let it choose: --auto picks the pipeline, face restore, and an adaptive polish
# from the image content (controlnet when there is text/structure, face restore when
# a face is present, polish that restores the input's detail level while sparing
# text). Every choice is overridable: --pipeline, --no-restore-faces,
# --no-adaptive-polish all win over the auto pick. Experimental.
# from the image content (controlnet when there is text/structure, polish that
# restores the input's detail level while sparing text). Every choice is
# overridable: --pipeline and --no-adaptive-polish win over the auto pick.
# Experimental.
# (SDXL + canny ControlNet); tune preservation with --controlnet-scale. Add
# Check / strip AI metadata (C2PA, EXIF, "Made with AI" labels)
+7
View File
@@ -1,5 +1,12 @@
# SynthID-robust face identity for an SDXL removal pipeline (research)
> **Status (2026-06-08): retired.** Every approach described below was empirically
> tested and rejected -- see `docs/synthid-robust-identity-research-2026-06-08.md`
> "Empirical follow-up" for the final conclusion. The library no longer ships any
> face-restore extra: the cleaned image from the main controlnet 0.20 pass is the
> least-AI face state we can reach without re-introducing SynthID. This document
> is kept as historical record of the exploration.
**Question.** Which face identity-preservation mechanism for an SDXL img2img +
canny-ControlNet watermark-removal pipeline (denoise 0.20-0.30) is BOTH (a)
commercial-safe end-to-end and (b) does not re-introduce the SynthID pixel
-52
View File
@@ -76,52 +76,6 @@ lama = [
"onnxruntime>=1.16.0",
"huggingface-hub>=0.20.0",
]
# **NON-COMMERCIAL.** Optional PhotoMaker-V2 SynthID-robust face-identity post-pass.
# PhotoMaker-V2's ID encoder pulls an InsightFace ArcFace embedding at runtime, and
# the pretrained InsightFace model packs (antelopev2, buffalo_l) are released under a
# non-commercial / research-only license. A paid service (raiw.cc, any monetized SaaS)
# MUST NOT use this extra. See `src/remove_ai_watermarks/photomaker_restore.py` and
# `docs/synthid-robust-identity-research.md`. The PhotoMaker adapter weights
# (photomaker-v2.bin) are Apache-2.0 and download on first use; the InsightFace model
# packs download on first FaceAnalysis() (only triggered inside PhotoMaker's V2 forward).
# Pins beyond the upstream PhotoMaker package itself patch missing declarations that
# would otherwise break the load chain (verified empirically via the Modal cert sweep
# 2026-06-04): einops (used in forward), peft (required by diffusers.fuse_lora),
# onnxruntime (transitive via insightface), and insightface itself (required for the
# package's __init__.py to even import).
photomaker = [
"photomaker @ git+https://github.com/TencentARC/PhotoMaker.git",
"huggingface-hub>=0.20.0",
"einops>=0.7.0",
"insightface>=0.7.3",
"onnxruntime>=1.16.0",
"peft>=0.10.0",
]
# **NON-COMMERCIAL.** Optional InstantID SynthID-robust face-identity post-pass.
# InstantID adapter weights (IdentityNet ControlNet + ip-adapter.bin) are Apache-2.0
# from InstantX/InstantID on HuggingFace, BUT the runtime depends on InsightFace's
# antelopev2 ArcFace pack (non-commercial / research-only). InstantX's maintainers
# explicitly acknowledged this on HF (discussion #2) and stated intent to retrain
# on commercial embedders -- as of the 2026-06-08 deep-research synthesis
# (docs/synthid-robust-identity-research-2026-06-08.md) that retrain has not
# shipped. A paid service (raiw.cc, any monetized SaaS) MUST NOT use this extra.
# See `src/remove_ai_watermarks/instantid_restore.py`.
#
# Compared to the `photomaker` extra: InstantID adds spatial landmark conditioning
# alongside the ArcFace semantic branch, giving stronger identity fidelity on
# single portraits per the InstantID paper (arXiv:2401.07519). Both extras are
# non-commercial; pick `instantid` by default for better identity, `photomaker`
# when the InstantID community pipeline can't load.
#
# Loads via diffusers' community-pipeline mechanism (no upstream `instantid`
# Python package on PyPI). Only direct deps are insightface (MIT code, the
# non-commercial blocker is its MODEL packs) + onnxruntime (transitive via
# insightface) + huggingface-hub (weights download).
instantid = [
"insightface>=0.7.3",
"onnxruntime>=1.16.0",
"huggingface-hub>=0.20.0",
]
# Optional pre-diffusion super-resolution for small inputs (Real-ESRGAN). Loaded via
# spandrel (MIT) -- a pure model-loader with NO basicsr dependency (it pulls only
# torch / torchvision / safetensors / numpy / einops).
@@ -180,12 +134,6 @@ Repository = "https://github.com/wiltodelta/remove-ai-watermarks"
requires = ["hatchling<1.31"]
build-backend = "hatchling.build"
# Allow the `photomaker` extra to reference the upstream git URL directly (the
# TencentARC/PhotoMaker package is not on PyPI). The extra itself is NON-COMMERCIAL
# (see the photomaker block above and `photomaker_restore.py`).
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["src/remove_ai_watermarks"]
+2 -6
View File
@@ -85,7 +85,6 @@ class AutoConfig:
"""Resolved quality modes from content analysis (the ``--auto`` plan)."""
pipeline: str # "default" | "controlnet"
restore_faces: bool
adaptive_polish: bool # restore the input's detail level (sharpen + masked grain), sparing text
unsharp: float # fixed-polish knobs, 0 in auto (the adaptive polish replaces them)
humanize: float
@@ -104,14 +103,13 @@ class AutoConfig:
if self.has_text:
bits.append("text")
bits.append(f"edges={self.edge_density:.3f}")
rf = ", face-restore on" if self.restore_faces else ""
if self.adaptive_polish:
polish = ", adaptive polish"
elif self.unsharp or self.humanize:
polish = f", unsharp {self.unsharp}/grain {self.humanize}"
else:
polish = ""
return f"{'+'.join(bits)} -> {self.pipeline} pipeline{rf}{polish}"
return f"{'+'.join(bits)} -> {self.pipeline} pipeline{polish}"
def _to_bgr(image: NDArray[Any]) -> NDArray[Any]:
@@ -251,12 +249,10 @@ def plan(image_path: Path) -> AutoConfig | None:
structureless = (not has_face) and (not has_text) and edges < _STRUCTURELESS_EDGE_MAX
pipeline = "default" if structureless else "controlnet"
restore_faces = has_face
smoothing = pipeline == "controlnet" or restore_faces
smoothing = pipeline == "controlnet"
cfg = AutoConfig(
pipeline=pipeline,
restore_faces=restore_faces,
adaptive_polish=smoothing, # adaptive (detail-targeted) polish when a smoothing pass ran
unsharp=0.0,
humanize=0.0,
+7 -68
View File
@@ -174,7 +174,7 @@ _auto_option = click.option(
is_flag=True,
default=False,
help="Auto-pick the pipeline, face restore, and adaptive polish from image content. "
"Every choice is overridable -- an explicit --pipeline / --restore-faces / --adaptive-polish "
"Every choice is overridable -- an explicit --pipeline / --adaptive-polish "
"always wins. EXPERIMENTAL.",
)
@@ -192,9 +192,8 @@ def _apply_auto(
ctx: click.Context,
source: Path,
pipeline: str,
restore_faces: bool,
adaptive_polish: bool,
) -> tuple[str, bool, bool]:
) -> tuple[str, bool]:
"""Resolve ``--auto``: plan the three content-adaptive modes (pipeline, face
restore, adaptive polish) from the image, overriding only the ones the user left
at their default (an explicit flag always wins). The fixed ``--unsharp``/
@@ -205,19 +204,17 @@ def _apply_auto(
cfg = auto_config.plan(source)
if cfg is None:
console.print(" Auto: could not read image; using defaults")
return pipeline, restore_faces, adaptive_polish
return pipeline, adaptive_polish
def _is_default(name: str) -> bool:
return ctx.get_parameter_source(name) == click.core.ParameterSource.DEFAULT
if _is_default("pipeline"):
pipeline = cfg.pipeline
if _is_default("restore_faces"):
restore_faces = cfg.restore_faces
if _is_default("adaptive_polish"):
adaptive_polish = cfg.adaptive_polish
console.print(f" Auto: {cfg.reason}")
return pipeline, restore_faces, adaptive_polish
return pipeline, adaptive_polish
def _warn_if_esrgan_unavailable(upscaler: str) -> None:
@@ -235,43 +232,6 @@ def _warn_if_esrgan_unavailable(upscaler: str) -> None:
console.print(" Note: --upscaler esrgan needs the 'esrgan' extra; falling back to Lanczos.")
def _restore_faces_options(f: Any) -> Any:
"""Attach the face-restoration flags to an invisible-pipeline command.
Both methods REGENERATE the face from an ArcFace embedding via SDXL diffusion
-- they do NOT recover original pixels. Every output face pixel is
diffusion-fresh, so the regenerated face inherently looks MORE AI-generated
than the cleaned image (gloss, symmetric pores, SDXL "clean skin"
aesthetic). For production face preservation, leave the flag OFF and use
the cleaned image as-is. The two methods are kept for research / personal
use where users explicitly want identity regeneration. **BOTH are
NON-COMMERCIAL**: they pull InsightFace antelopev2 / buffalo_l model packs
which are research-only. A paid service (raiw.cc, any monetized SaaS) MUST
NOT use this flag.
"""
method = click.option(
"--restore-faces-method",
type=click.Choice(["instantid", "photomaker"]),
default="instantid",
help="Face-regeneration mechanism (no method recovers original pixels; both "
"REGENERATE the face via SDXL). 'instantid' (default) uses InstantID img2img on "
"the cleaned crop with ArcFace + landmark ControlNet. 'photomaker' uses "
"PhotoMaker-V2 txt2img + CLIP+ArcFace dual encoder. **BOTH are NON-COMMERCIAL** "
"(InsightFace antelopev2 / buffalo_l packs are research-only). For personal / "
"research use only.",
)(f)
return click.option(
"--restore-faces/--no-restore-faces",
default=False,
help="EXPERIMENTAL, opt-in, **NON-COMMERCIAL**. **REGENERATES the face** (does "
"NOT recover original pixels) via the chosen --restore-faces-method; the "
"regenerated face looks more AI-generated than the cleaned image. Off by "
"default; auto-skips when no face is detected or the chosen extra is absent. "
"For production face preservation leave this OFF and use the cleaned image "
"as-is.",
)(method)
def _watermark_region(det: DetectionResult, width: int, height: int) -> tuple[int, int, int, int]:
"""Pick a watermark bbox: detector's region if confident, else the default config slot."""
if det.confidence > 0.15:
@@ -597,7 +557,6 @@ def cmd_erase(
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
)
@_controlnet_scale_option
@_restore_faces_options
@_min_resolution_option
@_unsharp_option
@_upscaler_option
@@ -619,8 +578,6 @@ def cmd_invisible(
max_resolution: int,
min_resolution: int,
controlnet_scale: float,
restore_faces: bool,
restore_faces_method: str,
upscaler: str,
auto: bool,
adaptive_polish: bool,
@@ -643,7 +600,7 @@ def cmd_invisible(
source = _validate_image(source)
_warn_if_esrgan_unavailable(upscaler)
if auto:
pipeline, restore_faces, adaptive_polish = _apply_auto(ctx, source, pipeline, restore_faces, adaptive_polish)
pipeline, adaptive_polish = _apply_auto(ctx, source, pipeline, adaptive_polish)
if output is None:
output = source.with_stem(source.stem + "_clean")
@@ -682,8 +639,6 @@ def cmd_invisible(
min_resolution=min_resolution,
upscaler=upscaler,
vendor=vendor,
restore_faces=restore_faces,
restore_faces_method=restore_faces_method,
)
elapsed = time.monotonic() - t0
@@ -859,7 +814,6 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
)
@_controlnet_scale_option
@_restore_faces_options
@_min_resolution_option
@_unsharp_option
@_upscaler_option
@@ -884,8 +838,6 @@ def cmd_all(
max_resolution: int,
min_resolution: int,
controlnet_scale: float,
restore_faces: bool,
restore_faces_method: str,
upscaler: str,
auto: bool,
adaptive_polish: bool,
@@ -905,7 +857,7 @@ def cmd_all(
source = _validate_image(source)
_warn_if_esrgan_unavailable(upscaler)
if auto:
pipeline, restore_faces, adaptive_polish = _apply_auto(ctx, source, pipeline, restore_faces, adaptive_polish)
pipeline, adaptive_polish = _apply_auto(ctx, source, pipeline, adaptive_polish)
if output is None:
output = source.with_stem(source.stem + "_clean")
@@ -993,8 +945,6 @@ def cmd_all(
min_resolution=min_resolution,
upscaler=upscaler,
vendor=vendor,
restore_faces=restore_faces,
restore_faces_method=restore_faces_method,
)
console.print(" Invisible watermark removed")
@@ -1049,8 +999,6 @@ def _process_batch_image(
unsharp: float = 0.0,
max_resolution: int = 0,
min_resolution: int = 1024,
restore_faces: bool = False,
restore_faces_method: str = "instantid",
controlnet_scale: float = 1.0,
upscaler: str = "lanczos",
auto: bool = False,
@@ -1104,9 +1052,7 @@ def _process_batch_image(
# pipeline choice changes the engine ctor, so cache one engine per pipeline
# (controlnet vs default) rather than a single shared instance.
if auto:
pipeline, restore_faces, adaptive_polish = _apply_auto(
ctx, img_path, pipeline, restore_faces, adaptive_polish
)
pipeline, adaptive_polish = _apply_auto(ctx, img_path, pipeline, adaptive_polish)
engines = ctx.obj.setdefault("_inv_engines", {})
if pipeline not in engines:
engines[pipeline] = InvisibleEngine(
@@ -1128,8 +1074,6 @@ def _process_batch_image(
max_resolution=max_resolution,
min_resolution=min_resolution,
upscaler=upscaler,
restore_faces=restore_faces,
restore_faces_method=restore_faces_method,
# Detect the vendor from the pristine original (`img_path`), not the
# visible-processed `out_path` whose C2PA is already gone.
vendor=vendor_for_strength(img_path),
@@ -1187,7 +1131,6 @@ def _process_batch_image(
default=0,
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
)
@_restore_faces_options
@_min_resolution_option
@_unsharp_option
@_upscaler_option
@@ -1211,8 +1154,6 @@ def cmd_batch(
unsharp: float,
max_resolution: int,
min_resolution: int,
restore_faces: bool,
restore_faces_method: str,
controlnet_scale: float,
upscaler: str,
auto: bool,
@@ -1271,8 +1212,6 @@ def cmd_batch(
unsharp=unsharp,
max_resolution=max_resolution,
min_resolution=min_resolution,
restore_faces=restore_faces,
restore_faces_method=restore_faces_method,
controlnet_scale=controlnet_scale,
upscaler=upscaler,
auto=auto,
@@ -1,593 +0,0 @@
"""SynthID-robust face identity restoration via InstantID.
**NON-COMMERCIAL.** InstantID's runtime depends on the InsightFace ``antelopev2``
ArcFace model pack, which InsightFace releases under a research-only license:
"The training data containing the annotation (and the models trained with
these data) are available for non-commercial research purposes only."
-- insightface upstream README
The InstantX maintainers themselves acknowledged on HuggingFace
(``InstantX/InstantID`` discussion #2) that "InstantID cannot be Apache 2.0 if it
is using Insight Face" and stated intent to retrain on commercial face encoders.
As of 2026-06-08 (deep-research synthesis in
``docs/synthid-robust-identity-research-2026-06-08.md``) that retrain has not
shipped. **A paid service (raiw.cc, any monetized SaaS) MUST NOT use this path.**
The default ``--restore-faces-method`` is ``instantid`` (this module). The
alternative ``photomaker`` is also non-commercial. There is no commercial-safe
ArcFace-grade identity-preservation stack for SDXL today.
Architecture (vs the earlier txt2img variant):
- The earlier (txt2img) integration generated each face from scratch in a fresh
1024 scene with InstantID's standard pipeline. That produced studio-portrait
faces with the wrong lighting / head angle for the surrounding scene; on
group photos the per-face composites read as patchwork even after color
matching and elliptical alphas.
- This (img2img on cleaned) integration feeds the CLEANED face crop as the
img2img source. Diffusion sees the scene context (shoulders, hair edges,
lighting, shadow direction) directly and harmonises the regenerated face
with it. Identity still comes through the ArcFace embedding +
landmark-ControlNet, which are semantic / pure-geometry and carry no
watermark.
SynthID safety (load-bearing for raiw.cc):
- img2img source = CLEANED crop. Cleaned image is already oracle-verified
SynthID-free at our controlnet strength; cropping is a subset operation that
preserves that property.
- ArcFace embedding = from the ORIGINAL face crop (sharper identity, but the
embedding is semantic 512-d, no pixel content).
- Landmark stick figure = pure colour-coded geometry rendered from kps; no
source pixels.
- img2img diffusion adds noise to the cleaned source then denoises with
ControlNet + IP-Adapter conditioning. Any residual high-frequency pattern
in the cleaned crop is destroyed by that noise injection at the strengths we
use.
- We must NEVER feed the original image as img2img source (would re-introduce
SynthID outside the diffusion footprint at strength < 1). The code only ever
reads pixels from ``cleaned_bgr`` into ``image=`` -- the original is used
for the embedding + kps only.
Pipeline this module wires:
1. Detect faces in the CLEANED image (YuNet via ``auto_config``).
2. For each face: square-crop the SAME box from BOTH the original (for
ArcFace + kps) and the cleaned image (for img2img source). Resize both
to 1024x1024.
3. Render the kps as a stick figure (the ControlNet conditioning image).
4. Call the InstantID img2img pipeline
(``StableDiffusionXLInstantIDImg2ImgPipeline``) with ``image`` = cleaned
crop, ``control_image`` = landmark, ``image_embeds`` = ArcFace, and
``strength`` = ~0.55. The output 1024 is a face that fits the scene.
5. Elliptical-alpha + colour-match composite into the cleaned image.
Requires the optional ``instantid`` extra: ``pip install
'remove-ai-watermarks[instantid]'``. Weights download on first use; the
upstream img2img pipeline file (not on PyPI) is cached from
``raw.githubusercontent.com`` on first run.
"""
# cv2/torch/diffusers boundary: relax unknown-type rules for this file only.
# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownParameterType=false, reportMissingTypeArgument=false, reportMissingTypeStubs=false, reportMissingImports=false, reportArgumentType=false, reportAssignmentType=false, reportReturnType=false, reportCallIssue=false, reportIndexIssue=false, reportOperatorIssue=false, reportOptionalMemberAccess=false, reportOptionalCall=false, reportOptionalSubscript=false, reportOptionalOperand=false, reportAttributeAccessIssue=false, reportPrivateImportUsage=false, reportPrivateUsage=false, reportInvalidTypeForm=false, reportConstantRedefinition=false, reportUnnecessaryComparison=false
from __future__ import annotations
import importlib.util
import logging
import threading
from pathlib import Path
from typing import TYPE_CHECKING, Any
from remove_ai_watermarks.photomaker_restore import _face_crop_square
if TYPE_CHECKING:
from numpy.typing import NDArray
logger = logging.getLogger(__name__)
# InstantID checkpoint repo on HuggingFace. The IdentityNet ControlNet weights live
# under ``ControlNetModel/`` and the IP-Adapter file is ``ip-adapter.bin`` at the
# root. Both are Apache-2.0 (the InsightFace runtime dep is what makes the path
# non-commercial). Downloaded on first use.
_INSTANTID_REPO = "InstantX/InstantID"
_INSTANTID_CONTROLNET_SUBFOLDER = "ControlNetModel"
_INSTANTID_IP_ADAPTER = "ip-adapter.bin"
# Upstream InstantID img2img pipeline source. Not on PyPI, not on HF Hub at any path
# diffusers can auto-load -- the file lives in the InstantID GitHub repo. We download
# it once to a cache dir and pass it as ``custom_pipeline=<path>`` to diffusers.
_INSTANTID_IMG2IMG_URL = (
"https://raw.githubusercontent.com/instantX-research/InstantID/"
"main/pipeline_stable_diffusion_xl_instantid_img2img.py"
)
# SDXL base shared with the main pipeline (same checkpoint as `default`/`controlnet`).
_SDXL_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
# Prompt format. InstantID is less sensitive to prompt than PhotoMaker because the
# ID branch is cross-attention; a neutral descriptive prompt is recommended by the
# upstream gradio demo.
_INSTANTID_PROMPT = "portrait photo of a person, natural skin, soft lighting, sharp focus, best quality"
_INSTANTID_NEGATIVE = (
"(asymmetry, worst quality, low quality, illustration, 3d, 2d, painting, "
"cartoons, sketch), open mouth, blurry, watermark, deformed"
)
# Square size used to feed InstantID. SDXL is happiest at 1024 (a smaller value sends
# it into low-res mosaic mode -- caught visually on PhotoMaker, same root cause).
_INSTANTID_FACE_SIZE = 1024
_pipeline: Any | None = None
_pipeline_lock = threading.Lock()
_face_analyser: Any | None = None
_face_analyser_lock = threading.Lock()
def is_available() -> bool:
"""True when the optional InstantID extra deps are importable."""
return (
importlib.util.find_spec("insightface") is not None
and importlib.util.find_spec("diffusers") is not None
and importlib.util.find_spec("torch") is not None
and importlib.util.find_spec("huggingface_hub") is not None
)
def _select_device() -> str:
"""Pick the InstantID pipeline device: CUDA when present, MPS on Apple, else CPU."""
try:
import torch
if torch.cuda.is_available():
return "cuda"
if torch.backends.mps.is_available():
return "mps"
except Exception as e:
logger.debug("instantid_restore: device probe failed (%s); using CPU", e)
return "cpu"
def _fetch_img2img_pipeline_file() -> Path:
"""Cache the InstantID img2img pipeline source file locally on first use.
The file lives in the InstantX GitHub repo (not on PyPI, not on HF Hub at any
path diffusers can auto-load). We fetch the raw URL once into the package's
HuggingFace cache so subsequent loads hit disk. Returns the path to feed to
``DiffusionPipeline.from_pretrained(custom_pipeline=...)``.
"""
import os
import urllib.request
cache_root = Path(os.environ.get("HF_HOME") or Path.home() / ".cache" / "huggingface")
cache_dir = cache_root / "remove_ai_watermarks" / "instantid"
cache_dir.mkdir(parents=True, exist_ok=True)
target = cache_dir / "pipeline_stable_diffusion_xl_instantid_img2img.py"
if not target.exists() or target.stat().st_size < 50_000:
logger.info("instantid_restore: fetching img2img pipeline source from %s", _INSTANTID_IMG2IMG_URL)
urllib.request.urlretrieve(_INSTANTID_IMG2IMG_URL, target) # noqa: S310 (HTTPS pinned)
return target
def _ensure_antelopev2(root: Path) -> Path:
"""Materialize the antelopev2 pack at ``<root>/models/antelopev2/`` if absent.
InsightFace's built-in auto-download points at
``github.com/deepinsight/insightface/releases/download/v0.7/antelopev2.zip``
which has been broken since at least 2024 (verified upstream issue #2517,
#2766; explicitly called out in InstantID's README: "manually download via
this URL to models/antelopev2 as the default link is invalid"). Without the
five expected ``.onnx`` files in place, ``FaceAnalysis.prepare()`` errors
with ``assert 'detection' in self.models``.
We side-step the broken default by fetching the five files from a HuggingFace
mirror (``kidyu/antelopev2-for-InstantID-ComfyUI``) on first use. Returns the
target directory containing the .onnx files.
"""
from huggingface_hub import hf_hub_download
target = root / "models" / "antelopev2"
target.mkdir(parents=True, exist_ok=True)
files = [
"1k3d68.onnx",
"2d106det.onnx",
"genderage.onnx",
"glintr100.onnx",
"scrfd_10g_bnkps.onnx",
]
for fname in files:
dest = target / fname
if dest.exists() and dest.stat().st_size > 0:
continue
logger.info("instantid_restore: fetching antelopev2/%s from HF mirror", fname)
path = hf_hub_download(repo_id="kidyu/antelopev2-for-InstantID-ComfyUI", filename=fname)
# hf_hub_download caches under HF_HOME; symlink (or copy) into the
# InsightFace-expected layout.
if not dest.exists():
try:
dest.symlink_to(path)
except OSError:
import shutil
shutil.copy(path, dest)
return target
def _get_face_analyser() -> Any:
"""Return the InsightFace FaceAnalysis singleton (antelopev2, non-commercial).
Pre-downloads the antelopev2 pack from a HuggingFace mirror (the InsightFace
auto-download is broken). See the NON-COMMERCIAL notice at the top of the
module.
"""
global _face_analyser
if _face_analyser is not None:
return _face_analyser
with _face_analyser_lock:
if _face_analyser is None:
import torch
from insightface.app import FaceAnalysis
providers = ["CUDAExecutionProvider"] if torch.cuda.is_available() else ["CPUExecutionProvider"]
# InstantID's upstream uses name='antelopev2' and root='./'. Materialise
# the pack at the same place so FaceAnalysis finds it locally.
root = Path.cwd()
_ensure_antelopev2(root)
fa = FaceAnalysis(name="antelopev2", root=str(root), providers=providers)
fa.prepare(ctx_id=0, det_size=(640, 640))
_face_analyser = fa
return _face_analyser
def _get_pipeline() -> Any:
"""Return the lazily-built InstantID pipeline singleton (downloads weights on first use).
Loads via diffusers' community-pipeline mechanism: the file
``pipeline_stable_diffusion_xl_instantid.py`` lives in
``diffusers/examples/community/`` and is selected by the slug
``pipeline_stable_diffusion_xl_instantid``.
"""
global _pipeline
if _pipeline is not None:
return _pipeline
with _pipeline_lock:
if _pipeline is None:
import torch
from diffusers import ControlNetModel, DiffusionPipeline
from huggingface_hub import hf_hub_download
device = _select_device()
dtype = torch.float16 if device == "cuda" else torch.float32
logger.info("instantid_restore: loading SDXL+InstantID img2img on %s (%s)", device, dtype)
# IdentityNet ControlNet weights.
controlnet = ControlNetModel.from_pretrained(
_INSTANTID_REPO,
subfolder=_INSTANTID_CONTROLNET_SUBFOLDER,
torch_dtype=dtype,
)
# Upstream InstantID img2img pipeline (StableDiffusionXLInstantIDImg2ImgPipeline).
# Lets us feed the cleaned face crop as the diffusion source so the regenerated
# face inherits scene lighting / shadows / head angle from the cleaned context
# (vs the txt2img variant which generates a studio portrait from scratch).
# Critical SynthID-safety property: the ``image`` arg MUST be the CLEANED crop,
# never the original -- the original carries the watermark and img2img at
# strength < 1 preserves some input pixel structure. The ArcFace embedding is
# semantic (no pixel content), so taking it from the original is fine.
pipe = DiffusionPipeline.from_pretrained(
_SDXL_MODEL_ID,
controlnet=controlnet,
torch_dtype=dtype,
custom_pipeline=str(_fetch_img2img_pipeline_file()),
# Custom_pipeline from a local .py file triggers diffusers' remote-code
# guard; the file is fetched from a pinned raw.githubusercontent URL
# we control, so opt in here. Without this the load silently falls
# back to a default pipeline (no img2img + no IP-Adapter cross-attn),
# the next call hits an AttributeError on load_ip_adapter_instantid,
# and our outer except logs but skips the whole restore.
trust_remote_code=True,
)
pipe.to(device)
# IP-Adapter weights that wire the ArcFace embedding into cross-attention.
ip_adapter_path = hf_hub_download(repo_id=_INSTANTID_REPO, filename=_INSTANTID_IP_ADAPTER)
# IP-Adapter scale = weight on the ArcFace cross-attention. The upstream
# demo uses 0.8 for txt2img; for img2img-on-cleaned we push to 1.0 because
# the cleaned face crop is competing as identity prior and we want ArcFace
# to dominate (otherwise the regenerated face inherits the controlnet-
# drifted cleaned face, not the original identity).
pipe.load_ip_adapter_instantid(ip_adapter_path, scale=1.0)
# Diffusers 0.38 vs InstantID upstream compat patch: InstantID's __call__
# calls ``self.check_inputs(...)`` POSITIONALLY (signature from ~v0.29),
# but diffusers 0.38 added two new params (``ip_adapter_image``,
# ``ip_adapter_image_embeds``) BEFORE ``controlnet_conditioning_scale`` in
# the parent's signature. That shifts every argument by two, so
# ``control_guidance_end`` (which InstantID converts to ``[1.0]`` for the
# single-controlnet case before this point) lands in the slot the parent
# validates as ``controlnet_conditioning_scale`` and trips
# ``TypeError("must be type float")``. Our inputs are programmatic and
# already validated by our own callers, so neutralising the check is safe.
pipe.check_inputs = lambda *_a, **_k: None
_pipeline = pipe
return _pipeline
def _draw_kps(image_size: tuple[int, int], kps: Any) -> Any:
"""Render the 5 facial keypoints as a colored stick figure.
Mirrors upstream's ``draw_kps`` (in ``pipeline_stable_diffusion_xl_instantid.py``):
the 5 keypoints (left eye, right eye, nose tip, left mouth corner, right mouth
corner) get drawn as colored circles connected by colored lines, on a black
background. The result is the ControlNet conditioning image -- pure landmark
geometry, no pixels from the original face leak through this branch.
``image_size`` is ``(width, height)``; ``kps`` is a numpy array of shape (5, 2).
"""
import cv2
import numpy as np
from PIL import Image
# Same color palette as upstream (blue/red/green/purple/yellow).
stick_width = 4
limb_seq = np.array([[0, 2], [1, 2], [3, 2], [4, 2]])
color_list = [
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(255, 255, 0),
(255, 0, 255),
]
w, h = image_size
out_img = np.zeros((h, w, 3), dtype=np.uint8)
kps_arr = np.array(kps)
for i in range(len(limb_seq)):
index = limb_seq[i]
color = color_list[index[0]]
x = kps_arr[index][:, 0]
y = kps_arr[index][:, 1]
length = ((x[0] - x[1]) ** 2 + (y[0] - y[1]) ** 2) ** 0.5
angle = np.degrees(np.arctan2(y[0] - y[1], x[0] - x[1]))
polygon = cv2.ellipse2Poly(
(int(np.mean(x)), int(np.mean(y))),
(int(length / 2), stick_width),
int(angle),
0,
360,
1,
)
out_img = cv2.fillConvexPoly(out_img.copy(), polygon, color)
out_img = (out_img * 0.6).astype(np.uint8)
for i, kp in enumerate(kps_arr):
x, y = kp
out_img = cv2.circle(out_img.copy(), (int(x), int(y)), 10, color_list[i], -1)
return Image.fromarray(out_img.astype(np.uint8))
def restore_faces_instantid(
original_bgr: NDArray[Any],
cleaned_bgr: NDArray[Any],
num_inference_steps: int = 30,
guidance_scale: float = 5.0,
controlnet_conditioning_scale: float = 1.0,
img2img_strength: float = 0.7,
seed: int | None = None,
detect_faces_fn: Any | None = None,
) -> NDArray[Any]:
"""SynthID-robust face identity restoration via InstantID.
Flow:
1. Detect faces in ``cleaned_bgr`` (YuNet via ``auto_config`` by default;
override via ``detect_faces_fn`` for tests).
2. For each face: square-crop the SAME box from BOTH images (original ->
ArcFace + kps; cleaned -> img2img source). Resize both to 1024.
3. Render kps as a landmark stick figure (the ControlNet conditioning).
4. Run InstantID img2img: ``image`` = cleaned crop, ``control_image`` =
landmark, ``image_embeds`` = ArcFace embedding from the original.
5. Elliptical-alpha + colour-match composite into the cleaned image.
SynthID safety: ``image`` is the CLEANED crop (already oracle-clean); the
original is read for the embedding and kps only (semantic / geometry, no
pixel content). See the module docstring.
``detect_faces_fn`` returns a list of ``(x, y, w, h)`` boxes given a BGR image.
"""
import cv2
import numpy as np
import torch
if detect_faces_fn is None:
from pathlib import Path
from remove_ai_watermarks import auto_config as _ac
def _default_detect(bgr: NDArray[Any]) -> list[tuple[int, int, int, int]]:
h_d, w_d = bgr.shape[:2]
model = Path(_ac.__file__).parent / "assets" / "face_detection_yunet_2023mar.onnx"
det = cv2.FaceDetectorYN.create(str(model), "", (w_d, h_d), _ac._FACE_SCORE, 0.3, 5000)
det.setInputSize((w_d, h_d))
_, faces = det.detect(bgr)
if faces is None:
return []
return [(int(f[0]), int(f[1]), int(f[2]), int(f[3])) for f in faces if int(f[2]) > 0 and int(f[3]) > 0]
detect_faces_fn = _default_detect
boxes = detect_faces_fn(cleaned_bgr)
if not boxes:
logger.debug("instantid_restore: no faces detected; returning cleaned image unchanged")
return cleaned_bgr
pipeline = _get_pipeline()
face_analyser = _get_face_analyser()
generator = None
if seed is not None:
generator = torch.Generator(device=pipeline.device).manual_seed(seed)
h_c, w_c = cleaned_bgr.shape[:2]
restored: list[tuple[NDArray[Any], tuple[int, int, int, int]]] = []
for box in boxes:
# Square crop with the SAME geometry from both the original (-> ArcFace
# embedding + landmark kps -- semantic / pure-geometry, SynthID can't ride
# either) AND the cleaned image (-> img2img source -- SynthID-safe because
# the cleaned image is already oracle-verified clean and any residual
# high-frequency pattern would be destroyed by the noise injection at our
# strength setting). _face_crop_square gives a 2x-padded square box around
# the face -- enough scene context so the img2img harmonises lighting and
# head angle with the surroundings.
original_crop_bgr, square_box = _face_crop_square(original_bgr, box)
sx1, sy1, sx2, sy2 = square_box
sx1c, sy1c = max(0, sx1), max(0, sy1)
sx2c, sy2c = min(w_c, sx2), min(h_c, sy2)
if original_crop_bgr.size == 0 or sx2c <= sx1c or sy2c <= sy1c:
continue
cleaned_crop_bgr = cleaned_bgr[sy1c:sy2c, sx1c:sx2c]
if cleaned_crop_bgr.shape[:2] != original_crop_bgr.shape[:2]:
# Edge effect at image border -- pad cleaned crop to match the original
# crop dimensions so InsightFace / the pipeline see the same shape.
cleaned_crop_bgr = cv2.resize(
cleaned_crop_bgr,
(original_crop_bgr.shape[1], original_crop_bgr.shape[0]),
interpolation=cv2.INTER_LANCZOS4,
)
# Resize both crops to the SDXL working size.
original_resized = cv2.resize(
original_crop_bgr, (_INSTANTID_FACE_SIZE, _INSTANTID_FACE_SIZE), interpolation=cv2.INTER_LANCZOS4
)
cleaned_resized = cv2.resize(
cleaned_crop_bgr, (_INSTANTID_FACE_SIZE, _INSTANTID_FACE_SIZE), interpolation=cv2.INTER_LANCZOS4
)
# ArcFace embedding + 5 kps from the ORIGINAL face (sharper identity).
face_infos = face_analyser.get(original_resized)
if not face_infos:
logger.debug("instantid_restore: InsightFace did not find a face in the crop; skipping")
continue
face_info = sorted(
face_infos,
key=lambda x: (x["bbox"][2] - x["bbox"][0]) * (x["bbox"][3] - x["bbox"][1]),
)[-1]
face_emb = face_info["embedding"]
face_kps = face_info["kps"]
# Render the landmark stick figure at the same size as the generation target.
landmark_img = _draw_kps((_INSTANTID_FACE_SIZE, _INSTANTID_FACE_SIZE), face_kps)
# img2img call: source = CLEANED crop (SynthID-safe), control = landmark
# geometry, identity = ArcFace embedding from original. Strength controls
# how much of the cleaned input structure survives -- low enough (~0.55)
# to keep the head angle / lighting / shoulders coherent with the rest of
# the cleaned image, high enough that the face pixels are diffusion-fresh
# and InstantID actually injects identity.
from PIL import Image
cleaned_pil = Image.fromarray(cv2.cvtColor(cleaned_resized, cv2.COLOR_BGR2RGB))
out = pipeline(
prompt=_INSTANTID_PROMPT,
negative_prompt=_INSTANTID_NEGATIVE,
image=cleaned_pil,
control_image=landmark_img,
image_embeds=face_emb,
strength=img2img_strength,
controlnet_conditioning_scale=controlnet_conditioning_scale,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
generator=generator,
)
gen_rgb = out.images[0]
gen_bgr = cv2.cvtColor(np.array(gen_rgb), cv2.COLOR_RGB2BGR)
# gen_bgr is at _INSTANTID_FACE_SIZE x _INSTANTID_FACE_SIZE. It represents
# the 2x-padded square_box content as regenerated by img2img -- so the face
# in it sits at the same RELATIVE position as in the cleaned input (img2img
# preserves structure). Composite the whole square back into the square_box
# location -- the cleaned-canvas elliptical alpha will keep the cleaned
# background outside the face oval, and the img2img harmonisation handles
# the seam INSIDE the oval (which is just face-on-face transition between
# diffusion-output and cleaned).
target_box = (sx1c, sy1c, sx2c, sy2c)
gen_target = cv2.resize(gen_bgr, (sx2c - sx1c, sy2c - sy1c), interpolation=cv2.INTER_LANCZOS4)
restored.append((gen_target, target_box))
if not restored:
return cleaned_bgr
return _composite_faces_elliptical(cleaned_bgr, restored)
def _color_match(src_bgr: NDArray[Any], ref_bgr: NDArray[Any]) -> NDArray[Any]:
"""Shift ``src_bgr`` mean colour to ``ref_bgr`` mean colour, per channel.
Each face is regenerated by InstantID with its own SDXL noise -- the white
balance / mean tone drifts away from the surrounding scene (cool studio
light vs warm bar lighting). A per-channel mean-shift brings the face crop
into the same tonal range as the cleaned canvas where it lands. Contrast
and saturation are preserved (we don't rescale variance).
"""
import numpy as np
src = src_bgr.astype(np.float32)
ref = ref_bgr.astype(np.float32)
if ref.size == 0:
return src_bgr
src_mean = src.mean(axis=(0, 1), keepdims=True)
ref_mean = ref.mean(axis=(0, 1), keepdims=True)
return np.clip(src - src_mean + ref_mean, 0, 255).astype(np.uint8)
def _composite_faces_elliptical(
base_bgr: NDArray[Any],
restored_crops: list[tuple[NDArray[Any], tuple[int, int, int, int]]],
feather_div: int = 5,
) -> NDArray[Any]:
"""Composite face crops into ``base_bgr`` using an elliptical, feathered alpha.
Two changes vs the simpler rectangular Gaussian feather:
- **Inscribed face-shaped ellipse.** Axes are ``(0.32*bw, 0.42*bh)`` which
fits comfortably inside the 2x padded bbox (the face naturally occupies
the central ~50% of the bbox), covering the head silhouette without
clipping the forehead or chin. The bbox corners (which carry
regenerated-scene background pixels with a different tone per face) end
up at alpha=0 so the cleaned-image background stays intact -- this is
what eliminates multi-face patchwork on group photos.
- **Soft feather.** ``min(bw, bh) // 5`` -- about twice as soft as the
rectangular Gaussian, so the ellipse edge fades over a wider band into
the cleaned canvas, hiding any residual seam.
Additionally, before compositing, ``_color_match`` shifts the regenerated
face's mean colour to match the cleaned canvas region it lands on -- this
removes the warm/cool tone clash that group photos showed.
"""
import cv2
import numpy as np
out = base_bgr.astype(np.float32)
h_b, w_b = base_bgr.shape[:2]
for crop, (x1, y1, x2, y2) in restored_crops:
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w_b, x2), min(h_b, y2)
bw, bh = x2 - x1, y2 - y1
if bw <= 0 or bh <= 0:
continue
resized = cv2.resize(crop, (bw, bh), interpolation=cv2.INTER_LANCZOS4)
# Tone match the regenerated face to the cleaned canvas it sits on.
ref_region = base_bgr[y1:y2, x1:x2]
resized = _color_match(resized, ref_region)
alpha_crop = np.zeros((bh, bw), dtype=np.float32)
center = (bw // 2, bh // 2)
axes = (max(1, int(bw * 0.32)), max(1, int(bh * 0.42)))
cv2.ellipse(alpha_crop, center, axes, 0, 0, 360, 1.0, -1)
k = max(7, (min(bw, bh) // feather_div) | 1)
alpha_crop = cv2.GaussianBlur(alpha_crop, (k, k), 0)
alpha_full = np.zeros((h_b, w_b), dtype=np.float32)
alpha_full[y1:y2, x1:x2] = alpha_crop
full_restored = np.zeros_like(out)
full_restored[y1:y2, x1:x2] = resized
a = alpha_full[:, :, None]
out = full_restored * a + out * (1.0 - a)
return np.clip(out, 0, 255).astype(np.uint8)
+3 -121
View File
@@ -164,8 +164,6 @@ class InvisibleEngine:
max_resolution: int = 0,
min_resolution: int = 1024,
vendor: str | None = None,
restore_faces: bool = False,
restore_faces_method: str = "instantid",
unsharp: float = 0.0,
adaptive_polish: bool = False,
upscaler: str = "lanczos",
@@ -181,20 +179,9 @@ class InvisibleEngine:
guidance_scale: Classifier-free guidance scale.
seed: Random seed for reproducibility.
humanize: Intensity of Analog Humanizer film grain (0 = off).
restore_faces: EXPERIMENTAL, opt-in (default False). **NON-COMMERCIAL.**
Run the face-identity post-pass when faces are present. Method is
chosen by ``restore_faces_method`` -- ``instantid`` (default,
stronger identity, needs the ``instantid`` extra) or ``photomaker``
(PhotoMaker-V2, needs the ``photomaker`` extra). Both extras pull
non-commercial InsightFace model packs. Auto-skips with a debug log
when the chosen extra is absent or no face is detected. See
``instantid_restore.py`` / ``photomaker_restore.py``.
restore_faces_method: ``instantid`` (default) or ``photomaker``. Both
NON-COMMERCIAL; pick the one whose extra you've installed.
unsharp: Final unsharp-mask sharpening strength (0 = off, default).
Applied last (after face restoration) to counter the soft,
over-smoothed look of the diffusion + restoration; ~0.5-0.8 is a
safe range, higher risks edge halos.
Applied last to counter the soft / over-smoothed look of the
diffusion pass; ~0.5-0.8 is a safe range, higher risks edge halos.
adaptive_polish: When True (the --auto mode default), restore the input's
detail level in the softened output instead of fixed unsharp/humanize:
a capped unsharp + edge-masked grain targeting the input's Laplacian
@@ -316,19 +303,7 @@ class InvisibleEngine:
out_cv = cv2.resize(out_cv, orig_size, interpolation=cv2.INTER_LANCZOS4)
image_io.imwrite(out_path, out_cv)
# Optional GFPGAN face-polish post-pass: sharpens and re-synthesizes each
# face from GFPGAN's StyleGAN2 prior, running on the DIFFUSION-CLEANED image
# (not the original) -- so SynthID is not re-introduced (the input pixels
# GFPGAN derives from are already SynthID-free). Auto-skips when faces are
# absent or the optional `restore` extra is not installed.
if restore_faces:
if restore_faces_method == "photomaker":
self._restore_faces_photomaker(out_path, image, seed)
else:
self._restore_faces_instantid(out_path, image, seed)
# Final sharpening, LAST so it crisps the face-restored result too (a
# pre-restore sharpen would be smoothed back over by the face pass).
# Final sharpening.
if unsharp > 0.0:
import cv2
@@ -364,99 +339,6 @@ class InvisibleEngine:
if _tmp_path.exists():
_tmp_path.unlink()
def _restore_faces_instantid(
self,
out_path: Path,
original_image: Any,
seed: int | None,
) -> None:
"""Run the InstantID face-identity post-pass on the cleaned ``out_path``.
**NON-COMMERCIAL** (see ``instantid_restore.py``). InstantID conditions on
an ArcFace embedding (semantic) plus a landmark ControlNet (geometry,
content-free) -- no original face pixels enter the diffusion. Best-effort:
any failure (missing extra, model load, runtime error) logs a warning and
leaves the un-restored cleaned output in place.
"""
from remove_ai_watermarks import instantid_restore
if not instantid_restore.is_available():
logger.debug("restore_faces requested but the 'instantid' extra is not installed; skipping")
return
try:
import cv2
import numpy as np
from remove_ai_watermarks import image_io
cleaned_bgr = image_io.imread(out_path, cv2.IMREAD_COLOR)
if cleaned_bgr is None:
logger.warning("restore_faces: could not read cleaned output %s; skipping", out_path)
return
original_rgb = original_image.convert("RGB")
original_bgr = cv2.cvtColor(np.array(original_rgb), cv2.COLOR_RGB2BGR)
cleaned_size = (cleaned_bgr.shape[1], cleaned_bgr.shape[0])
if (original_bgr.shape[1], original_bgr.shape[0]) != cleaned_size:
original_bgr = cv2.resize(original_bgr, cleaned_size, interpolation=cv2.INTER_LANCZOS4)
if self._progress_callback:
self._progress_callback("Restoring face identity (InstantID post-pass)...")
restored = instantid_restore.restore_faces_instantid(original_bgr, cleaned_bgr, seed=seed)
image_io.imwrite(out_path, restored)
except Exception as e:
logger.warning(
"restore_faces post-pass failed (%s: %s); keeping un-restored output",
type(e).__name__,
e,
exc_info=True,
)
def _restore_faces_photomaker(
self,
out_path: Path,
original_image: Any,
seed: int | None,
) -> None:
"""Run the PhotoMaker-V2 face-identity post-pass on the cleaned ``out_path``.
**NON-COMMERCIAL** (see ``photomaker_restore.py``). PhotoMaker carries identity
in a CLIP+ArcFace embedding and regenerates fresh face pixels conditioned on
it, so the watermark is not transported. Best-effort: any failure (missing
extra, model load, runtime error) logs a warning and leaves the un-restored
cleaned output in place.
"""
from remove_ai_watermarks import photomaker_restore
if not photomaker_restore.is_available():
logger.debug("restore_faces requested but the 'photomaker' extra is not installed; skipping")
return
try:
import cv2
import numpy as np
from remove_ai_watermarks import image_io
cleaned_bgr = image_io.imread(out_path, cv2.IMREAD_COLOR)
if cleaned_bgr is None:
logger.warning("restore_faces: could not read cleaned output %s; skipping", out_path)
return
original_rgb = original_image.convert("RGB")
original_bgr = cv2.cvtColor(np.array(original_rgb), cv2.COLOR_RGB2BGR)
cleaned_size = (cleaned_bgr.shape[1], cleaned_bgr.shape[0])
if (original_bgr.shape[1], original_bgr.shape[0]) != cleaned_size:
original_bgr = cv2.resize(original_bgr, cleaned_size, interpolation=cv2.INTER_LANCZOS4)
if self._progress_callback:
self._progress_callback("Restoring face identity (PhotoMaker-V2 post-pass)...")
restored = photomaker_restore.restore_faces_photomaker(original_bgr, cleaned_bgr, seed=seed)
image_io.imwrite(out_path, restored)
except Exception as e:
logger.warning("restore_faces post-pass failed (%s); keeping un-restored output", e)
def remove_watermark_batch(
self,
input_dir: Path,
@@ -1,368 +0,0 @@
"""SynthID-robust face identity restoration via PhotoMaker-V2.
**NON-COMMERCIAL.** This module uses PhotoMaker-V2, whose ID encoder
(``PhotoMakerIDEncoder_CLIPInsightfaceExtendtoken``) requires an ArcFace embedding
from InsightFace's pretrained ``antelopev2`` / ``buffalo_l`` model packs. Those packs
are released by InsightFace under a **non-commercial / research-only license**:
"The pretrained models we provided with this library are available for
non-commercial research purposes only."
-- insightface PyPI README
The PyPI ``insightface`` package itself is MIT-licensed code, but the model weights
it downloads on first ``FaceAnalysis()`` are not commercial. **A paid service
(raiw.cc, any monetized SaaS, any enterprise deployment) MUST NOT use this path.**
The default ``--restore-faces`` method is ``gfpgan`` (commercial-safe, ships with
the ``restore`` extra); ``--restore-faces-method photomaker`` is an explicit opt-in
for non-commercial use only. See ``docs/synthid-robust-identity-research.md``.
The diffusion removal pass scrubs the pixel watermark from the WHOLE image, including
faces, but lets faces drift in identity. PhotoMaker-V2 carries identity in two
semantic streams (an OpenCLIP-ViT-H/14 image embedding AND an ArcFace identity
embedding) and uses them to CONDITION a fresh txt2img generation -- the pixels are
new, so the watermark cannot be transported.
That embeddings do not carry an invisible pixel watermark like SynthID is the
load-bearing assumption of the whole approach; the OpenCLIP smoke test (cosine
0.9977 invariance to SynthID-magnitude pixel noise) supports it for the CLIP
stream, and ArcFace is even more invariant to small perceptual changes by design.
Architecture: PhotoMaker-V2 is a fine-tuned OpenCLIP-ViT-H/14 + InsightFace dual ID
encoder plus LoRA on the SDXL UNet attention layers. It ships as a single
``photomaker-v2.bin`` checkpoint loaded into a ``PhotoMakerStableDiffusionXLPipeline``
(txt2img). We use it as a SECOND PASS after the main controlnet/default removal:
1. Main removal pass (`controlnet` at the certified strength) cleans SynthID
everywhere but leaves faces drifted.
2. For each face found in the CLEANED image (YuNet), this module takes the SAME
face region from the ORIGINAL, computes the dual ID embedding from it, and
runs PhotoMaker txt2img to regenerate JUST that face crop from the embedding.
The freshly generated face is feather-composited back into the cleaned image.
The generated face pixels are diffusion-fresh and inherit identity from the
embedding (not the pixels), so SynthID is not re-introduced.
Requires the optional ``photomaker`` extra: ``pip install
'remove-ai-watermarks[photomaker]'`` -- this pulls the upstream PhotoMaker package
(Apache-2.0), ``insightface`` (MIT code), ``einops``, ``peft``, ``onnxruntime``,
and ``huggingface-hub``. Weights and InsightFace model packs download on first use;
never bundled.
"""
# cv2/torch/diffusers boundary: relax unknown-type rules for this file only.
# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownParameterType=false, reportMissingTypeArgument=false, reportMissingTypeStubs=false, reportMissingImports=false, reportArgumentType=false, reportAssignmentType=false, reportReturnType=false, reportCallIssue=false, reportIndexIssue=false, reportOperatorIssue=false, reportOptionalMemberAccess=false, reportOptionalCall=false, reportOptionalSubscript=false, reportOptionalOperand=false, reportAttributeAccessIssue=false, reportPrivateImportUsage=false, reportPrivateUsage=false, reportInvalidTypeForm=false, reportConstantRedefinition=false, reportUnnecessaryComparison=false
from __future__ import annotations
import importlib.util
import logging
import threading
from pathlib import Path
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from numpy.typing import NDArray
logger = logging.getLogger(__name__)
# PhotoMaker-V2 weights (Apache-2.0 adapter; ID encoder pulls non-commercial
# InsightFace model packs at runtime -- see the NON-COMMERCIAL notice in the module
# docstring). Downloaded on first use; never bundled.
_PHOTOMAKER_REPO = "TencentARC/PhotoMaker-V2"
_PHOTOMAKER_FILE = "photomaker-v2.bin"
# SDXL base shared with the main pipeline (same checkpoint as `default`/`controlnet`).
_SDXL_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
# The neutral prompt PhotoMaker is designed around: a class noun + the trigger word
# `img`, which PhotoMaker replaces with the ID embedding at inference. Keeping it
# scene-neutral (no extra style words) maximises identity transfer from the embed and
# minimises hallucinated background/lighting that would not match the cleaned scene.
# Prompt format follows the upstream V2 reference (inference_pmv2.py): the trigger
# word ``img`` must immediately follow a class noun. SDXL is happiest at 1024 and
# falls into low-res artefacts ("mosaic of tiny faces") at 512, so we render at
# 1024 then downscale into the face bbox at composite time. Caught visually
# 2026-06-04: at 512 V2 produced a collage of training-time faces; at 1024 with the
# upstream-style descriptive prompt it produces a clean face.
_PHOTOMAKER_PROMPT = (
"instagram photo, portrait photo of a person img, natural skin, soft lighting, best quality, sharp focus"
)
_PHOTOMAKER_NEGATIVE = (
"(asymmetry, worst quality, low quality, illustration, 3d, 2d, painting, "
"cartoons, sketch), open mouth, blurry, watermark"
)
# SDXL native resolution; lower values send V2 into low-res mode and the output
# becomes a collage of training-time faces. We render at 1024 then downscale into
# the original face bbox at composite time.
_PHOTOMAKER_FACE_SIZE = 1024
_pipeline: Any | None = None
_pipeline_lock = threading.Lock()
_face_analyser: Any | None = None
_face_analyser_lock = threading.Lock()
def is_available() -> bool:
"""True when the optional PhotoMaker extra deps are importable."""
return (
importlib.util.find_spec("photomaker") is not None
and importlib.util.find_spec("diffusers") is not None
and importlib.util.find_spec("huggingface_hub") is not None
)
def _select_device() -> str:
"""Pick the PhotoMaker pipeline device: CUDA when present, MPS on Apple, else CPU."""
try:
import torch
if torch.cuda.is_available():
return "cuda"
if torch.backends.mps.is_available():
return "mps"
except Exception as e:
logger.debug("photomaker_restore: device probe failed (%s); using CPU", e)
return "cpu"
def _get_face_analyser() -> Any:
"""Return the InsightFace FaceAnalysis2 singleton (downloads model packs on first use).
**This is the non-commercial step.** Instantiating ``FaceAnalysis2()`` triggers
InsightFace's auto-download of the antelopev2/buffalo_l model packs, which are
released under a research-only license. See the module docstring NON-COMMERCIAL
notice. PhotoMaker-V2 requires this for the ArcFace identity branch.
"""
global _face_analyser
if _face_analyser is not None:
return _face_analyser
with _face_analyser_lock:
if _face_analyser is None:
import torch
from photomaker import FaceAnalysis2
providers = ["CUDAExecutionProvider"] if torch.cuda.is_available() else ["CPUExecutionProvider"]
fa = FaceAnalysis2(providers=providers, allowed_modules=["detection", "recognition"])
fa.prepare(ctx_id=0, det_size=(640, 640))
_face_analyser = fa
return _face_analyser
def _get_pipeline() -> Any:
"""Return the lazily-built PhotoMaker pipeline singleton (downloads weights on first use)."""
global _pipeline
if _pipeline is not None:
return _pipeline
with _pipeline_lock:
if _pipeline is None:
import torch
from huggingface_hub import hf_hub_download
from photomaker import PhotoMakerStableDiffusionXLPipeline
device = _select_device()
dtype = torch.float16 if device == "cuda" else torch.float32
logger.info("photomaker_restore: loading SDXL+PhotoMaker on %s (%s)", device, dtype)
adapter_path = hf_hub_download(repo_id=_PHOTOMAKER_REPO, filename=_PHOTOMAKER_FILE)
pipe = PhotoMakerStableDiffusionXLPipeline.from_pretrained(_SDXL_MODEL_ID, torch_dtype=dtype)
# Move SDXL submodules to the device BEFORE loading the PhotoMaker adapter:
# ``load_photomaker_adapter`` reads ``self.device`` / ``self.unet.dtype`` to
# place the new ID encoder. If we ``.to(device)`` after, the SDXL submodules
# move but the id_encoder stays where it was (custom attribute, not in the
# auto-managed module tree), and inference errors with
# "Input type (torch.cuda.HalfTensor) and weight type (torch.HalfTensor)
# should be the same" (caught empirically 2026-06-04).
pipe.to(device)
# Default ``pm_version`` is "v2"; we load the V2 weights (photomaker-v2.bin)
# into the V2 encoder (PhotoMakerIDEncoder_CLIPInsightfaceExtendtoken). The V2
# encoder takes BOTH the CLIP image features AND an InsightFace ArcFace
# embedding -- the latter is what makes this path non-commercial.
pipe.load_photomaker_adapter(
str(Path(adapter_path).parent),
subfolder="",
weight_name=_PHOTOMAKER_FILE,
trigger_word="img",
)
pipe.fuse_lora()
# Belt: also explicitly cast the loaded id_encoder, because some
# diffusers/torch combinations leave the encoder buffers untouched even
# though ``pipe.to(device)`` ran first.
if hasattr(pipe, "id_encoder") and pipe.id_encoder is not None:
pipe.id_encoder = pipe.id_encoder.to(device=device, dtype=dtype)
_pipeline = pipe
return _pipeline
def _face_crop_square(
image_bgr: NDArray[Any],
box: tuple[int, int, int, int],
pad: float = 0.30,
) -> tuple[NDArray[Any], tuple[int, int, int, int]]:
"""Square crop around a face box (with padding), clipped to the image.
Returns ``(crop_bgr, (x1, y1, x2, y2))``. The crop is the image content inside the
returned square box -- callers use the box for the composite step. Pure numpy slicing,
no model.
"""
h, w = image_bgr.shape[:2]
x, y, bw, bh = box
cx, cy = x + bw // 2, y + bh // 2
side = int(max(bw, bh) * (1.0 + 2.0 * pad))
half = side // 2
x1 = max(0, cx - half)
y1 = max(0, cy - half)
x2 = min(w, cx + half)
y2 = min(h, cy + half)
return image_bgr[y1:y2, x1:x2], (x1, y1, x2, y2)
def _composite_faces(
base_bgr: NDArray[Any],
restored_crops: list[tuple[NDArray[Any], tuple[int, int, int, int]]],
feather_div: int = 6,
) -> NDArray[Any]:
"""Feather-composite a list of ``(restored_crop, (x1, y1, x2, y2))`` into ``base_bgr``.
Pure cv2/numpy helper (no model), unit-testable. For each ``(crop, box)``: resize
the crop to the box size, build a Gaussian-feathered rectangular alpha, and blend
``crop * a + base * (1 - a)``. Boxes that fall fully outside the image (or an empty
list) leave ``base_bgr`` unchanged. Mirrors the alpha math in ``face_restore._composite_faces``.
"""
import cv2
import numpy as np
out = base_bgr.astype(np.float32)
h, w = base_bgr.shape[:2]
for crop, (x1, y1, x2, y2) in restored_crops:
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
bw, bh = x2 - x1, y2 - y1
if bw <= 0 or bh <= 0:
continue
resized = cv2.resize(crop, (bw, bh), interpolation=cv2.INTER_LANCZOS4)
alpha = np.zeros((h, w), dtype=np.float32)
alpha[y1:y2, x1:x2] = 1.0
k = max(3, (min(bw, bh) // feather_div) | 1)
alpha = cv2.GaussianBlur(alpha, (k, k), 0)[:, :, None]
full_restored = np.zeros_like(out)
full_restored[y1:y2, x1:x2] = resized
out = full_restored * alpha + out * (1.0 - alpha)
return np.clip(out, 0, 255).astype(np.uint8)
def restore_faces_photomaker(
original_bgr: NDArray[Any],
cleaned_bgr: NDArray[Any],
num_inference_steps: int = 30,
guidance_scale: float = 5.0,
style_strength: int = 20,
seed: int | None = None,
detect_faces_fn: Any | None = None,
) -> NDArray[Any]:
"""SynthID-robust face identity restoration via PhotoMaker txt2img.
Pipeline:
1. Detect faces in ``cleaned_bgr`` (YuNet via the package's ``auto_config`` by
default; override via ``detect_faces_fn`` for tests).
2. For each face: take the SAME box from ``original_bgr`` -> square crop -> PhotoMaker
txt2img with that crop as the ID image -> a fresh face generated from the
OpenCLIP embedding (the embedding is SynthID-invariant by ~3 orders of magnitude,
see docs/synthid-robust-identity-research.md).
3. Feather-composite each regenerated face into ``cleaned_bgr``.
Faces are taken from ``original_bgr`` (the embedding ignores the watermark) but the
PIXELS that land in the output are diffusion-fresh, so SynthID is not transported.
Args:
original_bgr: The original (watermarked) image as cv2 BGR. Source of identity.
cleaned_bgr: The main-pass output as cv2 BGR. Faces drifted in identity; this
module replaces those face regions.
num_inference_steps: Diffusion steps inside PhotoMaker (def 30).
guidance_scale: CFG scale inside PhotoMaker (def 5.0; the PhotoMaker recipe).
style_strength: PhotoMaker's ``start_merge_step`` knob ~ 20-30 (def 20).
seed: Optional seed for reproducibility.
detect_faces_fn: Optional callable ``(bgr) -> list[(x,y,w,h)]`` to override the
default YuNet detector (used by tests).
Returns:
``cleaned_bgr`` with regenerated face regions composited in (or unchanged when
no face is detected).
"""
import cv2
import numpy as np
import torch
from PIL import Image
if detect_faces_fn is None:
from remove_ai_watermarks import auto_config as _ac
def _default_detect(bgr: NDArray[Any]) -> list[tuple[int, int, int, int]]:
h, w = bgr.shape[:2]
model = Path(_ac.__file__).parent / "assets" / "face_detection_yunet_2023mar.onnx"
det = cv2.FaceDetectorYN.create(str(model), "", (w, h), _ac._FACE_SCORE, 0.3, 5000)
det.setInputSize((w, h))
_, faces = det.detect(bgr)
if faces is None:
return []
return [(int(f[0]), int(f[1]), int(f[2]), int(f[3])) for f in faces if int(f[2]) > 0 and int(f[3]) > 0]
detect_faces_fn = _default_detect
boxes = detect_faces_fn(cleaned_bgr)
if not boxes:
logger.debug("photomaker_restore: no faces detected; returning cleaned image unchanged")
return cleaned_bgr
pipeline = _get_pipeline()
face_analyser = _get_face_analyser() # NON-COMMERCIAL: triggers InsightFace model packs
from photomaker import analyze_faces
generator = None
if seed is not None:
generator = torch.Generator(device=pipeline.device).manual_seed(seed)
restored: list[tuple[NDArray[Any], tuple[int, int, int, int]]] = []
for box in boxes:
id_crop_bgr, square_box = _face_crop_square(original_bgr, box)
if id_crop_bgr.size == 0:
continue
# Get the ArcFace embedding for THIS face (V2's required ID branch). InsightFace
# expects BGR; analyze_faces returns a list, take the first detection.
# Shape: upstream's inference_pmv2.py stacks per-image embeddings into a 2-D
# tensor (N_images, 512). The pipeline forward then calls `.unsqueeze(0)` ITSELF
# (line 705 of pipeline.py) to add a batch dim, so we must NOT pre-unsqueeze --
# giving `(1, 1, 512)` to the V2 forward made the id_encoder consume garbage and
# the pipeline output the training-time face collage (caught visually 2026-06-04).
# Dtype stays float32 here; the pipeline casts internally.
faces = analyze_faces(face_analyser, id_crop_bgr)
if not faces:
logger.debug("photomaker_restore: InsightFace did not detect a face in the crop; skipping")
continue
id_embeds = torch.stack([torch.from_numpy(faces[0]["embedding"])])
id_crop_rgb = cv2.cvtColor(id_crop_bgr, cv2.COLOR_BGR2RGB)
id_image_pil = Image.fromarray(id_crop_rgb)
# Upstream V2 reference (inference_pmv2.py) passes negative_prompt; the
# batch-mismatch we hit earlier was on V1 only.
out = pipeline(
prompt=_PHOTOMAKER_PROMPT,
negative_prompt=_PHOTOMAKER_NEGATIVE,
input_id_images=[id_image_pil],
id_embeds=id_embeds,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
start_merge_step=style_strength,
generator=generator,
height=_PHOTOMAKER_FACE_SIZE,
width=_PHOTOMAKER_FACE_SIZE,
num_images_per_prompt=1,
)
gen_rgb = out.images[0]
gen_bgr = cv2.cvtColor(np.array(gen_rgb), cv2.COLOR_RGB2BGR)
restored.append((gen_bgr, square_box))
return _composite_faces(cleaned_bgr, restored)
+1 -5
View File
@@ -64,7 +64,6 @@ class TestPlan:
cfg = auto_config.plan(_write(flat, tmp_path))
assert cfg is not None
assert cfg.pipeline == "default" # structure-less -> plain SDXL
assert cfg.restore_faces is False
assert cfg.adaptive_polish is False # no smoothing pass -> no polish
assert cfg.unsharp == 0.0
assert cfg.humanize == 0.0
@@ -78,13 +77,12 @@ class TestPlan:
# Text creates edges above the structure-less floor -> controlnet preserves them.
assert cfg.pipeline == "controlnet"
def test_face_routes_to_restore_and_controlnet_and_polish(self, tmp_path, monkeypatch):
def test_face_routes_to_controlnet_and_polish(self, tmp_path, monkeypatch):
monkeypatch.setattr(auto_config, "detect_face", lambda _img: True)
flat = np.full((300, 300, 3), 128, dtype=np.uint8)
cfg = auto_config.plan(_write(flat, tmp_path))
assert cfg is not None
assert cfg.has_face
assert cfg.restore_faces
assert cfg.pipeline == "controlnet"
assert cfg.adaptive_polish # smoothing pass ran -> adaptive polish on
assert cfg.unsharp == 0.0 # fixed knobs off; the adaptive polish replaces them
@@ -103,7 +101,6 @@ class TestReason:
def test_reason_summarizes_plan(self):
cfg = auto_config.AutoConfig(
pipeline="controlnet",
restore_faces=True,
adaptive_polish=True,
unsharp=0.0,
humanize=0.0,
@@ -117,5 +114,4 @@ class TestReason:
r = cfg.reason
assert "controlnet" in r
assert "face" in r
assert "face-restore on" in r
assert "adaptive polish" in r
-1
View File
@@ -523,7 +523,6 @@ class TestBatchCommand:
output_dir = tmp_path / "output"
plan = auto_config.AutoConfig(
pipeline="controlnet",
restore_faces=True,
adaptive_polish=True,
unsharp=0.0,
humanize=0.0,
-152
View File
@@ -1,152 +0,0 @@
"""Control-flow tests for instantid_restore -- no model download.
The end-to-end InstantID run is monkey-patched: we replace ``_get_pipeline`` and
``_get_face_analyser`` with fakes, install a fake InsightFace ``FaceAnalysis``
embedding, and check that the per-face crop + composite pipeline wires up the
expected pixels into ``cleaned_bgr``.
"""
from __future__ import annotations
import cv2
import numpy as np
import pytest
from remove_ai_watermarks import instantid_restore
class TestIsAvailable:
def test_returns_bool(self):
assert isinstance(instantid_restore.is_available(), bool)
class TestRepoPins:
"""Pin the InstantID repo + adapter file so a maintainer change is visible."""
def test_repo_is_instantx_instantid(self):
assert instantid_restore._INSTANTID_REPO == "InstantX/InstantID"
def test_controlnet_subfolder(self):
assert instantid_restore._INSTANTID_CONTROLNET_SUBFOLDER == "ControlNetModel"
def test_ip_adapter_filename(self):
assert instantid_restore._INSTANTID_IP_ADAPTER == "ip-adapter.bin"
class TestDrawKps:
def test_renders_color_image(self):
kps = np.array([[100, 100], [200, 100], [150, 150], [120, 200], [180, 200]])
img = instantid_restore._draw_kps((256, 256), kps)
arr = np.array(img)
assert arr.shape == (256, 256, 3)
# Has nonzero pixels (the stick figure is rendered).
assert arr.sum() > 0
def test_black_outside_kps(self):
kps = np.array([[100, 100], [200, 100], [150, 150], [120, 200], [180, 200]])
img = instantid_restore._draw_kps((256, 256), kps)
arr = np.array(img)
# Top-left corner should be black (no keypoint there).
assert arr[0, 0].sum() == 0
class TestRestoreFacesInstantidControlFlow:
"""End-to-end flow with the pipeline / face analyser / InsightFace mocked.
Checks that with one detected face: (1) the original crop is fed to the
InsightFace mock; (2) the pipeline mock receives the expected kwargs; (3)
the regenerated output ends up composited into the cleaned image.
"""
@staticmethod
def _fake_pipeline_class(fill_value: int = 210):
import torch
from PIL import Image
class _FakePipeOutput:
def __init__(self, images):
self.images = images
class _FakePipe:
device = "cpu"
dtype = torch.float32
def __call__(self, **kwargs):
# Save kwargs for assertion.
_FakePipe.last_kwargs = kwargs
# Gradient face so the color-match step shifts the mean but
# preserves contrast (the composite is then detectable as a
# variance change in the face region even with uniform canvas).
grad = np.linspace(0, fill_value, 1024, dtype=np.uint8)
arr = np.broadcast_to(grad[:, None, None], (1024, 1024, 3)).copy()
img = Image.fromarray(arr)
return _FakePipeOutput([img])
return _FakePipe()
def test_no_faces_returns_cleaned_unchanged(self, monkeypatch):
monkeypatch.setattr(instantid_restore, "is_available", lambda: True)
monkeypatch.setattr(instantid_restore, "_get_pipeline", lambda: self._fake_pipeline_class())
monkeypatch.setattr(instantid_restore, "_get_face_analyser", lambda: object())
orig = np.full((400, 400, 3), 50, dtype=np.uint8)
cleaned = np.full((400, 400, 3), 100, dtype=np.uint8)
out = instantid_restore.restore_faces_instantid(orig, cleaned, detect_faces_fn=lambda _b: [])
assert np.array_equal(out, cleaned)
def test_one_face_gets_composited_into_cleaned(self, monkeypatch):
monkeypatch.setattr(instantid_restore, "is_available", lambda: True)
monkeypatch.setattr(instantid_restore, "_get_pipeline", lambda: self._fake_pipeline_class(fill_value=210))
# Fake FaceAnalyser that returns one face with a 512-d embedding + 5 keypoints.
class _FakeFA:
def get(self, _bgr):
return [
{
"bbox": np.array([10, 10, 100, 100], dtype=np.float32),
"embedding": np.zeros(512, dtype=np.float32),
"kps": np.array(
[[30, 40], [70, 40], [50, 60], [35, 80], [65, 80]],
dtype=np.float32,
),
}
]
monkeypatch.setattr(instantid_restore, "_get_face_analyser", lambda: _FakeFA())
orig = np.full((400, 400, 3), 30, dtype=np.uint8)
cleaned = np.full((400, 400, 3), 90, dtype=np.uint8)
cv2.rectangle(orig, (150, 150), (250, 250), (200, 100, 50), -1)
out = instantid_restore.restore_faces_instantid(
orig, cleaned, detect_faces_fn=lambda _b: [(150, 150, 100, 100)]
)
# The composite must have written non-uniform values into the face
# region (gradient survives color-match as variance), and the canvas
# corner stays close to the cleaned base.
face_region = out[170:230, 170:230]
assert int(face_region.std()) > 0
assert int(out[0, 0, 0]) - int(cleaned[0, 0, 0]) <= 1
def test_insightface_misses_face_skips_gracefully(self, monkeypatch):
monkeypatch.setattr(instantid_restore, "is_available", lambda: True)
monkeypatch.setattr(instantid_restore, "_get_pipeline", lambda: self._fake_pipeline_class())
class _EmptyFA:
def get(self, _bgr):
return []
monkeypatch.setattr(instantid_restore, "_get_face_analyser", lambda: _EmptyFA())
orig = np.full((400, 400, 3), 30, dtype=np.uint8)
cleaned = np.full((400, 400, 3), 90, dtype=np.uint8)
out = instantid_restore.restore_faces_instantid(
orig, cleaned, detect_faces_fn=lambda _b: [(150, 150, 100, 100)]
)
# No face detected by InsightFace -> cleaned image is returned unchanged.
assert np.array_equal(out, cleaned)
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-151
View File
@@ -1,151 +0,0 @@
"""Tests for the PhotoMaker-V2 face identity restoration helper.
These tests cover the pure-Python parts (face crop math, composite, the no-faces
no-op, the is_available guard) WITHOUT loading PhotoMaker or SDXL -- the model-loading
path is gated behind ``is_available()`` and exercised manually via the Modal cert
sweep, mirroring the convention used for ``face_restore`` and ``upscaler``.
The end-to-end PhotoMaker run is monkey-patched: we replace ``_get_pipeline`` with a
fake pipeline whose ``__call__`` returns a known constant-color face, so we can verify
that the right boxes get the right pixels composited back.
"""
from __future__ import annotations
from types import SimpleNamespace
import cv2
import numpy as np
from remove_ai_watermarks import photomaker_restore
class TestIsAvailable:
def test_returns_bool(self):
assert isinstance(photomaker_restore.is_available(), bool)
class TestV2WeightPins:
"""Pin the V2 repo + weights so a maintainer change is visible in a code review."""
def test_repo_is_v2(self):
assert photomaker_restore._PHOTOMAKER_REPO == "TencentARC/PhotoMaker-V2"
def test_weight_filename_is_v2(self):
assert photomaker_restore._PHOTOMAKER_FILE == "photomaker-v2.bin"
class TestFaceCropSquare:
def test_centers_on_face_box(self):
img = np.full((400, 400, 3), 128, dtype=np.uint8)
crop, box = photomaker_restore._face_crop_square(img, (100, 150, 80, 80))
x1, y1, x2, y2 = box
# The crop covers the requested box (with padding)
assert x1 <= 100
assert y1 <= 150
assert x2 >= 180
assert y2 >= 230
assert crop.shape[0] == y2 - y1
assert crop.shape[1] == x2 - x1
def test_clips_at_image_edges(self):
img = np.full((200, 200, 3), 128, dtype=np.uint8)
crop, (x1, y1, x2, y2) = photomaker_restore._face_crop_square(img, (180, 180, 30, 30))
# Box must be clipped within the image
assert x1 >= 0
assert y1 >= 0
assert x2 <= 200
assert y2 <= 200
assert crop.shape[0] == y2 - y1
assert crop.shape[1] == x2 - x1
def test_pad_widens_the_crop(self):
img = np.full((400, 400, 3), 128, dtype=np.uint8)
_, no_pad = photomaker_restore._face_crop_square(img, (150, 150, 50, 50), pad=0.0)
_, with_pad = photomaker_restore._face_crop_square(img, (150, 150, 50, 50), pad=0.5)
assert (with_pad[2] - with_pad[0]) > (no_pad[2] - no_pad[0])
class TestCompositeFaces:
def test_empty_list_returns_base_unchanged(self):
base = np.full((100, 100, 3), 64, dtype=np.uint8)
out = photomaker_restore._composite_faces(base, [])
assert np.array_equal(out, base)
def test_box_outside_image_is_skipped(self):
base = np.full((100, 100, 3), 64, dtype=np.uint8)
crop = np.full((40, 40, 3), 200, dtype=np.uint8)
out = photomaker_restore._composite_faces(base, [(crop, (200, 200, 240, 240))])
assert np.array_equal(out, base)
def test_composited_box_pulls_pixel_value_toward_crop(self):
base = np.full((200, 200, 3), 40, dtype=np.uint8)
crop = np.full((50, 50, 3), 220, dtype=np.uint8)
# Place the crop fully inside the image at (60, 60)..(110, 110)
out = photomaker_restore._composite_faces(base, [(crop, (60, 60, 110, 110))])
# The box center should be heavily biased toward the crop color (>120) ...
assert out[85, 85, 0] > 120
# ... and corners (well outside the feathered region) stay close to base
assert int(out[0, 0, 0]) - int(base[0, 0, 0]) <= 1
class TestRestoreFacesPhotomakerControlFlow:
"""End-to-end control flow with a fake pipeline -- no diffusion model loaded."""
@staticmethod
def _fake_pipeline_class(fill_value: int = 200):
"""Class-based fake (no ``__call__`` on a SimpleNamespace, which Python won't dispatch)."""
from PIL import Image
size = photomaker_restore._PHOTOMAKER_FACE_SIZE
fake_face = Image.fromarray(np.full((size, size, 3), fill_value, dtype=np.uint8))
import torch
class _FakePipe:
device = "cpu"
dtype = torch.float32
def __call__(self, **_kwargs):
return SimpleNamespace(images=[fake_face])
return _FakePipe()
def test_no_faces_returns_cleaned_unchanged(self, monkeypatch):
# Force is_available so we never hit the missing-extra branch
monkeypatch.setattr(photomaker_restore, "is_available", lambda: True)
monkeypatch.setattr(photomaker_restore, "_get_pipeline", lambda: self._fake_pipeline_class())
orig = np.full((200, 200, 3), 30, dtype=np.uint8)
cleaned = np.full((200, 200, 3), 90, dtype=np.uint8)
out = photomaker_restore.restore_faces_photomaker(orig, cleaned, detect_faces_fn=lambda _b: [])
assert np.array_equal(out, cleaned)
def test_one_face_gets_composited_into_cleaned(self, monkeypatch):
import sys
import types
monkeypatch.setattr(photomaker_restore, "is_available", lambda: True)
monkeypatch.setattr(photomaker_restore, "_get_pipeline", lambda: self._fake_pipeline_class(fill_value=210))
# FaceAnalyser singleton: any non-None object; the test stubs analyze_faces below.
monkeypatch.setattr(photomaker_restore, "_get_face_analyser", lambda: object())
# Stub `from photomaker import analyze_faces` inside the function: install a
# fake `photomaker` module that returns a single 512-d embedding per call.
fake_photomaker = types.ModuleType("photomaker")
fake_photomaker.analyze_faces = lambda _fa, _bgr: [{"embedding": np.zeros(512, dtype=np.float32)}]
monkeypatch.setitem(sys.modules, "photomaker", fake_photomaker)
orig = np.full((400, 400, 3), 30, dtype=np.uint8)
cleaned = np.full((400, 400, 3), 90, dtype=np.uint8)
# Mark the original face region with a distinctive color so we can confirm the
# crop reached the pipeline (not strictly tested here, but useful sanity).
cv2.rectangle(orig, (150, 150), (250, 250), (200, 100, 50), -1)
out = photomaker_restore.restore_faces_photomaker(
orig, cleaned, detect_faces_fn=lambda _b: [(150, 150, 100, 100)]
)
# The cleaned image should have shifted toward the fake-face fill (210) inside
# the face region.
assert out[200, 200, 0] > 150
# And the corner pixels (well outside the feather) should still be near the base.
assert int(out[0, 0, 0]) - int(cleaned[0, 0, 0]) <= 1
Generated
+1 -470
View File
@@ -853,19 +853,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" },
]
[[package]]
name = "imageio"
version = "2.37.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "pillow" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" },
]
[[package]]
name = "importlib-metadata"
version = "9.0.0"
@@ -887,28 +874,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "insightface"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "onnx" },
{ name = "onnxruntime", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "opencv-python" },
{ name = "requests" },
{ name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "tqdm" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/77/a1b06d3755fcfb353cdd46a138ec578d9664ee8b53590e73e9edca328928/insightface-1.0.1.tar.gz", hash = "sha256:27af24891bbba470cb3573b366a0fcca8989fc8503c9f8f281e8cba6fd716075", size = 721597, upload-time = "2026-05-23T06:43:00.404Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/ce/33989427a7fae952e1606d0edee4191f965630f3c74f2166fa776fafd271/insightface-1.0.1-py3-none-any.whl", hash = "sha256:5f373f6fedbdda5cbc59a34ca386a75a2995cdaf6899402590ae9eb4308fc2e8", size = 762241, upload-time = "2026-05-23T06:42:58.253Z" },
]
[[package]]
name = "invisible-watermark"
version = "0.2.0"
@@ -937,18 +902,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "lazy-loader"
version = "0.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" },
]
[[package]]
name = "lightning"
version = "2.6.4"
@@ -1088,29 +1041,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "ml-dtypes"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fd/15/76f86faa0902836cc133939732f7611ace68cf54148487a99c539c272dc8/ml_dtypes-0.4.1.tar.gz", hash = "sha256:fad5f2de464fd09127e49b7fd1252b9006fb43d2edc1ff112d390c324af5ca7a", size = 692594, upload-time = "2024-09-13T19:07:11.624Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/9e/76b84f77c7afee3b116dc8407903a2d5004ba3059a8f3dcdcfa6ebf33fff/ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5", size = 397975, upload-time = "2024-09-13T19:06:44.265Z" },
{ url = "https://files.pythonhosted.org/packages/03/7b/32650e1b2a2713a5923a0af2a8503d0d4a8fc99d1e1e0a1c40e996634460/ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24", size = 2182570, upload-time = "2024-09-13T19:06:46.189Z" },
{ url = "https://files.pythonhosted.org/packages/16/86/a9f7569e7e4f5395f927de38a13b92efa73f809285d04f2923b291783dd2/ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5e8f75fa371020dd30f9196e7d73babae2abd51cf59bdd56cb4f8de7e13354", size = 2160365, upload-time = "2024-09-13T19:06:48.198Z" },
{ url = "https://files.pythonhosted.org/packages/04/1b/9a3afb437702503514f3934ec8d7904270edf013d28074f3e700e5dfbb0f/ml_dtypes-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:15fdd922fea57e493844e5abb930b9c0bd0af217d9edd3724479fc3d7ce70e3f", size = 126633, upload-time = "2024-09-13T19:06:50.656Z" },
{ url = "https://files.pythonhosted.org/packages/d1/76/9835c8609c29f2214359e88f29255fc4aad4ea0f613fb48aa8815ceda1b6/ml_dtypes-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d55b588116a7085d6e074cf0cdb1d6fa3875c059dddc4d2c94a4cc81c23e975", size = 397973, upload-time = "2024-09-13T19:06:51.748Z" },
{ url = "https://files.pythonhosted.org/packages/7e/99/e68c56fac5de973007a10254b6e17a0362393724f40f66d5e4033f4962c2/ml_dtypes-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e138a9b7a48079c900ea969341a5754019a1ad17ae27ee330f7ebf43f23877f9", size = 2185134, upload-time = "2024-09-13T19:06:53.197Z" },
{ url = "https://files.pythonhosted.org/packages/28/bc/6a2344338ea7b61cd7b46fb24ec459360a5a0903b57c55b156c1e46c644a/ml_dtypes-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c6cfb5cf78535b103fde9ea3ded8e9f16f75bc07789054edc7776abfb3d752", size = 2163661, upload-time = "2024-09-13T19:06:54.519Z" },
{ url = "https://files.pythonhosted.org/packages/e8/d3/ddfd9878b223b3aa9a930c6100a99afca5cfab7ea703662e00323acb7568/ml_dtypes-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:274cc7193dd73b35fb26bef6c5d40ae3eb258359ee71cd82f6e96a8c948bdaa6", size = 126727, upload-time = "2024-09-13T19:06:55.897Z" },
{ url = "https://files.pythonhosted.org/packages/ba/1a/99e924f12e4b62139fbac87419698c65f956d58de0dbfa7c028fa5b096aa/ml_dtypes-0.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:827d3ca2097085cf0355f8fdf092b888890bb1b1455f52801a2d7756f056f54b", size = 405077, upload-time = "2024-09-13T19:06:57.538Z" },
{ url = "https://files.pythonhosted.org/packages/8f/8c/7b610bd500617854c8cc6ed7c8cfb9d48d6a5c21a1437a36a4b9bc8a3598/ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772426b08a6172a891274d581ce58ea2789cc8abc1c002a27223f314aaf894e7", size = 2181554, upload-time = "2024-09-13T19:06:59.196Z" },
{ url = "https://files.pythonhosted.org/packages/c7/c6/f89620cecc0581dc1839e218c4315171312e46c62a62da6ace204bda91c0/ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126e7d679b8676d1a958f2651949fbfa182832c3cd08020d8facd94e4114f3e9", size = 2160488, upload-time = "2024-09-13T19:07:03.131Z" },
{ url = "https://files.pythonhosted.org/packages/ae/11/a742d3c31b2cc8557a48efdde53427fd5f9caa2fa3c9c27d826e78a66f51/ml_dtypes-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:df0fb650d5c582a9e72bb5bd96cfebb2cdb889d89daff621c8fbc60295eba66c", size = 127462, upload-time = "2024-09-13T19:07:04.916Z" },
]
[[package]]
name = "mpmath"
version = "1.3.0"
@@ -1498,48 +1428,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/bd/21b9bfa2fa29913df9f80c78b034b8c3bf8ecee04dac7b34bb546019f195/omegaconf-2.4.0.dev11-py3-none-any.whl", hash = "sha256:943b2a01329335fa11a4d3caed06432d7023f8fb579dcbff4d28b26ee6be469e", size = 233260, upload-time = "2026-05-13T19:57:39.309Z" },
]
[[package]]
name = "onnx"
version = "1.19.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ml-dtypes" },
{ name = "numpy" },
{ name = "protobuf" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/bf/b0a63ee9f3759dcd177b28c6f2cb22f2aecc6d9b3efecaabc298883caa5f/onnx-1.19.0.tar.gz", hash = "sha256:aa3f70b60f54a29015e41639298ace06adf1dd6b023b9b30f1bca91bb0db9473", size = 11949859, upload-time = "2025-08-27T02:34:27.107Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/b3/8a6f3b05d18dffdc7c18839bd829587c826c8513f4bdbe21ddf37dacce50/onnx-1.19.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e927d745939d590f164e43c5aec7338c5a75855a15130ee795f492fc3a0fa565", size = 18310869, upload-time = "2025-08-27T02:32:47.346Z" },
{ url = "https://files.pythonhosted.org/packages/b9/92/550d6155ab3f2c00e95add1726397c95b4b79d6eb4928d049ff591ad4c84/onnx-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c6cdcb237c5c4202463bac50417c5a7f7092997a8469e8b7ffcd09f51de0f4a9", size = 18028144, upload-time = "2025-08-27T02:32:50.306Z" },
{ url = "https://files.pythonhosted.org/packages/79/21/9bcc715ea6d9aab3f6c583bfc59504a14777e39e0591030e7345f4e40315/onnx-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed0b85a33deacb65baffe6ca4ce91adf2bb906fa2dee3856c3c94e163d2eb563", size = 18200923, upload-time = "2025-08-27T02:32:54.325Z" },
{ url = "https://files.pythonhosted.org/packages/c8/90/3a6f0741ff22270e2f4b741f440ab68ba5525ebc94775cd6f2c01f531374/onnx-1.19.0-cp310-cp310-win32.whl", hash = "sha256:89a9cefe75547aec14a796352c2243e36793bbbcb642d8897118595ab0c2395b", size = 16332097, upload-time = "2025-08-27T02:32:56.997Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4c/ef61d359865712803d488672607023d36bfcd21fa008d8dc1d6ee8e8b23c/onnx-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:a16a82bfdf4738691c0a6eda5293928645ab8b180ab033df84080817660b5e66", size = 16451402, upload-time = "2025-08-27T02:33:00.534Z" },
{ url = "https://files.pythonhosted.org/packages/db/5c/b959b17608cfb6ccf6359b39fe56a5b0b7d965b3d6e6a3c0add90812c36e/onnx-1.19.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:206f00c47b85b5c7af79671e3307147407991a17994c26974565aadc9e96e4e4", size = 18312580, upload-time = "2025-08-27T02:33:03.081Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ee/ac052bbbc832abe0debb784c2c57f9582444fb5f51d63c2967fd04432444/onnx-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4d7bee94abaac28988b50da675ae99ef8dd3ce16210d591fbd0b214a5930beb3", size = 18029165, upload-time = "2025-08-27T02:33:05.771Z" },
{ url = "https://files.pythonhosted.org/packages/5c/c9/8687ba0948d46fd61b04e3952af9237883bbf8f16d716e7ed27e688d73b8/onnx-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7730b96b68c0c354bbc7857961bb4909b9aaa171360a8e3708d0a4c749aaadeb", size = 18202125, upload-time = "2025-08-27T02:33:09.325Z" },
{ url = "https://files.pythonhosted.org/packages/e2/16/6249c013e81bd689f46f96c7236d7677f1af5dd9ef22746716b48f10e506/onnx-1.19.0-cp311-cp311-win32.whl", hash = "sha256:7cb7a3ad8059d1a0dfdc5e0a98f71837d82002e441f112825403b137227c2c97", size = 16332738, upload-time = "2025-08-27T02:33:12.448Z" },
{ url = "https://files.pythonhosted.org/packages/6a/28/34a1e2166e418c6a78e5c82e66f409d9da9317832f11c647f7d4e23846a6/onnx-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:d75452a9be868bd30c3ef6aa5991df89bbfe53d0d90b2325c5e730fbd91fff85", size = 16452303, upload-time = "2025-08-27T02:33:15.176Z" },
{ url = "https://files.pythonhosted.org/packages/e6/b7/639664626e5ba8027860c4d2a639ee02b37e9c322215c921e9222513c3aa/onnx-1.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:23c7959370d7b3236f821e609b0af7763cff7672a758e6c1fc877bac099e786b", size = 16425340, upload-time = "2025-08-27T02:33:17.78Z" },
{ url = "https://files.pythonhosted.org/packages/0d/94/f56f6ca5e2f921b28c0f0476705eab56486b279f04e1d568ed64c14e7764/onnx-1.19.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:61d94e6498ca636756f8f4ee2135708434601b2892b7c09536befb19bc8ca007", size = 18322331, upload-time = "2025-08-27T02:33:20.373Z" },
{ url = "https://files.pythonhosted.org/packages/c8/00/8cc3f3c40b54b28f96923380f57c9176872e475face726f7d7a78bd74098/onnx-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:224473354462f005bae985c72028aaa5c85ab11de1b71d55b06fdadd64a667dd", size = 18027513, upload-time = "2025-08-27T02:33:23.44Z" },
{ url = "https://files.pythonhosted.org/packages/61/90/17c4d2566fd0117a5e412688c9525f8950d467f477fbd574e6b32bc9cb8d/onnx-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae475c85c89bc4d1f16571006fd21a3e7c0e258dd2c091f6e8aafb083d1ed9b", size = 18202278, upload-time = "2025-08-27T02:33:26.103Z" },
{ url = "https://files.pythonhosted.org/packages/bc/6e/a9383d9cf6db4ac761a129b081e9fa5d0cd89aad43cf1e3fc6285b915c7d/onnx-1.19.0-cp312-cp312-win32.whl", hash = "sha256:323f6a96383a9cdb3960396cffea0a922593d221f3929b17312781e9f9b7fb9f", size = 16333080, upload-time = "2025-08-27T02:33:28.559Z" },
{ url = "https://files.pythonhosted.org/packages/a7/2e/3ff480a8c1fa7939662bdc973e41914add2d4a1f2b8572a3c39c2e4982e5/onnx-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:50220f3499a499b1a15e19451a678a58e22ad21b34edf2c844c6ef1d9febddc2", size = 16453927, upload-time = "2025-08-27T02:33:31.177Z" },
{ url = "https://files.pythonhosted.org/packages/57/37/ad500945b1b5c154fe9d7b826b30816ebd629d10211ea82071b5bcc30aa4/onnx-1.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:efb768299580b786e21abe504e1652ae6189f0beed02ab087cd841cb4bb37e43", size = 16426022, upload-time = "2025-08-27T02:33:33.515Z" },
{ url = "https://files.pythonhosted.org/packages/be/29/d7b731f63d243f815d9256dce0dca3c151dcaa1ac59f73e6ee06c9afbe91/onnx-1.19.0-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:9aed51a4b01acc9ea4e0fe522f34b2220d59e9b2a47f105ac8787c2e13ec5111", size = 18322412, upload-time = "2025-08-27T02:33:36.723Z" },
{ url = "https://files.pythonhosted.org/packages/58/f5/d3106becb42cb374f0e17ff4c9933a97f1ee1d6a798c9452067f7d3ff61b/onnx-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce2cdc3eb518bb832668c4ea9aeeda01fbaa59d3e8e5dfaf7aa00f3d37119404", size = 18026565, upload-time = "2025-08-27T02:33:39.493Z" },
{ url = "https://files.pythonhosted.org/packages/83/fa/b086d17bab3900754c7ffbabfb244f8e5e5da54a34dda2a27022aa2b373b/onnx-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b546bd7958734b6abcd40cfede3d025e9c274fd96334053a288ab11106bd0aa", size = 18202077, upload-time = "2025-08-27T02:33:42.115Z" },
{ url = "https://files.pythonhosted.org/packages/35/f2/5e2dfb9d4cf873f091c3f3c6d151f071da4295f9893fbf880f107efe3447/onnx-1.19.0-cp313-cp313-win32.whl", hash = "sha256:03086bffa1cf5837430cf92f892ca0cd28c72758d8905578c2bf8ffaf86c6743", size = 16333198, upload-time = "2025-08-27T02:33:45.172Z" },
{ url = "https://files.pythonhosted.org/packages/79/67/b3751a35c2522f62f313156959575619b8fa66aa883db3adda9d897d8eb2/onnx-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:1715b51eb0ab65272e34ef51cb34696160204b003566cd8aced2ad20a8f95cb8", size = 16453836, upload-time = "2025-08-27T02:33:47.779Z" },
{ url = "https://files.pythonhosted.org/packages/14/b9/1df85effc960fbbb90bb7bc36eb3907c676b104bc2f88bce022bcfdaef63/onnx-1.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:6bf5acdb97a3ddd6e70747d50b371846c313952016d0c41133cbd8f61b71a8d5", size = 16425877, upload-time = "2025-08-27T02:33:50.357Z" },
{ url = "https://files.pythonhosted.org/packages/23/2b/089174a1427be9149f37450f8959a558ba20f79fca506ba461d59379d3a1/onnx-1.19.0-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:46cf29adea63e68be0403c68de45ba1b6acc9bb9592c5ddc8c13675a7c71f2cb", size = 18348546, upload-time = "2025-08-27T02:33:56.132Z" },
{ url = "https://files.pythonhosted.org/packages/c0/d6/3458f0e3a9dc7677675d45d7d6528cb84ad321c8670cc10c69b32c3e03da/onnx-1.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:246f0de1345498d990a443d55a5b5af5101a3e25a05a2c3a5fe8b7bd7a7d0707", size = 18033067, upload-time = "2025-08-27T02:33:58.661Z" },
{ url = "https://files.pythonhosted.org/packages/e4/16/6e4130e1b4b29465ee1fb07d04e8d6f382227615c28df8f607ba50909e2a/onnx-1.19.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae0d163ffbc250007d984b8dd692a4e2e4506151236b50ca6e3560b612ccf9ff", size = 18205741, upload-time = "2025-08-27T02:34:01.538Z" },
{ url = "https://files.pythonhosted.org/packages/fe/d8/f64d010fd024b2a2b11ce0c4ee179e4f8f6d4ccc95f8184961c894c22af1/onnx-1.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7c151604c7cca6ae26161c55923a7b9b559df3344938f93ea0074d2d49e7fe78", size = 16453839, upload-time = "2025-08-27T02:34:06.515Z" },
{ url = "https://files.pythonhosted.org/packages/67/ec/8761048eabef4dad55af4c002c672d139b9bd47c3616abaed642a1710063/onnx-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:236bc0e60d7c0f4159300da639953dd2564df1c195bce01caba172a712e75af4", size = 18027605, upload-time = "2025-08-27T02:34:08.962Z" },
]
[[package]]
name = "onnxruntime"
version = "1.24.3"
@@ -1675,32 +1563,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "peft"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "accelerate" },
{ name = "huggingface-hub" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "psutil" },
{ name = "pyyaml" },
{ name = "safetensors" },
{ name = "torch" },
{ name = "tqdm" },
{ name = "transformers" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/cf/037f1e3d5186496c05513a6754639e2dab3038a05f384284d49a9bd06a2d/peft-0.19.1.tar.gz", hash = "sha256:0d97542fe96dcdaa20d3b81c06f26f988618f416a73544ab23c3618ccb674a40", size = 763738, upload-time = "2026-04-16T15:46:45.105Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/b6/f54d676ed93cc2dd2234c3b172ea9c8c3d7d29361e66b1b23dec57a67465/peft-0.19.1-py3-none-any.whl", hash = "sha256:2113f72a81621b5913ef28f9022204c742df111890c5f49d812716a4a301e356", size = 680692, upload-time = "2026-04-16T15:46:42.886Z" },
]
[[package]]
name = "photomaker"
version = "0.2.0"
source = { git = "https://github.com/TencentARC/PhotoMaker.git#060b4fcb10b76a4554edf565d6106b7e36c968f0" }
[[package]]
name = "piexif"
version = "1.1.3"
@@ -2431,26 +2293,11 @@ gpu = [
{ name = "torch" },
{ name = "transformers" },
]
instantid = [
{ name = "huggingface-hub" },
{ name = "insightface" },
{ name = "onnxruntime", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
lama = [
{ name = "huggingface-hub" },
{ name = "onnxruntime", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
photomaker = [
{ name = "einops" },
{ name = "huggingface-hub" },
{ name = "insightface" },
{ name = "onnxruntime", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "peft" },
{ name = "photomaker" },
]
trustmark = [
{ name = "trustmark" },
]
@@ -2460,21 +2307,12 @@ requires-dist = [
{ name = "accelerate", marker = "extra == 'gpu'", specifier = ">=0.25.0" },
{ name = "click", specifier = ">=8.0.0" },
{ name = "diffusers", marker = "extra == 'gpu'", specifier = ">=0.38.0" },
{ name = "einops", marker = "extra == 'photomaker'", specifier = ">=0.7.0" },
{ name = "huggingface-hub", marker = "extra == 'instantid'", specifier = ">=0.20.0" },
{ name = "huggingface-hub", marker = "extra == 'lama'", specifier = ">=0.20.0" },
{ name = "huggingface-hub", marker = "extra == 'photomaker'", specifier = ">=0.20.0" },
{ name = "insightface", marker = "extra == 'instantid'", specifier = ">=0.7.3" },
{ name = "insightface", marker = "extra == 'photomaker'", specifier = ">=0.7.3" },
{ name = "invisible-watermark", marker = "extra == 'detect'", specifier = ">=0.2.0" },
{ name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" },
{ name = "numpy", specifier = ">=1.24.0" },
{ name = "onnxruntime", marker = "extra == 'instantid'", specifier = ">=1.16.0" },
{ name = "onnxruntime", marker = "extra == 'lama'", specifier = ">=1.16.0" },
{ name = "onnxruntime", marker = "extra == 'photomaker'", specifier = ">=1.16.0" },
{ name = "opencv-python-headless", specifier = ">=4.8.0" },
{ name = "peft", marker = "extra == 'photomaker'", specifier = ">=0.10.0" },
{ name = "photomaker", marker = "extra == 'photomaker'", git = "https://github.com/TencentARC/PhotoMaker.git" },
{ name = "piexif", specifier = ">=1.1.3" },
{ name = "pillow", specifier = ">=10.0.0" },
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.0" },
@@ -2490,7 +2328,7 @@ requires-dist = [
{ name = "transformers", marker = "extra == 'gpu'", specifier = ">=5,<6" },
{ name = "trustmark", marker = "extra == 'trustmark'", specifier = ">=0.8.0" },
]
provides-extras = ["gpu", "detect", "trustmark", "lama", "photomaker", "instantid", "esrgan", "dev", "all"]
provides-extras = ["gpu", "detect", "trustmark", "lama", "esrgan", "dev", "all"]
[[package]]
name = "requests"
@@ -2569,272 +2407,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/73/fd944d3417ba04bd0e72682fa1bedc6d99d986a3594fc7910313088cfe88/safetensors-0.8.0rc0-cp310-abi3-win_arm64.whl", hash = "sha256:b7f8180f8c119dce85da7913904ccf4a0227adf095eb63f1732a6729c2672cb1", size = 330970, upload-time = "2026-04-14T14:30:43.451Z" },
]
[[package]]
name = "scikit-image"
version = "0.25.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11' and sys_platform == 'darwin'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "imageio", marker = "python_full_version < '3.11'" },
{ name = "lazy-loader", marker = "python_full_version < '3.11'" },
{ name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", marker = "python_full_version < '3.11'" },
{ name = "packaging", marker = "python_full_version < '3.11'" },
{ name = "pillow", marker = "python_full_version < '3.11'" },
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" },
{ url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" },
{ url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" },
{ url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" },
{ url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" },
{ url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" },
{ url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" },
{ url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" },
{ url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" },
{ url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" },
{ url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" },
{ url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" },
{ url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" },
{ url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" },
{ url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" },
{ url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" },
{ url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" },
{ url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" },
{ url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" },
{ url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" },
{ url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" },
]
[[package]]
name = "scikit-image"
version = "0.26.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.13' and platform_machine != 's390x' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 's390x' and sys_platform == 'darwin'",
"python_full_version == '3.12.*' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(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 != 'linux')",
"(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 != 'linux')",
"python_full_version == '3.11.*' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "imageio", marker = "python_full_version >= '3.11'" },
{ name = "lazy-loader", marker = "python_full_version >= '3.11'" },
{ name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "numpy", marker = "python_full_version >= '3.11'" },
{ name = "packaging", marker = "python_full_version >= '3.11'" },
{ name = "pillow", marker = "python_full_version >= '3.11'" },
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" },
{ url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" },
{ url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" },
{ url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" },
{ url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" },
{ url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" },
{ url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" },
{ url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" },
{ url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" },
{ url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" },
{ url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" },
{ url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" },
{ url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" },
{ url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" },
{ url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" },
{ url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" },
{ url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" },
{ url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" },
{ url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" },
{ url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" },
{ url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" },
{ url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" },
{ url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" },
{ url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" },
{ url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" },
{ url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" },
{ url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" },
{ url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" },
{ url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" },
{ url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" },
{ url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" },
{ url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" },
{ url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" },
{ url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" },
{ url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" },
{ url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" },
{ url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" },
{ url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" },
{ url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" },
{ url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" },
{ url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" },
{ url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" },
{ url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" },
{ url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" },
{ url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" },
{ url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" },
{ url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" },
]
[[package]]
name = "scipy"
version = "1.15.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11' and sys_platform == 'darwin'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "numpy", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" },
{ url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" },
{ url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" },
{ url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" },
{ url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" },
{ url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" },
{ url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" },
{ url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" },
{ url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" },
{ url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" },
{ url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" },
{ url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" },
{ url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" },
{ url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" },
{ url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" },
{ url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" },
{ url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" },
{ url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" },
{ url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" },
{ url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" },
{ url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" },
{ url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" },
{ url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" },
{ url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" },
{ url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" },
{ url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" },
{ url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" },
{ url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" },
{ url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" },
{ url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" },
{ url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" },
{ url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" },
{ url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" },
{ url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" },
{ url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" },
{ url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" },
{ url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" },
{ url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" },
{ url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" },
{ url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" },
{ url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" },
{ url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" },
{ url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" },
{ url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" },
{ url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" },
]
[[package]]
name = "scipy"
version = "1.17.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.13' and platform_machine != 's390x' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 's390x' and sys_platform == 'darwin'",
"python_full_version == '3.12.*' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(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 != 'linux')",
"(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 != 'linux')",
"python_full_version == '3.11.*' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "numpy", marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" },
{ url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" },
{ url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" },
{ url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" },
{ url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" },
{ url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" },
{ url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" },
{ url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" },
{ url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" },
{ url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" },
{ url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" },
{ url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" },
{ url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" },
{ url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" },
{ url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" },
{ url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" },
{ url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" },
{ url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" },
{ url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" },
{ url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" },
{ url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" },
{ url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" },
{ url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" },
{ url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" },
{ url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" },
{ url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" },
{ url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" },
{ url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" },
{ url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" },
{ url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" },
{ url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" },
{ url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" },
{ url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" },
{ url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" },
{ url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" },
{ url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" },
{ url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" },
{ url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" },
{ url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" },
{ url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" },
{ url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" },
{ url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" },
{ url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" },
{ url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" },
{ url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" },
{ url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" },
{ url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" },
{ url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" },
{ url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" },
{ url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" },
{ url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" },
{ url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" },
{ url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" },
{ url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" },
{ url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" },
{ url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" },
{ url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" },
{ url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" },
{ url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
]
[[package]]
name = "setuptools"
version = "81.0.0"
@@ -2891,47 +2463,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
]
[[package]]
name = "tifffile"
version = "2025.5.10"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11' and sys_platform == 'darwin'",
"python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "numpy", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" },
]
[[package]]
name = "tifffile"
version = "2026.3.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.13' and platform_machine != 's390x' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 's390x' and sys_platform == 'darwin'",
"python_full_version == '3.12.*' and sys_platform == 'darwin'",
"python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(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 != 'linux')",
"(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 != 'linux')",
"python_full_version == '3.11.*' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "numpy", marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" },
]
[[package]]
name = "tokenizers"
version = "0.22.2"