Compare commits

..
13 Commits
Author SHA1 Message Date
Outis 2bd87dbf23 Merge pull request #69 from outis1one/claude/rename-paintplus-z9eg86
Rename remaining EditmaskwithAI references to PaintPlus
2026-07-30 19:51:20 -04:00
Claude 608d6958b1 Rename remaining EditmaskwithAI references to PaintPlus
Updates README, Docker/Compose comments and container name, install
script banner, about dialog GitHub link, and a script's cd path to
match the repo's new name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011g84QwMKwPMFQPwLVnPrQ1
2026-07-30 23:44:43 +00:00
Outis 4e43728583 Merge pull request #68 from outis1one/claude/focused-maxwell-3kgh3x
Make model prefetching automatic in install/run scripts
2026-06-18 13:58:54 -04:00
Claude 3f4a6b754c Make model prefetching automatic in install/run scripts
install-local-gpu.sh and bring-up-local-gpu.sh now call
prefetch-models.sh --sdxl (host-side download, all models including
SDXL/inpaint, ~13GB) automatically rather than treating it as a manual
DNS-troubleshooting step. install-local-gpu.sh drops back to the
invoking user via sudo -u "$SUDO_USER" -H before running it, since
that script itself runs as root and would otherwise leave downloaded
files root-owned. Both call sites are non-fatal: a prefetch failure
logs a warning and the container still starts, falling back to its
own in-container download attempt.

Updated README to match.
2026-06-18 17:47:18 +00:00
Outis f49718a260 Merge pull request #67 from outis1one/claude/focused-maxwell-3kgh3x
Fix stale UI cache, BEN2 edge quality, and fresh-install permissions
2026-06-18 13:23:20 -04:00
Claude b4a790473c Fix stale UI cache, BEN2 edge quality, and fresh-install permissions
- Serve /dist and /src with Cache-Control: no-cache so browsers always
  revalidate the webpack bundle (it has a fixed filename with no content
  hash, so a stale browser cache was hiding the new Remove Background
  model dropdown after the rebuild).
- Enable BEN2's refine_foreground pass by default - it's the model's own
  documented option for preserving fine/semi-transparent edge detail
  (text borders, light rays) instead of a harder cutout, at a small
  speed cost.
- Make install-local-gpu.sh and bring-up-local-gpu.sh pre-create ./data
  as the invoking non-root user before Docker ever runs, so its daemon
  never gets a chance to auto-create those bind-mount dirs as root.
- Make prefetch-models.sh self-heal a root-owned ./data automatically
  (sudo chown) instead of erroring out with a manual fix-it command.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ro4PwQKvSc3CH19LSN21Ht
2026-06-18 17:09:23 +00:00
Outis e96d7cc204 Merge pull request #66 from outis1one/claude/focused-maxwell-3kgh3x
Fix prefetch-models.sh write-permission detection and PEP 668 pip ins…
2026-06-18 12:44:50 -04:00
Claude eca834a7f0 Fix prefetch-models.sh write-permission detection and PEP 668 pip install
The mkdir -p preflight check only verified that data/models and
data/hf_cache could be created, which succeeds vacuously on a directory
that already exists — exactly the case when Docker created ./data as
root on a prior run. Confirmed in practice: U2Net's download reached the
network fine but failed writing the file with Permission denied. Now
test actual writability and point at the one-time fix (chown ./data back
to the calling user) instead of suggesting sudo re-runs that would leave
the ownership problem in place permanently.

Also handle pip's PEP 668 "externally-managed-environment" error
(Debian/Ubuntu 12+) when installing huggingface_hub, retrying with
--break-system-packages — safe here since --user already keeps the
install out of apt-managed system site-packages.
2026-06-18 16:43:55 +00:00
Outis b8523cb729 Merge pull request #65 from outis1one/claude/focused-maxwell-3kgh3x
Fix SAM symlink crash and make prefetch-models.sh resilient per-step
2026-06-18 12:40:34 -04:00
Claude 851e255641 Fix SAM symlink crash and make prefetch-models.sh resilient per-step
download_sam_model.py crashed with an uncaught PermissionError when
data/models/ is root-owned (common after a prior Docker run) and a
non-root host user tries to recreate the convenience sam_model.pth
symlink — observed in the wild, and it aborted the whole prefetch run
before U2Net/BEN2/BiRefNet-HR were ever attempted. Worse, the same
unguarded symlink call sat inside the post-download try/except, so a
successful download could get deleted just because the symlink step
failed afterward. Wrapped symlink creation in a shared helper that
warns and continues instead of raising — the real model file already
satisfies entrypoint.sh's checks regardless of the symlink.

