Spaces:
Sleeping
Sleeping
Commit History
-
Revert this session's changes back to the last known-good version
Restores the tree to 4b38f72 exactly. The reported result was worse than the version that was already working, across the swap output, the head swap and the app generally, so the right move is to put the working version back rather than keep patching on top of it. Reverts, newest first: f008eb7 head-swap pose gate + warnings in the UI 0ab4ea6 seam blend against base instead of target b1cd367 workflow PDF + binary .gitattributes rules f25d3bf glasses transfer made opt-in 74dd70d hair matte fix + removal of the crude hair fallback b60d8aa background jobs + Head Swap toggle ddb0022 head-only super-resolution 27e2136 composite onto the full-resolution original 9e549b6 grain confined to the parsed head bb3e953 Control Panel sign-in inside the Hugging Face iframe e522792 Flask debug off by default + threaded pre-warm 27bac33 scene-harmonisation regressions Nothing is lost: each of the above is still in history and can be cherry-picked back individually o…f4e824d -
Refuse a head swap when the two heads face different ways, and say so
full_head_swap fits a 2D similarity transform, so it can slide, spin and scale the source head but cannot turn it. Transplanting a head that is nodded down onto a body with its chin up lands visibly crooked however well the seam is blended, and nothing downstream can correct it. Measured against the Library target (pitch +14, yaw 0): of four test photos, one differed by 26 degrees of pitch, one by 27 of yaw, one by 30 and 20. Those are exactly the results that come out misaligned. The pose delta is now checked before transplanting. Past 25 degrees of pitch or yaw the head swap is skipped and the clean face swap is used instead; between 15 and 25 it runs but says the alignment may be imperfect. Roll is excluded on purpose — it is in-plane and the transform already corrects it. A 20-degree case was checked by eye and still looks right, so the cut sits above it. The frontend never rendered the warnings array at all, so this explanation — and the pre-existing "face is very small" notes …f008eb7 -
Stop the seam blend from erasing the head swap
With the Head Swap toggle on, the output was indistinguishable from an ordinary face swap. The head transplant ran, but step 3b then blended the swapped face against `target` — the ORIGINAL scene — rather than against `base`, the image the head swap had just produced. Everywhere outside the face mask, which is exactly where the transplanted head shape and hair live, the result was pulled back to the untouched target. Measured on the Library target: full_head_swap changes the frame by mean 0.90, but only 0.29 survived the blend — roughly two thirds of the effect discarded. Blending against `base` retains 0.90, and end to end the toggle now changes 1.69% of pixels by more than 15 levels instead of producing the same picture. `base` is `target` whenever the head swap is off, so nothing changes there. The file's own comment already warned about this trap for the hair step; the same one was left open for the head swap. Also drops the composite feather from 0.025 to 0.012, which measured …0ab4ea6 -
Fix the empty hair matte, and stop pasting hair when HairFastGAN is unavailable
Two separate hair problems, found by running the pipeline on real photos. _soft_hair_matte parsed a very wide crop so long hair wouldn't be cut off before parsing, but that also shrinks the head inside the 512x512 parse input and takes BiSeNet's hair confidence down with it. On a 66x77 face the matte peaked at 0.341 — nothing at all above the 0.4 that swap_hair thresholds at — so the mask came out empty and hair transfer silently did nothing at all. It now keeps the wide crop when it is confident and tightens only when it is not; across five test photos the matte went from 0.341/0.00% usable to 0.75-0.84, all usable. That alone made things worse, though: with a mask to work from, the fallback path warps the source's hair straight into the scene and composites a smeared, semi-transparent blob over the head — the pasted-hair artifact this project has been chasing. It is plainly worse than keeping the target's hair, which the clean face swap already produces. So the fallback is gone. H…74dd70d
-
Super-resolve the head only, which makes real SR affordable on CPU
RealESRGAN was GPU-only: on CPU a 4x pass over the full frame takes ~20 minutes here, so the free tier fell back to a plain Lanczos resize and the "4K" download was pure interpolation. Now that everything outside the changed region is composited straight from the full-resolution original, the background is already native and has nothing for a super-resolution model to reconstruct. Only the head came through the swap's 128x128 bottleneck, so RealESRGAN now runs on that crop alone and Lanczos covers the rest, feathered at the crop edge. Measured on the Library target (CPU, 1086x1448 -> 4344x5792): head-only SR takes 370s against ~1200s for the full frame, and high-frequency detail in the face region rises from 5.9 to 19.9. GPU behaviour is unchanged when no bbox is given. Not yet safe to deploy synchronously — 370s exceeds the request timeout. The async job endpoint lands next.ddb0022 -
Confine scene grain to the parsed head instead of a box around it
harmonize_to_scene built its mask from a rectangle around the face bbox, so the grain meant for the GAN-smooth face and hair was sprayed over everything sharing those bounds — background, bookshelves, clothing — dirtying the scene and leaving the box's own feathered edge visible in the result. It now parses the head/hair/neck shape (clamped to a generous head box so stray specks elsewhere can't pick up grain) and feathers it narrowly, just enough to fade at the hairline. Class 18 (hat) is included here only: the parser labels part of the hair as hat, and dropping it left an ungrained hole in the top of the head. _HEAD_CLASSES still excludes it for hair transfer, where it misfires. Measured on a self-swap of the Library target (768x1024, CPU), where the output should barely differ from the input: the mean pixel change from this step falls from 6.46 to 0.33, and the masked region from roughly half the frame to 2.4%. Background, shirt and shelves now come through untouched.9e549b6 -
Fix scene-harmonisation regressions: quality scores, skin-tone reversal, long-hair gap
Three bugs in the harmonise pass (step 8) and its wiring: 1. Grain was injected BEFORE compute_quality_score ran on the same image, so the deliberate noise fed straight into the naturalness noise-penalty and the blend discontinuity term — the "make it look photographed" step was lowering the app's own reported quality on every swap. Quality + alignment are now scored first; harmonise still runs last before the output is encoded, so the grain survives into both the preview and the 4K download. 2. The colour cast sampled scene_mean over the WHOLE target frame, which still contains the original target face (target is never overwritten by the swap). That dragged the head's colour back toward the tone of the face it replaced, partially undoing match_skin_to_source one step earlier. It now samples only outside the head mask, and works in LAB with the same darken-only L rule as _match_lighting, so a bright background can't wash hair/skin to grey/silver. 3. The…27bac33
-
Add scene-harmonisation final pass: match the photo's grain + colour cast over the swapped head so the GAN-smooth face/hair stop looking synthetic/pasted and read as photographed with the scene
4b38f72 -
Hair robustness (100-img batch): lighting match only DARKENS hair (no grey wash on bright bg) + conservative grey-edge trim that won't cut lit/highlighted hair
1addd52
-
Fix facial discolour patches: restrict the neck/body skin pass to strictly below the chin (it was recolouring lips + cheek-ring -> patches on the face)
c5fbe15 -
Hair edge: soft strand edge for both paths (loosen clamp) + remove portrait grey crown by COLOUR not cropping -> no website grey line AND no hard cut-out, local/HF identical
5f1915f -
Fix website grey hair line: raise scene-fill threshold + clamp matte to confident-hair extent (kills the faint grey crown haze); drop guided filter for local/HF parity (no ximgproc on HF)
2cf928a -
Hair/forehead high-precision edge via guided filter (edge-aware matte) + gentle neck/body skin pass; opencv-contrib-headless on HF for ximgproc
1544cc7 -
Hair edge: composite with BiSeNet SOFTMAX hair matte (soft strand alpha) instead of a feathered binary mask -> real wispy hair edges, no cut-paste look, both genders
6ae45af -
Hair edge: smoother feather (0.025) for natural soft edges both genders, no hard cut-off; gentler lighting keeps it grey-free
324e77e -
Hair edge: crisper feather + erode-to-solid + gentler lighting match -> natural hairline, no grey fringe on dark hair
0d2a722 -
Hair blend: fill portrait grey padding with the scene before feathering (kills the blurry pasted halo) + tighter edge feather -> natural hair edges
34e8dd6
-
Hair swap: run Laplacian blend BEFORE hair (it was overwriting transferred hair with target's); opaque core + soft edge + recolour residual old hair for a seamless blend, both genders
e8b68af -
Skin tone: single uniform LAB offset over whole skin (no gate/per-region) -> no patches; verified clean across 10 tones light-to-dark, strength 0.92
8478059 -
Skin tone: recolour face-skin + body to SOURCE complexion (sampled from clean parsed skin, lips/eyes excluded, relative luminance gate) — clean on normal faces
ddb96a3 -
Skin tone: leave face untouched (swap already source-toned), only lift neck/arms/hands to match face — fixes blue/blotchy face patch
e711814 -
Skin tone: drive face+neck+body to SOURCE complexion (per-region delta), with luminance gate so beard/brows don't go blue
e3164c8 -
Skin tone: blend across whole body (face+neck+arms+hands) via adaptive chroma mask, clothes/background excluded
9d2db57 -
Fix close-up selfie detection: pad margin so face-fills-frame photos are found (0 faces -> crude paste was the no-hair/skin bug)
f0e4246 -
Fix hair composite: widen parse crop + region clamp so long hair transfers into scene (re-enable hair by default)
1940cda
-
Auto de-roll tilted source faces (eye-line alignment) before swap
8eed89d -
Enable GPU for InsightFace: auto-add CUDA12 + cuDNN9 DLL dirs on Windows
onnxruntime-gpu silently fell back to CPU (LoadLibrary error 126) because cuDNN 9 keeps its CUDA-12 DLLs in a versioned subfolder (CUDNN\vX\bin\12.x) that isn't on PATH. detector._ensure_cuda_dlls() auto-discovers the CUDA toolkit bin + cuDNN 12.x bin and prepends them before importing onnxruntime. Verified: InsightFace, GFPGAN and RealESRGAN all run on the RTX 4060. No-op on Linux. + GPU_ENHANCEMENT_PLAN.md runbook (incl. batch test harness).e323c6f -
Swap only the main subject + always run face swap (fixes the mess)
- swapper: swap ONLY the largest target face (and any >=45% of it), so background faces (notice-board photos, posters, reflections) are no longer swapped — that was the 'mess'. Sharpen only the swapped region. - web_app: pipeline order head -> face -> hair. Face swap now ALWAYS runs (a weak head-swap result no longer short-circuits it), then GFPGAN restore, skin-tone match, hair swap, glasses, Laplacian blend. Verified by rendering the full pipeline output.4a2b4cf -
Fix: list_locations resilient when message column missing (was showing 0)
If the locations table hasn't had the message column added yet, selecting it returned an error and the whole list came back empty. Now it retries without message, so locations always show; custom messages activate once the ALTER runs.83651d7
-
Custom per-location result messages + unique defaults; remove em-dashes & "4K"
Custom messages (Control Panel) - locations table gets a `message` column; /api/admin/location/message sets it (edit = any admin). Supports {name} and {location} placeholders, auto-filled - Control Panel: per-location "Edit message" action (prompt), shows a ✎ marker when a custom message exists - App result uses the owner's custom message for that location if set, else a built-in UNIQUE message per location (Library, ICT Dept, VLSI Lab, etc.) UI copy - Removed em-dashes from all visible text (replaced with hyphens/periods) - "Download 4K" -> "Download"; filename no longer suffixed _4k supabase_store: list_locations returns message; add set_message() supabase_schema.sql: add message column (+ ALTER for existing tables)0550cee -
Fix core swap: auto-download InsightFace buffalo_l + glasses in both paths
Root cause of "model not working / no head swap / no glasses / alignment 50": InsightFace buffalo_l was never downloaded — detector only CHECKED for it and returned None when missing, silently falling back to a crude Haar-cascade paste (no real swap, no head transplant, no parser-based hair/glasses/skin matching). core/detector.py - _get_insightface now lets FaceAnalysis auto-download buffalo_l on first use instead of short-circuiting to None. This makes the real inswapper, head swap, BiSeNet hair/glasses transfer, and skin-tone matching actually run. web_app.py - Transfer the source's glasses in BOTH paths (head-swap parser sometimes misses thin frames); wrapped in try/except, no-op if no glasses. frontend/src/pages/AppPage.jsx - Results show only the final output (removed Your Photo + Location preview panels and the technical metrics); nicer heading + "Your Campus Look" framing - Location step no longer previews the backend image — just a clean confirmation frontend/src/p…6ce5ccf -
Add Supabase migration script + explicit sort_order; gitignore Location images
- scripts/seed_supabase_locations.py: one-time upload of local Location photos to Supabase (Storage + table), preserving folder number as sort_order and using the clean label (no number) as the display name - supabase_store.upsert_location: optional sort_order param to preserve order - .gitignore: Location target images live in Supabase, kept out of git (folder structure preserved via .gitkeep)8668f1e -
Store target images in Supabase (table + Storage) with local FS fallback
Owners manage locations from the Control Panel; target images now persist in Supabase instead of only the local disk. Swap output is still never stored — it's returned inline for client-side download. New - supabase_schema.sql — `locations` table (gender, name, sort_order, image_path) + `location-images` Storage bucket + public-read RLS policies + optional seed of the 19 standard locations for both genders - core/supabase_store.py — REST integration via `requests` (no new dependency): list / get-image / upsert / rename / delete. Service-role key, server-side web_app.py - Load .env on startup (python-dotenv) so Supabase keys + admin passwords apply - /api/locations, /api/location-image, /api/admin/location[/delete|/rename] and the swap target resolution now use Supabase when SUPABASE_URL + SERVICE_KEY are set; otherwise fall back to the local Location/ folder (unchanged behaviour) .env.example — documents SUPABASE_URL / SUPABASE_SERVICE_KEY / SUPABASE_BUCKET and the owner p…27af323
-
Improve face-neck-hair pipeline per 7-step checklist
core/head_swap.py — swap_hair() - Add _match_lighting() call before compositing source hair onto target. Previously, hair from a brightly-lit source looked cut-out against a dimmer target. Now exposure is matched (60% L-channel, 35% colour) before the alpha blend, same as full_head_swap() already does. web_app.py - Import laplacian_blend from core.blender - Add step 5: Laplacian pyramid blend (4 levels) over the face mask after all compositing is done. Blends high-frequency hair/skin detail and low-frequency colour separately — eliminates the hard boundary edge that can remain after InsightFace paste_back + hair composite. Skips gracefully if segmentation or blend fails.4f2819a -
Fix all listed technical challenges across detection, landmarks, segmentation
core/detector.py - Add CLAHE contrast enhancement retry when first detection attempt fails, so low-light, backlit, and shadow-heavy photos don't return empty results - Lower minimum face size filter 64px->40px to catch small/distant faces - Extract _detect_insightface() helper to avoid code duplication core/landmarks.py - Fix critical bug: _fallback_landmarks() was returning random uniform noise inside the bounding box, silently corrupting alignment transforms and quality scores. Now falls back to InsightFace 5-point keypoints mapped to the correct MediaPipe indices, or returns None if InsightFace unavailable core/segmentor.py - Remove dead code: fcn_resnet50(pretrained=False) with random weights would produce garbage segmentation. Replaced with facexlib BiSeNet (same model already used by head_swap.py) with heuristic fallback web_app.py - Add small-face quality warnings (< 80px) returned in swap response - Improve face-not-found error messages with actionable tips fron…f4995df
-
feat: head swap — keep the SOURCE's face shape (not just identity)
InsightFace paints the source identity onto the TARGET's face shape, so a round source on a slim target stayed slim. New full_head_swap() transplants the source's whole head — face SHAPE + skin + hair + glasses — onto the target body: - similarity transform (eyes/nose/mouth) preserves the source's proportions (no shear => true face shape), - BiSeNet segments the source head (face+hair, largest component), - clamped to the target head region (background untouched), - _match_lighting shifts exposure toward the target scene so it isn't a cut-out, - feathered composite, then GFPGAN restores natural detail. Wired as the primary stage (head_swap=1 default) with a clean fallback to the InsightFace face swap; hair/glasses steps are skipped when the head swap runs (they come with the head). Verified: source face shape preserved, seamless neck blend, background intact, naturalness ~97. Best for similar (frontal) poses.82f3589 -
fix: natural 4K upscale + hair/glasses on by default (toggles hidden)
- The final RealESRGAN x4 was the unnatural culprit: as a general-purpose model it over-sharpens skin into a plastic/waxy texture on faces. upscale_image now BLENDS RealESRGAN 50/50 with a plain Lanczos upscale (realesrgan_weight) — keeps the resolution/detail but removes the artificial texture. Verified the 4K download face now looks natural. - Hair transfer and source-glasses now run by DEFAULT (swap_hair/keep_glasses default to 1); removed the Options toggles from the UI per request. Disable per-request with swap_hair=0 / keep_glasses=0 if ever needed.1d042b5
-
fix: glasses look natural — eye-locked alignment + see-through lenses
The 5-point warp + fully-opaque paste made glasses look stuck on. Now: - align by the two eye keypoints (similarity from the eye line) so the glasses sit exactly on the eyes regardless of face-proportion differences; - per-pixel opacity from frame-likeness (dark/coloured frame pixels ~opaque, bright lens pixels ~half) so the TARGET's own eyes read through the lens tint instead of the source's eyes — far less pasted. Verified: glasses land on the eyes and the eye shows through the lens.d40313c -
fix: hair paste-back + transfer source glasses onto the swap
- Hair was silently skipped: HairFastGAN returns a TIGHT FFHQ portrait (face fills the frame) and RetinaFace can't detect a frame-filling face, so the paste-back's swap_hair found "no face" and bailed. Pad the portrait before paste-back so the face is detectable and the new hair warps onto the scene. - transfer_glasses (NEW): InsightFace swaps identity but not accessories, so a source's spectacles vanished. Segment them with BiSeNet (class 6 = eye_g), warp onto the swapped face via the 5-point transform, and composite on top. Auto-applied (keep_glasses form flag, default on); no-op when the source wears no glasses. Verified: source glasses now appear on the result, aligned.6ca2728 -
feat: local GPU HairFastGAN hair transfer (auto-selects local, else HF Space)
Validated end-to-end on an RTX 4060 (8GB): full app pipeline swap -> GFPGAN -> skin/neck tone -> HairFastGAN hair -> paste-back -> 4K, no OOM, ~18s warm. - hair_transfer.py: prefer the local GPU HairFastGAN (vendored under external/, run via its MSVC-env launcher subprocess); fall back to the hosted Space when unavailable. Self-heals by copying the committed runner scripts into the repo. HAIRFAST_LOCAL=0 forces the Space. - scripts/hairfast/run_hairfast.{py,bat}: GPU runner (faulthandler + batch_size=1 to co-exist with the Flask models) and a portable MSVC-env launcher that finds Visual Studio via vswhere. - scripts/setup_hairfast.py: reproducible installer — clones the repo + ~7GB weights (git-lfs), patches the StyleGAN2/inplace_abn CUDA ops for torch 2.x (AT_CHECK->TORCH_CHECK, .data<>-> .data_ptr<>), links weights into place, and installs the runners. external/ stays gitignored. - README: local HairFast setup + HF Space fallback (HF_TOKEN / HAIRFAST_SPACE). The hea…500d3c3
-
feat: high-quality hair transfer via hosted HairFastGAN Space (opt-in)
A local HairFastGAN install is impractical here: it pins torch==1.13.1 (the app needs 2.7 for InsightFace/GFPGAN) and compiles StyleGAN2 CUDA ops (fragile on Windows). Instead, core/hair_transfer.py calls a hosted HairFastGAN Gradio Space (StyleGAN, GPU server-side) via gradio_client: - repairs a broken SSL_CERT_FILE from certifi (conda envs set it to a missing path) - HAIRFAST_SPACE env var selects the Space (default public AIRI-Institute); point it at your own duplicated HF Pro GPU Space. HF_TOKEN lifts the ZeroGPU quota. - aligns face/shape/color via /resize_inner*, then /swap_hair. web_app: when the hair toggle is on, transfer_hair(face=swap, shape=source, color=source) returns an aligned portrait whose hair is pasted back into the scene via swap_hair(); falls back to the lightweight local composite if the Space is unavailable. requirements: add gradio_client. NOTE: the public Space errors on anonymous ZeroGPU calls, so this needs your own Pro GPU Space + HF_TOKEN to run reliab…ccb3885 -
fix: match face AND neck to source tone (kill the pasted-jaw seam)
The earlier matcher recoloured only the face box, so the source-toned face met the target-toned neck at the jaw — a visible pasted seam. match_skin_to_source now applies one uniform LAB offset across a BiSeNet skin+neck mask (classes 1,2,3,7,8,9,10,11,12,13,14,15), feathered, stopping at the collar (cloth excluded). Face and neck end up the same complexion, so the seam disappears; falls back to the face-only oval matcher when the parser is unavailable. Also: widen the hair-swap clamp region (down 3.0x, sides 1.2x of the face box) so LONG hair — e.g. female styles past the shoulders — is preserved instead of being cut at the chin. The pipeline (InsightFace + BiSeNet + GFPGAN) is otherwise gender-agnostic and works for male and female faces alike.f98b2ba -
feat: experimental hair/head swap (opt-in)
InsightFace only swaps the face, so hair always stayed the target's. This adds an opt-in step that transplants the source's hair onto the result. core/head_swap.py: - BiSeNet (facexlib) parses the source head; hair = class 17 (verified). - 5-point similarity transform (InsightFace kps) maps source -> target pose. - Source image + hair mask warped into the target frame and composited over the face swap. Robustness: mask eroded before feathering (no background halo), clamped to a plausible head region (no hair floating above the head), and reduced to its largest connected component (drops stray misclassified patches). - include_face=True transfers the whole head (fuller, lower-fidelity face). web_app.py: opt-in via `swap_hair` / `full_head` form flags; default off so the solid face-swap pipeline is unchanged. AppPage.jsx: "Swap hair too (experimental)" checkbox in advanced settings. Note: quality is pose/lighting/source-quality sensitive and can look artificial; it is intention…1eb6757 -
feat: match swapped face complexion to the source
Adds match_face_to_source_tone: samples mean CIE-LAB from the central face oval of both the source and the swapped result, then adds the per-channel difference back over the target face box through a feathered ellipse. Only the face is affected (neck/body untouched), so the swapped face takes on the source's complexion as requested. Wired into the web pipeline after GFPGAN restoration.732cb77 -
fix: full-identity swap — stop pasting target eyes back over every swap
_restore_glasses_region ran unconditionally after each swap, blending the target's eye/spectacle region back over the result. The eyes are the strongest identity cue, so this made every swap look only half-done (still like the target). It was meant only for targets that wear glasses. - swap_face_insightface: add preserve_glasses flag (default False). The glasses/ eye restore now runs only when explicitly requested, so the default is a full identity swap. - Select the largest detected source face (most frontal/clear) instead of the first, for more reliable identity transfer.e61f9c7 -
feat: enable GFPGAN on HF CPU deploy; CPU-aware upscaling
The blur fix (GFPGAN) only ran on local GPU — the HuggingFace deploy had none of it: gfpgan/realesrgan/basicsr weren't in requirements-deploy.txt and the GFPGAN weights weren't downloaded at startup, so the live site stayed blurry. - requirements-deploy.txt: add basicsr, facexlib, gfpgan, realesrgan. The super_res.py torchvision.functional_tensor shim lets them import on the deploy's torchvision 0.18 (which dropped that submodule). - startup.sh: download GFPGANv1.4.pth alongside inswapper in the background, with a truncation check. RealESRGAN weights skipped on CPU (unused there). - super_res.py upscale_image: use RealESRGAN only on CUDA; on CPU fall back to Lanczos. A 4x RealESRGAN pass on CPU takes 60-120s and risks an OOM/timeout on HF's free tier — GFPGAN already provides the facial sharpness, so CPU only needs the cheap resolution bump. Verified locally (GPU): swap -> GFPGAN -> RealESRGAN produces a 1840x4096 (4K) download in ~5s; quality alignment now 50.0 (was 0.0)…07d6508 -
fix: GFPGAN face restoration removes swap blur; simplify pipeline; GPU 4K
The blur and hair mismatch came from two things: (1) gfpgan/realesrgan never actually ran — basicsr imports torchvision.transforms.functional_tensor, removed in torchvision 0.17+, so super-res silently fell back to Lanczos (which enlarges but cannot recreate lost facial detail); (2) the heavy post-swap stack (seamless_hair_to_neck_blend + laplacian + poisson + bilateral) smeared the hairline and softened the face. - super_res.py: shim torchvision.transforms.functional_tensor so basicsr/gfpgan/ realesrgan import on modern torchvision. Split into restore_faces() (GFPGAN, same-res detail restoration) and upscale_image() (RealESRGAN x4, Lanczos fallback). GPU-aware (cuda/fp16 when available). Fix UnicodeEncodeError — the arrow char crashed on Windows cp1252 stdout. - web_app.py: new swap path is InsightFace -> GFPGAN restore -> RealESRGAN 4K. GFPGAN runs BEFORE the preview is encoded so the on-screen image is sharp, not just the download. Dropped hair-neck blend / laplacian / …ce89434 -
test: fix false-positive pipeline test + cover super-res and face-mask paths
- test_pipeline.py: the old progress-callback test fed a faceless flat image, so the pipeline returned early at stage 5 and the test passed without ever exercising the swap path. Rewrote it to assert the real callback contract (fires at pct=5 'Detecting faces', returns error). Removed unused pytest import. - Added test_super_res_lanczos_fallback_upscales_4x — guarantees enhance_resolution delivers a 4x image even when SR model weights are absent (never silent no-op). - Added test_match_skin_tone_confined_to_face_mask — verifies the tone transfer stays inside the mask and leaves the rest of the image bit-exact. - skin_tone.py: composite match_skin_tone in BGR (not LAB) so masked-out pixels keep their exact original values — no LAB round-trip ±1 drift. - test_skin_tone.py: removed unused pytest import.f3c5fbe -
fix: sharp output, source skin tone preservation, 4K upscaling pipeline
- swapper.py: sharpen ORIGINAL crop (not bilateral-blurred) — unsharp mask strength 2.3x on raw crop; mild bilateral d=5/30 only removes compression artefacts without killing texture detail - skin_tone.py: match_skin_tone now accepts face_mask; LAB transfer confined to face region via feathered alpha — hair/neck/background no longer shifted - core/super_res.py (NEW): GFPGAN face restoration + RealESRGAN x4 upscale; Lanczos fallback when models absent — always delivers 4K download - web_app.py: complete pipeline — add match_skin_tone (source tone, face mask), laplacian_blend + poisson_blend, 4K hi-res PNG saved for download, preview JPEG at 92 quality; input cap raised 1024→2048 for more InsightFace detail - quality_checker.py: _alignment_score handles None/empty arrays — returns 50.0 instead of propagating nan (was showing 0.0/100 in UI) - full_pipeline.py: enable_super_res=True by default; pass face_mask to match_skin_tone; Lanczos fallback on super_res failure; remove…f1b722f -
feat: restore glasses/eye region from target after swap, preserve spectacles cleanly
00273de