mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-06-10 04:43:54 +02:00
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:
@@ -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
|
- **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)
|
- **"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
|
- **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
|
- **Batch processing** — process entire directories
|
||||||
- **Detection** — three-stage NCC watermark detection with confidence scoring
|
- **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)
|
- **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).
|
> **`--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.
|
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).
|
> **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.)
|
**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]"
|
> 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`,
|
> For sharper upscaling of small inputs before diffusion (`--upscaler esrgan`,
|
||||||
> Real-ESRGAN), install the `esrgan` extra. It loads via spandrel (MIT, no basicsr);
|
> Real-ESRGAN), install the `esrgan` extra. It loads via spandrel (MIT, no basicsr);
|
||||||
> the Real-ESRGAN weights (BSD-3-Clause) download on first use:
|
> 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
|
# Strength is vendor-adaptive by default (OpenAI 0.10 / Google 0.15); override
|
||||||
# with --strength. To preserve text/face structure, use --pipeline controlnet
|
# with --strength. To preserve text/face structure, use --pipeline controlnet
|
||||||
# Or let it choose: --auto picks the pipeline, face restore, and an adaptive polish
|
# 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
|
# from the image content (controlnet when there is text/structure, polish that
|
||||||
# a face is present, polish that restores the input's detail level while sparing
|
# restores the input's detail level while sparing text). Every choice is
|
||||||
# text). Every choice is overridable: --pipeline, --no-restore-faces,
|
# overridable: --pipeline and --no-adaptive-polish win over the auto pick.
|
||||||
# --no-adaptive-polish all win over the auto pick. Experimental.
|
# Experimental.
|
||||||
# (SDXL + canny ControlNet); tune preservation with --controlnet-scale. Add
|
# (SDXL + canny ControlNet); tune preservation with --controlnet-scale. Add
|
||||||
|
|
||||||
# Check / strip AI metadata (C2PA, EXIF, "Made with AI" labels)
|
# Check / strip AI metadata (C2PA, EXIF, "Made with AI" labels)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# SynthID-robust face identity for an SDXL removal pipeline (research)
|
# 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 +
|
**Question.** Which face identity-preservation mechanism for an SDXL img2img +
|
||||||
canny-ControlNet watermark-removal pipeline (denoise 0.20-0.30) is BOTH (a)
|
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
|
commercial-safe end-to-end and (b) does not re-introduce the SynthID pixel
|
||||||
|
|||||||
@@ -76,52 +76,6 @@ lama = [
|
|||||||
"onnxruntime>=1.16.0",
|
"onnxruntime>=1.16.0",
|
||||||
"huggingface-hub>=0.20.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
|
# 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
|
# spandrel (MIT) -- a pure model-loader with NO basicsr dependency (it pulls only
|
||||||
# torch / torchvision / safetensors / numpy / einops).
|
# torch / torchvision / safetensors / numpy / einops).
|
||||||
@@ -180,12 +134,6 @@ Repository = "https://github.com/wiltodelta/remove-ai-watermarks"
|
|||||||
requires = ["hatchling<1.31"]
|
requires = ["hatchling<1.31"]
|
||||||
build-backend = "hatchling.build"
|
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]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/remove_ai_watermarks"]
|
packages = ["src/remove_ai_watermarks"]
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class AutoConfig:
|
|||||||
"""Resolved quality modes from content analysis (the ``--auto`` plan)."""
|
"""Resolved quality modes from content analysis (the ``--auto`` plan)."""
|
||||||
|
|
||||||
pipeline: str # "default" | "controlnet"
|
pipeline: str # "default" | "controlnet"
|
||||||
restore_faces: bool
|
|
||||||
adaptive_polish: bool # restore the input's detail level (sharpen + masked grain), sparing text
|
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)
|
unsharp: float # fixed-polish knobs, 0 in auto (the adaptive polish replaces them)
|
||||||
humanize: float
|
humanize: float
|
||||||
@@ -104,14 +103,13 @@ class AutoConfig:
|
|||||||
if self.has_text:
|
if self.has_text:
|
||||||
bits.append("text")
|
bits.append("text")
|
||||||
bits.append(f"edges={self.edge_density:.3f}")
|
bits.append(f"edges={self.edge_density:.3f}")
|
||||||
rf = ", face-restore on" if self.restore_faces else ""
|
|
||||||
if self.adaptive_polish:
|
if self.adaptive_polish:
|
||||||
polish = ", adaptive polish"
|
polish = ", adaptive polish"
|
||||||
elif self.unsharp or self.humanize:
|
elif self.unsharp or self.humanize:
|
||||||
polish = f", unsharp {self.unsharp}/grain {self.humanize}"
|
polish = f", unsharp {self.unsharp}/grain {self.humanize}"
|
||||||
else:
|
else:
|
||||||
polish = ""
|
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]:
|
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
|
structureless = (not has_face) and (not has_text) and edges < _STRUCTURELESS_EDGE_MAX
|
||||||
pipeline = "default" if structureless else "controlnet"
|
pipeline = "default" if structureless else "controlnet"
|
||||||
restore_faces = has_face
|
smoothing = pipeline == "controlnet"
|
||||||
smoothing = pipeline == "controlnet" or restore_faces
|
|
||||||
|
|
||||||
cfg = AutoConfig(
|
cfg = AutoConfig(
|
||||||
pipeline=pipeline,
|
pipeline=pipeline,
|
||||||
restore_faces=restore_faces,
|
|
||||||
adaptive_polish=smoothing, # adaptive (detail-targeted) polish when a smoothing pass ran
|
adaptive_polish=smoothing, # adaptive (detail-targeted) polish when a smoothing pass ran
|
||||||
unsharp=0.0,
|
unsharp=0.0,
|
||||||
humanize=0.0,
|
humanize=0.0,
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ _auto_option = click.option(
|
|||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Auto-pick the pipeline, face restore, and adaptive polish from image content. "
|
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.",
|
"always wins. EXPERIMENTAL.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -192,9 +192,8 @@ def _apply_auto(
|
|||||||
ctx: click.Context,
|
ctx: click.Context,
|
||||||
source: Path,
|
source: Path,
|
||||||
pipeline: str,
|
pipeline: str,
|
||||||
restore_faces: bool,
|
|
||||||
adaptive_polish: bool,
|
adaptive_polish: bool,
|
||||||
) -> tuple[str, bool, bool]:
|
) -> tuple[str, bool]:
|
||||||
"""Resolve ``--auto``: plan the three content-adaptive modes (pipeline, face
|
"""Resolve ``--auto``: plan the three content-adaptive modes (pipeline, face
|
||||||
restore, adaptive polish) from the image, overriding only the ones the user left
|
restore, adaptive polish) from the image, overriding only the ones the user left
|
||||||
at their default (an explicit flag always wins). The fixed ``--unsharp``/
|
at their default (an explicit flag always wins). The fixed ``--unsharp``/
|
||||||
@@ -205,19 +204,17 @@ def _apply_auto(
|
|||||||
cfg = auto_config.plan(source)
|
cfg = auto_config.plan(source)
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
console.print(" Auto: could not read image; using defaults")
|
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:
|
def _is_default(name: str) -> bool:
|
||||||
return ctx.get_parameter_source(name) == click.core.ParameterSource.DEFAULT
|
return ctx.get_parameter_source(name) == click.core.ParameterSource.DEFAULT
|
||||||
|
|
||||||
if _is_default("pipeline"):
|
if _is_default("pipeline"):
|
||||||
pipeline = cfg.pipeline
|
pipeline = cfg.pipeline
|
||||||
if _is_default("restore_faces"):
|
|
||||||
restore_faces = cfg.restore_faces
|
|
||||||
if _is_default("adaptive_polish"):
|
if _is_default("adaptive_polish"):
|
||||||
adaptive_polish = cfg.adaptive_polish
|
adaptive_polish = cfg.adaptive_polish
|
||||||
console.print(f" Auto: {cfg.reason}")
|
console.print(f" Auto: {cfg.reason}")
|
||||||
return pipeline, restore_faces, adaptive_polish
|
return pipeline, adaptive_polish
|
||||||
|
|
||||||
|
|
||||||
def _warn_if_esrgan_unavailable(upscaler: str) -> None:
|
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.")
|
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]:
|
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."""
|
"""Pick a watermark bbox: detector's region if confident, else the default config slot."""
|
||||||
if det.confidence > 0.15:
|
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.",
|
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
|
||||||
)
|
)
|
||||||
@_controlnet_scale_option
|
@_controlnet_scale_option
|
||||||
@_restore_faces_options
|
|
||||||
@_min_resolution_option
|
@_min_resolution_option
|
||||||
@_unsharp_option
|
@_unsharp_option
|
||||||
@_upscaler_option
|
@_upscaler_option
|
||||||
@@ -619,8 +578,6 @@ def cmd_invisible(
|
|||||||
max_resolution: int,
|
max_resolution: int,
|
||||||
min_resolution: int,
|
min_resolution: int,
|
||||||
controlnet_scale: float,
|
controlnet_scale: float,
|
||||||
restore_faces: bool,
|
|
||||||
restore_faces_method: str,
|
|
||||||
upscaler: str,
|
upscaler: str,
|
||||||
auto: bool,
|
auto: bool,
|
||||||
adaptive_polish: bool,
|
adaptive_polish: bool,
|
||||||
@@ -643,7 +600,7 @@ def cmd_invisible(
|
|||||||
source = _validate_image(source)
|
source = _validate_image(source)
|
||||||
_warn_if_esrgan_unavailable(upscaler)
|
_warn_if_esrgan_unavailable(upscaler)
|
||||||
if auto:
|
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:
|
if output is None:
|
||||||
output = source.with_stem(source.stem + "_clean")
|
output = source.with_stem(source.stem + "_clean")
|
||||||
|
|
||||||
@@ -682,8 +639,6 @@ def cmd_invisible(
|
|||||||
min_resolution=min_resolution,
|
min_resolution=min_resolution,
|
||||||
upscaler=upscaler,
|
upscaler=upscaler,
|
||||||
vendor=vendor,
|
vendor=vendor,
|
||||||
restore_faces=restore_faces,
|
|
||||||
restore_faces_method=restore_faces_method,
|
|
||||||
)
|
)
|
||||||
elapsed = time.monotonic() - t0
|
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.",
|
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
|
||||||
)
|
)
|
||||||
@_controlnet_scale_option
|
@_controlnet_scale_option
|
||||||
@_restore_faces_options
|
|
||||||
@_min_resolution_option
|
@_min_resolution_option
|
||||||
@_unsharp_option
|
@_unsharp_option
|
||||||
@_upscaler_option
|
@_upscaler_option
|
||||||
@@ -884,8 +838,6 @@ def cmd_all(
|
|||||||
max_resolution: int,
|
max_resolution: int,
|
||||||
min_resolution: int,
|
min_resolution: int,
|
||||||
controlnet_scale: float,
|
controlnet_scale: float,
|
||||||
restore_faces: bool,
|
|
||||||
restore_faces_method: str,
|
|
||||||
upscaler: str,
|
upscaler: str,
|
||||||
auto: bool,
|
auto: bool,
|
||||||
adaptive_polish: bool,
|
adaptive_polish: bool,
|
||||||
@@ -905,7 +857,7 @@ def cmd_all(
|
|||||||
source = _validate_image(source)
|
source = _validate_image(source)
|
||||||
_warn_if_esrgan_unavailable(upscaler)
|
_warn_if_esrgan_unavailable(upscaler)
|
||||||
if auto:
|
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:
|
if output is None:
|
||||||
output = source.with_stem(source.stem + "_clean")
|
output = source.with_stem(source.stem + "_clean")
|
||||||
@@ -993,8 +945,6 @@ def cmd_all(
|
|||||||
min_resolution=min_resolution,
|
min_resolution=min_resolution,
|
||||||
upscaler=upscaler,
|
upscaler=upscaler,
|
||||||
vendor=vendor,
|
vendor=vendor,
|
||||||
restore_faces=restore_faces,
|
|
||||||
restore_faces_method=restore_faces_method,
|
|
||||||
)
|
)
|
||||||
console.print(" Invisible watermark removed")
|
console.print(" Invisible watermark removed")
|
||||||
|
|
||||||
@@ -1049,8 +999,6 @@ def _process_batch_image(
|
|||||||
unsharp: float = 0.0,
|
unsharp: float = 0.0,
|
||||||
max_resolution: int = 0,
|
max_resolution: int = 0,
|
||||||
min_resolution: int = 1024,
|
min_resolution: int = 1024,
|
||||||
restore_faces: bool = False,
|
|
||||||
restore_faces_method: str = "instantid",
|
|
||||||
controlnet_scale: float = 1.0,
|
controlnet_scale: float = 1.0,
|
||||||
upscaler: str = "lanczos",
|
upscaler: str = "lanczos",
|
||||||
auto: bool = False,
|
auto: bool = False,
|
||||||
@@ -1104,9 +1052,7 @@ def _process_batch_image(
|
|||||||
# pipeline choice changes the engine ctor, so cache one engine per pipeline
|
# pipeline choice changes the engine ctor, so cache one engine per pipeline
|
||||||
# (controlnet vs default) rather than a single shared instance.
|
# (controlnet vs default) rather than a single shared instance.
|
||||||
if auto:
|
if auto:
|
||||||
pipeline, restore_faces, adaptive_polish = _apply_auto(
|
pipeline, adaptive_polish = _apply_auto(ctx, img_path, pipeline, adaptive_polish)
|
||||||
ctx, img_path, pipeline, restore_faces, adaptive_polish
|
|
||||||
)
|
|
||||||
engines = ctx.obj.setdefault("_inv_engines", {})
|
engines = ctx.obj.setdefault("_inv_engines", {})
|
||||||
if pipeline not in engines:
|
if pipeline not in engines:
|
||||||
engines[pipeline] = InvisibleEngine(
|
engines[pipeline] = InvisibleEngine(
|
||||||
@@ -1128,8 +1074,6 @@ def _process_batch_image(
|
|||||||
max_resolution=max_resolution,
|
max_resolution=max_resolution,
|
||||||
min_resolution=min_resolution,
|
min_resolution=min_resolution,
|
||||||
upscaler=upscaler,
|
upscaler=upscaler,
|
||||||
restore_faces=restore_faces,
|
|
||||||
restore_faces_method=restore_faces_method,
|
|
||||||
# Detect the vendor from the pristine original (`img_path`), not the
|
# Detect the vendor from the pristine original (`img_path`), not the
|
||||||
# visible-processed `out_path` whose C2PA is already gone.
|
# visible-processed `out_path` whose C2PA is already gone.
|
||||||
vendor=vendor_for_strength(img_path),
|
vendor=vendor_for_strength(img_path),
|
||||||
@@ -1187,7 +1131,6 @@ def _process_batch_image(
|
|||||||
default=0,
|
default=0,
|
||||||
help="Cap long side (px) before diffusion; 0 = native (best quality, like raiw.cc). Raise only on GPU/MPS OOM.",
|
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
|
@_min_resolution_option
|
||||||
@_unsharp_option
|
@_unsharp_option
|
||||||
@_upscaler_option
|
@_upscaler_option
|
||||||
@@ -1211,8 +1154,6 @@ def cmd_batch(
|
|||||||
unsharp: float,
|
unsharp: float,
|
||||||
max_resolution: int,
|
max_resolution: int,
|
||||||
min_resolution: int,
|
min_resolution: int,
|
||||||
restore_faces: bool,
|
|
||||||
restore_faces_method: str,
|
|
||||||
controlnet_scale: float,
|
controlnet_scale: float,
|
||||||
upscaler: str,
|
upscaler: str,
|
||||||
auto: bool,
|
auto: bool,
|
||||||
@@ -1271,8 +1212,6 @@ def cmd_batch(
|
|||||||
unsharp=unsharp,
|
unsharp=unsharp,
|
||||||
max_resolution=max_resolution,
|
max_resolution=max_resolution,
|
||||||
min_resolution=min_resolution,
|
min_resolution=min_resolution,
|
||||||
restore_faces=restore_faces,
|
|
||||||
restore_faces_method=restore_faces_method,
|
|
||||||
controlnet_scale=controlnet_scale,
|
controlnet_scale=controlnet_scale,
|
||||||
upscaler=upscaler,
|
upscaler=upscaler,
|
||||||
auto=auto,
|
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)
|
|
||||||
@@ -164,8 +164,6 @@ class InvisibleEngine:
|
|||||||
max_resolution: int = 0,
|
max_resolution: int = 0,
|
||||||
min_resolution: int = 1024,
|
min_resolution: int = 1024,
|
||||||
vendor: str | None = None,
|
vendor: str | None = None,
|
||||||
restore_faces: bool = False,
|
|
||||||
restore_faces_method: str = "instantid",
|
|
||||||
unsharp: float = 0.0,
|
unsharp: float = 0.0,
|
||||||
adaptive_polish: bool = False,
|
adaptive_polish: bool = False,
|
||||||
upscaler: str = "lanczos",
|
upscaler: str = "lanczos",
|
||||||
@@ -181,20 +179,9 @@ class InvisibleEngine:
|
|||||||
guidance_scale: Classifier-free guidance scale.
|
guidance_scale: Classifier-free guidance scale.
|
||||||
seed: Random seed for reproducibility.
|
seed: Random seed for reproducibility.
|
||||||
humanize: Intensity of Analog Humanizer film grain (0 = off).
|
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).
|
unsharp: Final unsharp-mask sharpening strength (0 = off, default).
|
||||||
Applied last (after face restoration) to counter the soft,
|
Applied last to counter the soft / over-smoothed look of the
|
||||||
over-smoothed look of the diffusion + restoration; ~0.5-0.8 is a
|
diffusion pass; ~0.5-0.8 is a safe range, higher risks edge halos.
|
||||||
safe range, higher risks edge halos.
|
|
||||||
adaptive_polish: When True (the --auto mode default), restore the input's
|
adaptive_polish: When True (the --auto mode default), restore the input's
|
||||||
detail level in the softened output instead of fixed unsharp/humanize:
|
detail level in the softened output instead of fixed unsharp/humanize:
|
||||||
a capped unsharp + edge-masked grain targeting the input's Laplacian
|
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)
|
out_cv = cv2.resize(out_cv, orig_size, interpolation=cv2.INTER_LANCZOS4)
|
||||||
image_io.imwrite(out_path, out_cv)
|
image_io.imwrite(out_path, out_cv)
|
||||||
|
|
||||||
# Optional GFPGAN face-polish post-pass: sharpens and re-synthesizes each
|
# Final sharpening.
|
||||||
# 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).
|
|
||||||
if unsharp > 0.0:
|
if unsharp > 0.0:
|
||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
@@ -364,99 +339,6 @@ class InvisibleEngine:
|
|||||||
if _tmp_path.exists():
|
if _tmp_path.exists():
|
||||||
_tmp_path.unlink()
|
_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(
|
def remove_watermark_batch(
|
||||||
self,
|
self,
|
||||||
input_dir: Path,
|
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)
|
|
||||||
@@ -64,7 +64,6 @@ class TestPlan:
|
|||||||
cfg = auto_config.plan(_write(flat, tmp_path))
|
cfg = auto_config.plan(_write(flat, tmp_path))
|
||||||
assert cfg is not None
|
assert cfg is not None
|
||||||
assert cfg.pipeline == "default" # structure-less -> plain SDXL
|
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.adaptive_polish is False # no smoothing pass -> no polish
|
||||||
assert cfg.unsharp == 0.0
|
assert cfg.unsharp == 0.0
|
||||||
assert cfg.humanize == 0.0
|
assert cfg.humanize == 0.0
|
||||||
@@ -78,13 +77,12 @@ class TestPlan:
|
|||||||
# Text creates edges above the structure-less floor -> controlnet preserves them.
|
# Text creates edges above the structure-less floor -> controlnet preserves them.
|
||||||
assert cfg.pipeline == "controlnet"
|
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)
|
monkeypatch.setattr(auto_config, "detect_face", lambda _img: True)
|
||||||
flat = np.full((300, 300, 3), 128, dtype=np.uint8)
|
flat = np.full((300, 300, 3), 128, dtype=np.uint8)
|
||||||
cfg = auto_config.plan(_write(flat, tmp_path))
|
cfg = auto_config.plan(_write(flat, tmp_path))
|
||||||
assert cfg is not None
|
assert cfg is not None
|
||||||
assert cfg.has_face
|
assert cfg.has_face
|
||||||
assert cfg.restore_faces
|
|
||||||
assert cfg.pipeline == "controlnet"
|
assert cfg.pipeline == "controlnet"
|
||||||
assert cfg.adaptive_polish # smoothing pass ran -> adaptive polish on
|
assert cfg.adaptive_polish # smoothing pass ran -> adaptive polish on
|
||||||
assert cfg.unsharp == 0.0 # fixed knobs off; the adaptive polish replaces them
|
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):
|
def test_reason_summarizes_plan(self):
|
||||||
cfg = auto_config.AutoConfig(
|
cfg = auto_config.AutoConfig(
|
||||||
pipeline="controlnet",
|
pipeline="controlnet",
|
||||||
restore_faces=True,
|
|
||||||
adaptive_polish=True,
|
adaptive_polish=True,
|
||||||
unsharp=0.0,
|
unsharp=0.0,
|
||||||
humanize=0.0,
|
humanize=0.0,
|
||||||
@@ -117,5 +114,4 @@ class TestReason:
|
|||||||
r = cfg.reason
|
r = cfg.reason
|
||||||
assert "controlnet" in r
|
assert "controlnet" in r
|
||||||
assert "face" in r
|
assert "face" in r
|
||||||
assert "face-restore on" in r
|
|
||||||
assert "adaptive polish" in r
|
assert "adaptive polish" in r
|
||||||
|
|||||||
@@ -523,7 +523,6 @@ class TestBatchCommand:
|
|||||||
output_dir = tmp_path / "output"
|
output_dir = tmp_path / "output"
|
||||||
plan = auto_config.AutoConfig(
|
plan = auto_config.AutoConfig(
|
||||||
pipeline="controlnet",
|
pipeline="controlnet",
|
||||||
restore_faces=True,
|
|
||||||
adaptive_polish=True,
|
adaptive_polish=True,
|
||||||
unsharp=0.0,
|
unsharp=0.0,
|
||||||
humanize=0.0,
|
humanize=0.0,
|
||||||
|
|||||||
@@ -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"])
|
|
||||||
@@ -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
|
|
||||||
@@ -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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "9.0.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "invisible-watermark"
|
name = "invisible-watermark"
|
||||||
version = "0.2.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "lightning"
|
name = "lightning"
|
||||||
version = "2.6.4"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "mpmath"
|
name = "mpmath"
|
||||||
version = "1.3.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "onnxruntime"
|
name = "onnxruntime"
|
||||||
version = "1.24.3"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "piexif"
|
name = "piexif"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@@ -2431,26 +2293,11 @@ gpu = [
|
|||||||
{ name = "torch" },
|
{ name = "torch" },
|
||||||
{ name = "transformers" },
|
{ 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 = [
|
lama = [
|
||||||
{ name = "huggingface-hub" },
|
{ 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.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 = "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 = [
|
trustmark = [
|
||||||
{ name = "trustmark" },
|
{ name = "trustmark" },
|
||||||
]
|
]
|
||||||
@@ -2460,21 +2307,12 @@ requires-dist = [
|
|||||||
{ name = "accelerate", marker = "extra == 'gpu'", specifier = ">=0.25.0" },
|
{ name = "accelerate", marker = "extra == 'gpu'", specifier = ">=0.25.0" },
|
||||||
{ name = "click", specifier = ">=8.0.0" },
|
{ name = "click", specifier = ">=8.0.0" },
|
||||||
{ name = "diffusers", marker = "extra == 'gpu'", specifier = ">=0.38.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 == '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 == 'detect'", specifier = ">=0.2.0" },
|
||||||
{ name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" },
|
{ name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" },
|
||||||
{ name = "numpy", specifier = ">=1.24.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 == 'lama'", specifier = ">=1.16.0" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'photomaker'", specifier = ">=1.16.0" },
|
|
||||||
{ name = "opencv-python-headless", specifier = ">=4.8.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 = "piexif", specifier = ">=1.1.3" },
|
||||||
{ name = "pillow", specifier = ">=10.0.0" },
|
{ name = "pillow", specifier = ">=10.0.0" },
|
||||||
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.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 = "transformers", marker = "extra == 'gpu'", specifier = ">=5,<6" },
|
||||||
{ name = "trustmark", marker = "extra == 'trustmark'", specifier = ">=0.8.0" },
|
{ 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]]
|
[[package]]
|
||||||
name = "requests"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "81.0.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "tokenizers"
|
name = "tokenizers"
|
||||||
version = "0.22.2"
|
version = "0.22.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user