prefetch-models.sh now treats SAM, U2Net, and the HuggingFace models as
independent steps (one failing no longer aborts the rest) and prints a
summary of which steps failed, so a single run gives full diagnostic
signal instead of stopping at the first error.
2026-06-18 16:33:17 +00:00
Outis b70f95ede5 Merge pull request #64 from outis1one/claude/focused-maxwell-3kgh3x
Add prefetch-models.sh to download AI models on host when container D…
2026-06-18 12:22:14 -04:00
Outis f58c96c20f Merge pull request #63 from outis1one/claude/focused-maxwell-3kgh3x
Claude/focused maxwell 3kgh3x
2026-06-18 10:39:24 -04:00
Outis 668728348b Merge pull request #62 from outis1one/claude/focused-maxwell-3kgh3x
Auto-download U2Net model on startup; remove dead PyTorch U2Net module
2026-06-18 09:29:16 -04:00
12 changed files with 176 additions and 64 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# Caddy 2 Configuration for EditmaskwithAI
# Caddy 2 Configuration for PaintPlus
# ==========================================
#
# SETUP INSTRUCTIONS:
+1 -1
View File
@@ -1,5 +1,5 @@
# =============================================================================
# EditmaskwithAI — GPU Container (NVIDIA CUDA)
# PaintPlus — GPU Container (NVIDIA CUDA)
#
# Usage:
# docker compose -f docker-compose.gpu.yml up --build
+10 -10
View File
@@ -1,4 +1,4 @@
# EditmaskwithAI
# PaintPlus
A self-hosted, web-based AI photo editor. Paint over any object, describe what you want, and the AI replaces just that region — every pixel outside your selection stays untouched.
@@ -7,11 +7,11 @@ A self-hosted, web-based AI photo editor. Paint over any object, describe what y
### GPU machine (recommended — free inference, best quality)
```bash
git clone https://github.com/outis1one/editmaskwithai
cd editmaskwithai
git clone https://github.com/outis1one/paintplus
cd paintplus
# One-time setup: installs nvidia-container-toolkit, configures Docker,
# and sets up a permanent DNS fix so the container can download models.
# sets up a permanent DNS fix, and prefetches all AI models on the host.
chmod +x install-local-gpu.sh
./install-local-gpu.sh
@@ -22,15 +22,15 @@ chmod +x bring-up-local-gpu.sh
Open **http://localhost:3080**
**First startup downloads the AI model for your GPU (~13 GB, one time).** Models are cached in `./data/hf_cache/` and survive rebuilds.
**Models (~13 GB total, one time) download automatically on the host**, outside Docker — both scripts call `./prefetch-models.sh` for you, since in-container DNS is unreliable on some hosts. They're cached in `./data/hf_cache/` and `./data/models/`, and survive rebuilds.
---
### Cloud API (no GPU required)
```bash
git clone https://github.com/outis1one/editmaskwithai
cd editmaskwithai
git clone https://github.com/outis1one/paintplus
cd paintplus
cp .env.example .env
# Edit .env: set AI_PROVIDER and your API key (see .env.example for options)
docker compose up -d --build
@@ -140,7 +140,7 @@ docker compose logs -f
## File structure
```
EditmaskwithAI/
PaintPlus/
├── backend/
│ ├── app/
│ │ ├── routers/ # API endpoints (ai_tools, print_tools, …)
@@ -163,7 +163,7 @@ EditmaskwithAI/
├── Dockerfile.gpu
├── install-local-gpu.sh # One-time GPU host setup
├── bring-up-local-gpu.sh # Start/stop the GPU container
├── prefetch-models.sh # Download AI models on the host (DNS-blocked workaround)
├── prefetch-models.sh # Download AI models on the host (called automatically; also runnable standalone)
└── .env.example
```
@@ -232,7 +232,7 @@ You can also pick a specific model per-edit from the Remove Background dialog's
**AI models not downloading (container DNS blocked)**
Easiest fix: download the models on the host instead of inside the container — they land in `./data/`, which is already bind-mounted into the container, so it picks them up with no rebuild:
`install-local-gpu.sh` and `bring-up-local-gpu.sh` already run this for you automatically on every start, so you normally don't need to think about it. If a model still didn't download (no network at the time, etc.), re-run it manually — it lands in `./data/`, which is already bind-mounted into the container, so it's picked up with no rebuild:
```bash
./prefetch-models.sh # SAM + U2Net + BEN2 + BiRefNet-HR (~1.5GB)
+17 -6
View File
@@ -103,20 +103,31 @@ def health():
STATIC_DIR = Path("/app/static")
class NoCacheStaticFiles(StaticFiles):
"""webpack outputs a fixed 'bundle.js' filename (no content hash), so
browsers can keep serving a stale cached copy after a rebuild unless
forced to revalidate on every request."""
def file_response(self, *args, **kwargs):
response = super().file_response(*args, **kwargs)
response.headers["Cache-Control"] = "no-cache"
return response
# Serve static assets - mount subdirectories if they exist
if STATIC_DIR.exists():
# React-style assets folder
if (STATIC_DIR / "assets").exists():
app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets")
# miniPaint dist folder (webpack bundle)
# miniPaint dist folder (webpack bundle) - no-cache so code updates are picked up immediately
if (STATIC_DIR / "dist").exists():
app.mount("/dist", StaticFiles(directory=STATIC_DIR / "dist"), name="dist")
app.mount("/dist", NoCacheStaticFiles(directory=STATIC_DIR / "dist"), name="dist")
# miniPaint images folder
if (STATIC_DIR / "images").exists():
app.mount("/images", StaticFiles(directory=STATIC_DIR / "images"), name="images")
# miniPaint CSS folder
# miniPaint CSS folder - no-cache, same reasoning as /dist
if (STATIC_DIR / "src").exists():
app.mount("/src", StaticFiles(directory=STATIC_DIR / "src"), name="src")
app.mount("/src", NoCacheStaticFiles(directory=STATIC_DIR / "src"), name="src")
@app.get("/", response_class=HTMLResponse)
@@ -124,7 +135,7 @@ async def serve_spa():
"""Serve miniPaint index.html"""
index_path = STATIC_DIR / "index.html"
if index_path.exists():
return FileResponse(index_path)
return FileResponse(index_path, headers={"Cache-Control": "no-cache"})
return HTMLResponse("<h1>Frontend not built. Run npm build in frontend/</h1>")
@@ -146,6 +157,6 @@ async def serve_spa_routes(request: Request, full_path: str):
# Otherwise serve index.html
index_path = STATIC_DIR / "index.html"
if index_path.exists():
return FileResponse(index_path)
return FileResponse(index_path, headers={"Cache-Control": "no-cache"})
return HTMLResponse("<h1>Frontend not built</h1>", status_code=404)
+4 -1
View File
@@ -369,7 +369,10 @@ async def _remove_background_ben2(img: Image.Image) -> bytes:
_ben2_model.to(device).eval()
print("BEN2_Base model loaded")
result = _ben2_model.inference(img.convert('RGB'), refine_foreground=False)
# refine_foreground=True runs BEN2's extra foreground-color refinement pass
# (slower, but recovers fine/semi-transparent edge detail instead of a hard
# cutout — matters for things like lace, light rays, or fine text borders).
result = _ben2_model.inference(img.convert('RGB'), refine_foreground=True)
buffer = BytesIO()
result.save(buffer, format='PNG')
+17 -3
View File
@@ -4,9 +4,11 @@
# Run this each time you want to start the app.
# Run ./install-local-gpu.sh once first on a new machine.
#
# If models fail to download inside the container (DNS/firewall blocked),
# run ./prefetch-models.sh first — it downloads them on the host into
# ./data/, which this container already bind-mounts, so no rebuild needed.
# Before starting, this fetches any missing models on the host (outside
# Docker) via ./prefetch-models.sh — in-container DNS/network is unreliable
# on some hosts, so this is the default now, not a manual troubleshooting
# step. It never blocks startup: if it fails (no network, no python3, etc.)
# the container still starts and falls back to its own in-container download.
#
# Usage:
# ./bring-up-local-gpu.sh # start (detached, rebuild if needed)
@@ -19,7 +21,19 @@
set -euo pipefail
cd "$(dirname "$0")"
# Pre-create ./data as the current (non-root) user. Otherwise, on a fresh
# checkout, Docker's daemon (root) auto-creates these bind-mount sources on
# the first 'up' — leaving them root-owned and blocking this same user from
# later writing to them without sudo (e.g. ./prefetch-models.sh). No-op if
# they already exist, regardless of current ownership.
mkdir -p data/models data/hf_cache data/projects data/patches
if [ $# -eq 0 ]; then
# Best-effort: fetch any missing models on the host first (see header).
./prefetch-models.sh --sdxl \
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
exec docker compose -f docker-compose.gpu.yml up -d --build
else
exec docker compose -f docker-compose.gpu.yml "$@"
+2 -2
View File
@@ -1,5 +1,5 @@
# =============================================================================
# EditmaskwithAI — GPU Docker Compose (NVIDIA CUDA)
# PaintPlus — GPU Docker Compose (NVIDIA CUDA)
#
# ── PREREQUISITES ─────────────────────────────────────────────────────────────
#
@@ -66,7 +66,7 @@ services:
# Increment BUILDID to force pip layers to re-run without full --no-cache:
# BUILDID=$(date +%s) docker compose -f docker-compose.gpu.yml up --build
BUILDID: ${BUILDID:-1}
container_name: editmaskwithai-gpu
container_name: paintplus-gpu
ports:
- "${PORT:-3080}:8000"
volumes:
+1 -1
View File
@@ -25,7 +25,7 @@ class Help_about_class {
{title: "Smart Select:", html: '<a href="https://github.com/facebookresearch/segment-anything" target="_blank">SAM</a> (Meta AI)'},
{title: "Remote AI:", html: 'InvokeAI · ComfyUI · OpenAI (user-configured)'},
{title: "", html: '<hr style="margin:8px 0;border-color:#444;">'},
{title: "GitHub:", html: '<a href="https://github.com/outis1one/EditmaskwithAI" target="_blank">outis1one/EditmaskwithAI</a>'},
{title: "GitHub:", html: '<a href="https://github.com/outis1one/PaintPlus" target="_blank">outis1one/PaintPlus</a>'},
],
};
this.POP.show(settings);
+35 -4
View File
@@ -19,7 +19,7 @@ if [ "$EUID" -ne 0 ]; then
fi
echo "=================================================="
echo " EditmaskwithAI — Local GPU one-time setup"
echo " PaintPlus — Local GPU one-time setup"
echo "=================================================="
echo ""
@@ -80,6 +80,18 @@ fi
echo ""
# ── 0.5. Pre-create ./data with correct ownership ────────────────────────────
# Docker's daemon (always root) auto-creates bind-mount source directories on
# the first 'compose up' if they don't exist yet — leaving ./data root-owned
# and blocking the invoking user from later running ./prefetch-models.sh
# without a manual 'sudo chown'. Create it now, owned by the real (non-root)
# user, so that problem has no chance to happen on a fresh checkout.
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
mkdir -p "$REPO_DIR"/data/{models,hf_cache,projects,patches}
chown -R "${SUDO_UID:-$(id -u)}:${SUDO_GID:-$(id -g)}" "$REPO_DIR/data"
echo "✓ ./data prepared (writable without sudo)"
echo ""
# ── 1. NVIDIA container toolkit ──────────────────────────────────────────────
if command -v nvidia-ctk &>/dev/null; then
echo "✓ nvidia-container-toolkit already installed — skipping"
@@ -158,12 +170,31 @@ else
echo " Minimum driver version: 525"
fi
# ── 6. Prefetch all AI models (host-side, outside Docker) ───────────────────
# In-container DNS/network is unreliable on some hosts, so downloading on the
# host up front (including SDXL/inpaint, ~13GB) is the default now rather
# than a manual troubleshooting step. Best-effort: this script must finish
# (and leave the GPU/Docker setup done) even if prefetch fails outright.
# Run as the real invoking user, not root, so downloaded files (and any
# `pip install --user` side effects) end up owned by that user — this script
# itself is already running as root via the sudo re-exec above.
echo ""
echo "Prefetching AI models (this can take a while for SDXL, ~13GB)..."
if [ -n "${SUDO_USER:-}" ]; then
sudo -u "$SUDO_USER" -H "$REPO_DIR/prefetch-models.sh" --sdxl \
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
else
"$REPO_DIR/prefetch-models.sh" --sdxl \
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
fi
echo ""
echo "=================================================="
echo " Setup complete."
echo " Start the app with: ./bring-up-local-gpu.sh"
echo ""
echo " If models fail to download inside the container (DNS/firewall"
echo " blocked), fetch them on the host first instead:"
echo " ./prefetch-models.sh"
echo " Models are prefetched automatically by this script and by"
echo " bring-up-local-gpu.sh on every start. If any failed above (no"
echo " network, etc.), re-run manually any time:"
echo " ./prefetch-models.sh --sdxl"
echo "=================================================="
+71 -23
View File
@@ -12,9 +12,11 @@
# ./prefetch-models.sh --sdxl # also prefetch SDXL base + inpaint (~13GB)
#
# Safe to re-run: every download here skips files that already exist
# (HuggingFace Hub) or are already present (SAM/U2Net).
# (HuggingFace Hub) or are already present (SAM/U2Net). Each model is
# independent — one failing (e.g. no network reachable at all) doesn't
# block the others from being attempted.
set -euo pipefail
set -uo pipefail
cd "$(dirname "$0")"
@@ -24,11 +26,32 @@ if ! command -v python3 &>/dev/null; then
exit 1
fi
if ! mkdir -p data/models data/hf_cache 2>/dev/null; then
echo "✗ Could not create ./data/models or ./data/hf_cache." >&2
echo " If ./data/ was already created by Docker (root-owned), re-run with sudo:" >&2
echo " sudo ./prefetch-models.sh $*" >&2
exit 1
# mkdir -p succeeds silently on an already-existing directory even when we
# can't write into it, so actually test writability rather than trusting that.
check_writable() {
mkdir -p "$1" 2>/dev/null
touch "$1/.write_test" 2>/dev/null && rm -f "$1/.write_test"
}
NEED_CHOWN=0
for d in data/models data/hf_cache; do
check_writable "$d" || NEED_CHOWN=1
done
if [ "$NEED_CHOWN" -eq 1 ]; then
echo "⚠ ./data isn't writable by $(id -un) — this usually means Docker created it as root on a previous run."
echo " Fixing ownership (the container runs as root and will still work fine afterward):"
echo " sudo chown -R $(id -u):$(id -g) ./data"
if ! sudo chown -R "$(id -u):$(id -g)" ./data; then
echo "✗ Could not fix ownership automatically (sudo failed or unavailable)." >&2
echo " Run this manually, then re-run this script:" >&2
echo " sudo chown -R \$(id -u):\$(id -g) ./data" >&2
exit 1
fi
for d in data/models data/hf_cache; do
check_writable "$d" || { echo "✗ Still no write permission in ./$d after chown." >&2; exit 1; }
done
echo "✓ Fixed."
fi
PREFETCH_SDXL=0
@@ -36,36 +59,50 @@ if [ "${1:-}" = "--sdxl" ]; then
PREFETCH_SDXL=1
fi
FAILED=()
echo "=================================================="
echo " Prefetching AI models (host-side, no Docker)"
echo "=================================================="
echo ""
echo "── SAM (Smart Select) ───────────────────────────────"
python3 scripts/download_sam_model.py vit_b
python3 scripts/download_sam_model.py vit_b || FAILED+=("SAM")
echo ""
echo "── U2Net (Remove Background fallback) ──────────────"
python3 scripts/download_u2net_model.py u2net
python3 scripts/download_u2net_model.py u2net || FAILED+=("U2Net")
echo ""
echo "── HuggingFace Hub models (BEN2, BiRefNet-HR) ───────"
if ! python3 -c "import huggingface_hub" &>/dev/null; then
echo "Installing huggingface_hub (lightweight — no torch/GPU needed for this step)..."
python3 -m pip install --quiet --user "huggingface_hub>=0.23.0"
PIP_ERR=$(python3 -m pip install --quiet --user "huggingface_hub>=0.23.0" 2>&1) || {
if echo "$PIP_ERR" | grep -q "externally-managed-environment"; then
# PEP 668 (Debian/Ubuntu 12+): --user already keeps this out of
# apt-managed system site-packages, so overriding here is safe.
echo "System Python is externally managed — retrying with --break-system-packages"
python3 -m pip install --quiet --user --break-system-packages "huggingface_hub>=0.23.0" \
|| FAILED+=("huggingface_hub install")
else
echo "$PIP_ERR" >&2
FAILED+=("huggingface_hub install")
fi
}
fi
# HF_HOME must match what the container resolves by default: the bind mount
# maps ./data/hf_cache -> /root/.cache/huggingface, and the container never
# sets HF_HOME explicitly, so it defaults to ~/.cache/huggingface there.
# huggingface_hub itself appends "/hub" to HF_HOME to get the actual cache
# root (HF_HUB_CACHE) — setting HF_HOME here (instead of passing --cache-dir
# or cache_dir=... directly) lets both sides derive that "/hub" nesting the
# same way, rather than us hardcoding it and risking a mismatch.
export HF_HOME="$(pwd)/data/hf_cache"
if python3 -c "import huggingface_hub" &>/dev/null; then
# HF_HOME must match what the container resolves by default: the bind mount
# maps ./data/hf_cache -> /root/.cache/huggingface, and the container never
# sets HF_HOME explicitly, so it defaults to ~/.cache/huggingface there.
# huggingface_hub itself appends "/hub" to HF_HOME to get the actual cache
# root (HF_HUB_CACHE) — setting HF_HOME here (instead of passing --cache-dir
# or cache_dir=... directly) lets both sides derive that "/hub" nesting the
# same way, rather than us hardcoding it and risking a mismatch.
export HF_HOME="$(pwd)/data/hf_cache"
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF'
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF' || FAILED+=("HuggingFace models")
import os
from huggingface_hub import snapshot_download
@@ -81,12 +118,23 @@ for repo_id in repos:
snapshot_download(repo_id=repo_id, ignore_patterns=["*.msgpack", "flax_*", "tf_*"])
print(f" done: {repo_id}")
PYEOF
else
echo "⚠ Skipping BEN2/BiRefNet-HR — huggingface_hub unavailable (install failed above)"
FAILED+=("HuggingFace models")
fi
echo ""
echo "=================================================="
echo " Done. Models cached under ./data/models and ./data/hf_cache"
if [ "$PREFETCH_SDXL" != "1" ]; then
echo " (SDXL not included — re-run with --sdxl to also prefetch txt2img/inpaint, ~13GB)"
if [ ${#FAILED[@]} -eq 0 ]; then
echo " Done. Models cached under ./data/models and ./data/hf_cache"
if [ "$PREFETCH_SDXL" != "1" ]; then
echo " (SDXL not included — re-run with --sdxl to also prefetch txt2img/inpaint, ~13GB)"
fi
echo " Start the app: ./bring-up-local-gpu.sh"
else
echo " Finished with failures: ${FAILED[*]}"
echo " If ALL of the above failed, this host can't reach the internet right now"
echo " (check: curl -v https://github.com) — that's a host/network issue, not Docker."
echo " If only some failed, re-run this script to retry just those."
fi
echo " Start the app: ./bring-up-local-gpu.sh"
echo "=================================================="
+16 -11
View File
@@ -40,6 +40,20 @@ SAM_MODELS = {
}
}
def create_symlink(symlink_path: Path, target_name: str):
"""Best-effort convenience symlink. Never raises — a missing/stale
symlink is harmless (callers also check the real filename directly),
but data/models/ is often root-owned from a prior Docker run, which
makes unlink/symlink_to fail with PermissionError for other users."""
try:
if symlink_path.exists() or symlink_path.is_symlink():
symlink_path.unlink()
symlink_path.symlink_to(target_name)
print(f"Symlink created: {symlink_path} -> {target_name}")
except OSError as e:
print(f"(skipping symlink: {e})")
def download_with_progress(url: str, dest_path: Path):
"""Download file with progress indicator"""
print(f"Downloading to: {dest_path}")
@@ -88,12 +102,7 @@ def main():
print(f"\nModel already exists at: {dest_path}")
print("To re-download, delete the file first.")
# Create symlink for easy access
symlink_path = models_dir / 'sam_model.pth'
if symlink_path.exists() or symlink_path.is_symlink():
symlink_path.unlink()
symlink_path.symlink_to(dest_path.name)
print(f"Symlink created: {symlink_path} -> {dest_path.name}")
create_symlink(models_dir / 'sam_model.pth', dest_path.name)
return
print(f"\nDownloading SAM {model_type.upper()} ({model_info['size']})...")
@@ -103,17 +112,13 @@ def main():
try:
download_with_progress(model_info['url'], dest_path)
# Create symlink for easy access
symlink_path = models_dir / 'sam_model.pth'
if symlink_path.exists() or symlink_path.is_symlink():
symlink_path.unlink()
symlink_path.symlink_to(dest_path.name)
create_symlink(symlink_path, dest_path.name)
print()
print("=" * 60)
print("SUCCESS!")
print(f"Model saved to: {dest_path}")
print(f"Symlink: {symlink_path}")
print("=" * 60)
except Exception as e:
+1 -1
View File
@@ -6,7 +6,7 @@ These are public domain images from Wikimedia Commons of classical sculptures.
Run this script to populate the eye catalog with example eyes.
Usage:
cd /home/user/EditmaskwithAI
cd /home/user/PaintPlus
python scripts/download_sample_eyes.py
"""