Spaces:
Runtime error
Runtime error
Philippe Potvin commited on
Commit ·
39f014d
1
Parent(s): f1d60fc
Fri, 03 Jul 2026 00:16 - Add Qwen face hair detailer
Browse files- README.md +2 -2
- app.py +141 -8
- requirements.txt +6 -9
- tests/test_enhance_stage.py +16 -5
README.md
CHANGED
|
@@ -7,10 +7,10 @@ sdk_version: 5.49.1
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
-
version: 1.0.
|
| 11 |
short_description: Powerful image editing - supports one or two input images.
|
| 12 |
---
|
| 13 |
|
| 14 |
-
Pro Realism Edit Studio is a powerful image editor powered by [Qwen-Image-Edit-2511](https://huggingface.co/Qwen/Qwen-Image-Edit-2511) with [Phr00t's Rapid-AIO v23](https://huggingface.co/Phr00t/Qwen-Image-Edit-Rapid-AIO) accelerated transformer for 4-step inference. Upload one or two input images, write a prompt, get high-quality results. The enhancement stage uses Nomos-family tiled upscaling, Qwen-aware masked artifact repair, micro-detail sharpening, and final photographic grain.
|
| 15 |
|
| 16 |
Video generation is disabled unless an owned Gradio video Space is configured with `VIDEO_SPACE_ID`.
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
+
version: 1.0.2
|
| 11 |
short_description: Powerful image editing - supports one or two input images.
|
| 12 |
---
|
| 13 |
|
| 14 |
+
Pro Realism Edit Studio is a powerful image editor powered by [Qwen-Image-Edit-2511](https://huggingface.co/Qwen/Qwen-Image-Edit-2511) with [Phr00t's Rapid-AIO v23](https://huggingface.co/Phr00t/Qwen-Image-Edit-Rapid-AIO) accelerated transformer for 4-step inference. Upload one or two input images, write a prompt, get high-quality results. The enhancement stage uses Nomos-family tiled upscaling, Qwen-aware masked artifact repair, tiled face/hair detail regions, micro-detail sharpening, and final photographic grain.
|
| 15 |
|
| 16 |
Video generation is disabled unless an owned Gradio video Space is configured with `VIDEO_SPACE_ID`.
|
app.py
CHANGED
|
@@ -9,7 +9,7 @@ Advanced image editing and enhancement studio powered by:
|
|
| 9 |
- Qwen-aware masked cleanup and final photographic detail
|
| 10 |
|
| 11 |
Author: Enhanced with Hugging Face CLI and image generation expertise
|
| 12 |
-
Version: 1.0.
|
| 13 |
"""
|
| 14 |
|
| 15 |
import gradio as gr
|
|
@@ -25,7 +25,7 @@ from pathlib import Path
|
|
| 25 |
# Advanced imports
|
| 26 |
from accelerate import init_empty_weights
|
| 27 |
from collections import OrderedDict
|
| 28 |
-
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
|
| 29 |
from diffusers.models import QwenImageTransformer2DModel as DiffusersQwenImageTransformer2DModel
|
| 30 |
from diffusers.models.model_loading_utils import load_model_dict_into_meta
|
| 31 |
from huggingface_hub import hf_hub_download, HfApi, login, whoami
|
|
@@ -42,7 +42,7 @@ from gradio_client import Client, handle_file
|
|
| 42 |
|
| 43 |
# Base model configuration
|
| 44 |
BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
|
| 45 |
-
APP_VERSION = "1.0.
|
| 46 |
PHR00T_REPO_ID = os.environ.get("PHR00T_REPO_ID", "Phr00t/Qwen-Image-Edit-Rapid-AIO").strip()
|
| 47 |
RAPID_TRANSFORMER_FILENAME = os.environ.get(
|
| 48 |
"RAPID_TRANSFORMER_FILENAME",
|
|
@@ -60,6 +60,9 @@ ENHANCE_MAX_INPUT_EDGE = int(os.environ.get("ENHANCE_MAX_INPUT_EDGE", "2048"))
|
|
| 60 |
ENHANCE_QWEN_DOWNSCALE_TRIGGER_EDGE = int(os.environ.get("ENHANCE_QWEN_DOWNSCALE_TRIGGER_EDGE", "1536"))
|
| 61 |
ENHANCE_QWEN_DOWNSCALE_FACTOR = float(os.environ.get("ENHANCE_QWEN_DOWNSCALE_FACTOR", "0.75"))
|
| 62 |
ENHANCE_GRAIN_STRENGTH = float(os.environ.get("ENHANCE_GRAIN_STRENGTH", "0.010"))
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
# Advanced Detail Enhancement Configuration
|
| 65 |
DETAIL_ENHANCEMENT_ENABLED = os.environ.get("DETAIL_ENHANCEMENT_ENABLED", "true").lower() == "true"
|
|
@@ -606,6 +609,123 @@ def qwen_defect_mask(image):
|
|
| 606 |
mask_image = mask_image.filter(ImageFilter.MaxFilter(size=3))
|
| 607 |
return mask_image.filter(ImageFilter.GaussianBlur(radius=1.1))
|
| 608 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
def masked_texture_repair(image, strength=0.55):
|
| 610 |
base = image.convert("RGB")
|
| 611 |
mask = qwen_defect_mask(base)
|
|
@@ -640,8 +760,8 @@ def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
|
| 640 |
enhanced = image.convert("RGB")
|
| 641 |
total_steps = {
|
| 642 |
ENHANCE_MODE_UPSCALE: 2,
|
| 643 |
-
ENHANCE_MODE_CLEAN:
|
| 644 |
-
ENHANCE_MODE_MAX_DETAIL:
|
| 645 |
}[mode]
|
| 646 |
step = 0
|
| 647 |
|
|
@@ -657,6 +777,15 @@ def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
|
| 657 |
enhanced = masked_texture_repair(enhanced, strength=0.55 if mode == ENHANCE_MODE_CLEAN else 0.68)
|
| 658 |
enhanced = qwen_micro_detail(enhanced, amount=0.18)
|
| 659 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
if mode == ENHANCE_MODE_MAX_DETAIL:
|
| 661 |
if progress:
|
| 662 |
step += 1
|
|
@@ -669,6 +798,10 @@ def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
|
| 669 |
enhanced = tile_upscale(enhanced)
|
| 670 |
|
| 671 |
if mode == ENHANCE_MODE_MAX_DETAIL:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
if progress:
|
| 673 |
step += 1
|
| 674 |
progress(0.98, desc="Adding final photographic grain...")
|
|
@@ -855,7 +988,7 @@ with gr.Blocks(css=css) as demo:
|
|
| 855 |
|
| 856 |
Pro Tips:
|
| 857 |
- Use Clean for portraits with smudged or pitted skin
|
| 858 |
-
- Use Max Detail for texture, grain, and sharper final output
|
| 859 |
""")
|
| 860 |
|
| 861 |
with gr.Row():
|
|
@@ -882,8 +1015,8 @@ with gr.Blocks(css=css) as demo:
|
|
| 882 |
Enhancement Options:
|
| 883 |
- Off: No post-processing
|
| 884 |
- Upscale: Qwen precondition + 4x Nomos tiled upscale
|
| 885 |
-
- Clean: Masked Qwen artifact repair + upscaling
|
| 886 |
-
- Max Detail:
|
| 887 |
""", visible=False)
|
| 888 |
|
| 889 |
run_button = gr.Button("Generate!", variant="primary")
|
|
|
|
| 9 |
- Qwen-aware masked cleanup and final photographic detail
|
| 10 |
|
| 11 |
Author: Enhanced with Hugging Face CLI and image generation expertise
|
| 12 |
+
Version: 1.0.2
|
| 13 |
"""
|
| 14 |
|
| 15 |
import gradio as gr
|
|
|
|
| 25 |
# Advanced imports
|
| 26 |
from accelerate import init_empty_weights
|
| 27 |
from collections import OrderedDict
|
| 28 |
+
from PIL import Image, ImageChops, ImageEnhance, ImageFilter, ImageOps
|
| 29 |
from diffusers.models import QwenImageTransformer2DModel as DiffusersQwenImageTransformer2DModel
|
| 30 |
from diffusers.models.model_loading_utils import load_model_dict_into_meta
|
| 31 |
from huggingface_hub import hf_hub_download, HfApi, login, whoami
|
|
|
|
| 42 |
|
| 43 |
# Base model configuration
|
| 44 |
BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
|
| 45 |
+
APP_VERSION = "1.0.2"
|
| 46 |
PHR00T_REPO_ID = os.environ.get("PHR00T_REPO_ID", "Phr00t/Qwen-Image-Edit-Rapid-AIO").strip()
|
| 47 |
RAPID_TRANSFORMER_FILENAME = os.environ.get(
|
| 48 |
"RAPID_TRANSFORMER_FILENAME",
|
|
|
|
| 60 |
ENHANCE_QWEN_DOWNSCALE_TRIGGER_EDGE = int(os.environ.get("ENHANCE_QWEN_DOWNSCALE_TRIGGER_EDGE", "1536"))
|
| 61 |
ENHANCE_QWEN_DOWNSCALE_FACTOR = float(os.environ.get("ENHANCE_QWEN_DOWNSCALE_FACTOR", "0.75"))
|
| 62 |
ENHANCE_GRAIN_STRENGTH = float(os.environ.get("ENHANCE_GRAIN_STRENGTH", "0.010"))
|
| 63 |
+
ENHANCE_DETAILER_ENABLED = os.environ.get("ENHANCE_DETAILER_ENABLED", "true").lower() == "true"
|
| 64 |
+
ENHANCE_DETAILER_MAX_REGIONS = int(os.environ.get("ENHANCE_DETAILER_MAX_REGIONS", "8"))
|
| 65 |
+
ENHANCE_DETAILER_MIN_REGION_AREA = float(os.environ.get("ENHANCE_DETAILER_MIN_REGION_AREA", "0.003"))
|
| 66 |
|
| 67 |
# Advanced Detail Enhancement Configuration
|
| 68 |
DETAIL_ENHANCEMENT_ENABLED = os.environ.get("DETAIL_ENHANCEMENT_ENABLED", "true").lower() == "true"
|
|
|
|
| 609 |
mask_image = mask_image.filter(ImageFilter.MaxFilter(size=3))
|
| 610 |
return mask_image.filter(ImageFilter.GaussianBlur(radius=1.1))
|
| 611 |
|
| 612 |
+
def qwen_hair_mask(image):
|
| 613 |
+
base = image.convert("RGB")
|
| 614 |
+
rgb = np.asarray(base).astype(np.float32)
|
| 615 |
+
gray_image = base.convert("L")
|
| 616 |
+
gray = np.asarray(gray_image).astype(np.float32)
|
| 617 |
+
skin = np.asarray(qwen_skin_mask(base)).astype(np.float32) / 255.0
|
| 618 |
+
edges = np.asarray(gray_image.filter(ImageFilter.FIND_EDGES).filter(ImageFilter.GaussianBlur(radius=0.7))).astype(np.float32)
|
| 619 |
+
chroma = rgb.max(axis=2) - rgb.min(axis=2)
|
| 620 |
+
|
| 621 |
+
dark_strands = (gray < 122) & (edges > 8) & (skin < 0.45)
|
| 622 |
+
light_strands = (gray < 235) & (edges > 18) & (chroma > 8) & (skin < 0.28)
|
| 623 |
+
mask = (dark_strands | light_strands).astype(np.uint8) * 255
|
| 624 |
+
|
| 625 |
+
try:
|
| 626 |
+
import cv2
|
| 627 |
+
kernel = np.ones((3, 3), np.uint8)
|
| 628 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
| 629 |
+
mask = cv2.GaussianBlur(mask, (0, 0), 1.0)
|
| 630 |
+
except ImportError:
|
| 631 |
+
mask_image = Image.fromarray(mask, mode="L")
|
| 632 |
+
mask_image = mask_image.filter(ImageFilter.MaxFilter(size=3))
|
| 633 |
+
mask_image = mask_image.filter(ImageFilter.GaussianBlur(radius=1.0))
|
| 634 |
+
mask = np.asarray(mask_image)
|
| 635 |
+
|
| 636 |
+
return Image.fromarray(mask.astype(np.uint8), mode="L")
|
| 637 |
+
|
| 638 |
+
def qwen_face_feature_mask(image):
|
| 639 |
+
base = image.convert("RGB")
|
| 640 |
+
skin = np.asarray(qwen_skin_mask(base)).astype(np.float32) / 255.0
|
| 641 |
+
gray_image = base.convert("L")
|
| 642 |
+
edges = np.asarray(gray_image.filter(ImageFilter.FIND_EDGES)).astype(np.float32)
|
| 643 |
+
features = ((skin > 0.08) & (edges > 10)).astype(np.uint8) * 255
|
| 644 |
+
defects = np.asarray(qwen_defect_mask(base)).astype(np.uint8)
|
| 645 |
+
mask = np.maximum(features, defects)
|
| 646 |
+
mask_image = Image.fromarray(mask, mode="L")
|
| 647 |
+
mask_image = mask_image.filter(ImageFilter.MaxFilter(size=5))
|
| 648 |
+
return mask_image.filter(ImageFilter.GaussianBlur(radius=1.6))
|
| 649 |
+
|
| 650 |
+
def _mask_to_boxes(mask_image, max_regions=ENHANCE_DETAILER_MAX_REGIONS):
|
| 651 |
+
mask = np.asarray(mask_image.convert("L"))
|
| 652 |
+
binary = (mask > 24).astype(np.uint8)
|
| 653 |
+
min_area = max(32, int(binary.shape[0] * binary.shape[1] * ENHANCE_DETAILER_MIN_REGION_AREA))
|
| 654 |
+
boxes = []
|
| 655 |
+
|
| 656 |
+
try:
|
| 657 |
+
import cv2
|
| 658 |
+
count, labels, stats, _ = cv2.connectedComponentsWithStats(binary, connectivity=8)
|
| 659 |
+
for label in range(1, count):
|
| 660 |
+
x, y, width, height, area = stats[label]
|
| 661 |
+
if area >= min_area:
|
| 662 |
+
boxes.append((int(x), int(y), int(x + width), int(y + height), int(area)))
|
| 663 |
+
except ImportError:
|
| 664 |
+
bbox = mask_image.point(lambda value: 255 if value > 24 else 0).getbbox()
|
| 665 |
+
if bbox:
|
| 666 |
+
x0, y0, x1, y1 = bbox
|
| 667 |
+
boxes.append((x0, y0, x1, y1, (x1 - x0) * (y1 - y0)))
|
| 668 |
+
|
| 669 |
+
boxes.sort(key=lambda item: item[4], reverse=True)
|
| 670 |
+
return [box[:4] for box in boxes[:max_regions]]
|
| 671 |
+
|
| 672 |
+
def _expand_box(box, image_size, pad_ratio=0.18, min_size=192):
|
| 673 |
+
x0, y0, x1, y1 = box
|
| 674 |
+
width = x1 - x0
|
| 675 |
+
height = y1 - y0
|
| 676 |
+
pad = int(max(width, height) * pad_ratio)
|
| 677 |
+
if width < min_size:
|
| 678 |
+
extra = (min_size - width) // 2
|
| 679 |
+
x0 -= extra
|
| 680 |
+
x1 += extra
|
| 681 |
+
if height < min_size:
|
| 682 |
+
extra = (min_size - height) // 2
|
| 683 |
+
y0 -= extra
|
| 684 |
+
y1 += extra
|
| 685 |
+
return (
|
| 686 |
+
max(0, x0 - pad),
|
| 687 |
+
max(0, y0 - pad),
|
| 688 |
+
min(image_size[0], x1 + pad),
|
| 689 |
+
min(image_size[1], y1 + pad),
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
def _local_detail_crop(crop, mask_crop, strength=0.35, hair=False):
|
| 693 |
+
base = crop.convert("RGB")
|
| 694 |
+
mask = mask_crop.convert("L").filter(ImageFilter.GaussianBlur(radius=1.8))
|
| 695 |
+
strength = max(0.0, min(1.0, strength))
|
| 696 |
+
|
| 697 |
+
if hair:
|
| 698 |
+
detailed = base.filter(ImageFilter.UnsharpMask(radius=0.55, percent=int(130 + 120 * strength), threshold=2))
|
| 699 |
+
detailed = ImageEnhance.Contrast(detailed).enhance(1.0 + 0.08 * strength)
|
| 700 |
+
edge_mask = base.convert("L").filter(ImageFilter.FIND_EDGES).filter(ImageFilter.GaussianBlur(radius=0.7))
|
| 701 |
+
mask = ImageChops.multiply(mask, edge_mask.point(lambda value: min(255, int(value * 2.2))))
|
| 702 |
+
else:
|
| 703 |
+
repaired = masked_texture_repair(base, strength=0.35 + 0.30 * strength)
|
| 704 |
+
detailed = repaired.filter(ImageFilter.UnsharpMask(radius=0.75, percent=int(80 + 90 * strength), threshold=3))
|
| 705 |
+
detailed = ImageEnhance.Contrast(detailed).enhance(1.0 + 0.045 * strength)
|
| 706 |
+
|
| 707 |
+
mask = mask.point(lambda value: int(value * strength))
|
| 708 |
+
return Image.composite(detailed, base, mask)
|
| 709 |
+
|
| 710 |
+
def qwen_tiled_detailer_pass(image, strength=0.35, include_hair=True):
|
| 711 |
+
if not ENHANCE_DETAILER_ENABLED or strength <= 0:
|
| 712 |
+
return image.convert("RGB")
|
| 713 |
+
|
| 714 |
+
result = image.convert("RGB")
|
| 715 |
+
region_specs = [(qwen_face_feature_mask(result), strength, False, 0.22)]
|
| 716 |
+
if include_hair:
|
| 717 |
+
region_specs.append((qwen_hair_mask(result), strength * 0.85, True, 0.12))
|
| 718 |
+
|
| 719 |
+
for mask, region_strength, hair, pad_ratio in region_specs:
|
| 720 |
+
for box in _mask_to_boxes(mask):
|
| 721 |
+
expanded = _expand_box(box, result.size, pad_ratio=pad_ratio, min_size=192)
|
| 722 |
+
crop = result.crop(expanded)
|
| 723 |
+
mask_crop = mask.crop(expanded)
|
| 724 |
+
detailed = _local_detail_crop(crop, mask_crop, strength=region_strength, hair=hair)
|
| 725 |
+
result.paste(detailed, expanded, mask_crop.filter(ImageFilter.GaussianBlur(radius=2.5)))
|
| 726 |
+
|
| 727 |
+
return result
|
| 728 |
+
|
| 729 |
def masked_texture_repair(image, strength=0.55):
|
| 730 |
base = image.convert("RGB")
|
| 731 |
mask = qwen_defect_mask(base)
|
|
|
|
| 760 |
enhanced = image.convert("RGB")
|
| 761 |
total_steps = {
|
| 762 |
ENHANCE_MODE_UPSCALE: 2,
|
| 763 |
+
ENHANCE_MODE_CLEAN: 4,
|
| 764 |
+
ENHANCE_MODE_MAX_DETAIL: 7,
|
| 765 |
}[mode]
|
| 766 |
step = 0
|
| 767 |
|
|
|
|
| 777 |
enhanced = masked_texture_repair(enhanced, strength=0.55 if mode == ENHANCE_MODE_CLEAN else 0.68)
|
| 778 |
enhanced = qwen_micro_detail(enhanced, amount=0.18)
|
| 779 |
|
| 780 |
+
if progress:
|
| 781 |
+
step += 1
|
| 782 |
+
progress(0.85 * step / total_steps, desc="Detailing face regions...")
|
| 783 |
+
enhanced = qwen_tiled_detailer_pass(
|
| 784 |
+
enhanced,
|
| 785 |
+
strength=0.32 if mode == ENHANCE_MODE_CLEAN else 0.48,
|
| 786 |
+
include_hair=(mode == ENHANCE_MODE_MAX_DETAIL),
|
| 787 |
+
)
|
| 788 |
+
|
| 789 |
if mode == ENHANCE_MODE_MAX_DETAIL:
|
| 790 |
if progress:
|
| 791 |
step += 1
|
|
|
|
| 798 |
enhanced = tile_upscale(enhanced)
|
| 799 |
|
| 800 |
if mode == ENHANCE_MODE_MAX_DETAIL:
|
| 801 |
+
if progress:
|
| 802 |
+
step += 1
|
| 803 |
+
progress(0.94, desc="Detailing final face and hair tiles...")
|
| 804 |
+
enhanced = qwen_tiled_detailer_pass(enhanced, strength=0.30, include_hair=True)
|
| 805 |
if progress:
|
| 806 |
step += 1
|
| 807 |
progress(0.98, desc="Adding final photographic grain...")
|
|
|
|
| 988 |
|
| 989 |
Pro Tips:
|
| 990 |
- Use Clean for portraits with smudged or pitted skin
|
| 991 |
+
- Use Max Detail for hair, eyes, lashes, texture, grain, and sharper final output
|
| 992 |
""")
|
| 993 |
|
| 994 |
with gr.Row():
|
|
|
|
| 1015 |
Enhancement Options:
|
| 1016 |
- Off: No post-processing
|
| 1017 |
- Upscale: Qwen precondition + 4x Nomos tiled upscale
|
| 1018 |
+
- Clean: Masked Qwen artifact repair + tiled face detail + upscaling
|
| 1019 |
+
- Max Detail: Tiled face/hair detail + micro detail + upscaling + final grain
|
| 1020 |
""", visible=False)
|
| 1021 |
|
| 1022 |
run_button = gr.Button("Generate!", variant="primary")
|
requirements.txt
CHANGED
|
@@ -15,15 +15,12 @@ Kernels==0.11.0
|
|
| 15 |
Peft
|
| 16 |
Torchao==0.11.0
|
| 17 |
|
| 18 |
-
# Image processing and upscaling
|
| 19 |
spandrel
|
| 20 |
spandrel_extra_arches
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# OpenCV for faster mask cleanup (optional)
|
| 25 |
-
# Install with: pip install opencv-python
|
| 26 |
-
# opencv-python>=4.5.0
|
| 27 |
|
| 28 |
# SciPy for legacy image processing helpers (optional)
|
| 29 |
# Install with: pip install scipy
|
|
@@ -41,6 +38,6 @@ Gradio_client
|
|
| 41 |
# Performance monitoring (optional)
|
| 42 |
# Install with: pip install psutil
|
| 43 |
|
| 44 |
-
# Note: For optional
|
| 45 |
# pip install -r requirements_enhanced.txt
|
| 46 |
-
# pip install
|
|
|
|
| 15 |
Peft
|
| 16 |
Torchao==0.11.0
|
| 17 |
|
| 18 |
+
# Image processing and upscaling
|
| 19 |
spandrel
|
| 20 |
spandrel_extra_arches
|
| 21 |
+
opencv-python-headless
|
| 22 |
+
Pillow
|
| 23 |
+
Numpy
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
# SciPy for legacy image processing helpers (optional)
|
| 26 |
# Install with: pip install scipy
|
|
|
|
| 38 |
# Performance monitoring (optional)
|
| 39 |
# Install with: pip install psutil
|
| 40 |
|
| 41 |
+
# Note: For optional legacy helper coverage, run:
|
| 42 |
# pip install -r requirements_enhanced.txt
|
| 43 |
+
# pip install scipy
|
tests/test_enhance_stage.py
CHANGED
|
@@ -34,11 +34,22 @@ class EnhanceStageContractTest(unittest.TestCase):
|
|
| 34 |
self.assertIn("spandrel", app)
|
| 35 |
self.assertIn("tile_upscale", app)
|
| 36 |
|
| 37 |
-
def test_upscaler_dependencies_are_declared(self):
|
| 38 |
-
requirements = read_text("requirements.txt")
|
| 39 |
-
|
| 40 |
-
self.assertIn("spandrel", requirements)
|
| 41 |
-
self.assertIn("spandrel_extra_arches", requirements)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
if __name__ == "__main__":
|
|
|
|
| 34 |
self.assertIn("spandrel", app)
|
| 35 |
self.assertIn("tile_upscale", app)
|
| 36 |
|
| 37 |
+
def test_upscaler_dependencies_are_declared(self):
|
| 38 |
+
requirements = read_text("requirements.txt")
|
| 39 |
+
|
| 40 |
+
self.assertIn("spandrel", requirements)
|
| 41 |
+
self.assertIn("spandrel_extra_arches", requirements)
|
| 42 |
+
self.assertIn("opencv-python-headless", requirements)
|
| 43 |
+
|
| 44 |
+
def test_qwen_detailer_is_part_of_clean_and_max_detail(self):
|
| 45 |
+
app = read_text("app.py")
|
| 46 |
+
|
| 47 |
+
self.assertIn("ENHANCE_DETAILER_ENABLED", app)
|
| 48 |
+
self.assertIn("qwen_tiled_detailer_pass", app)
|
| 49 |
+
self.assertIn("qwen_hair_mask", app)
|
| 50 |
+
self.assertIn("qwen_face_feature_mask", app)
|
| 51 |
+
self.assertIn("Detailing face regions", app)
|
| 52 |
+
self.assertIn("Detailing final face and hair tiles", app)
|
| 53 |
|
| 54 |
|
| 55 |
if __name__ == "__main__":
|