Spaces:
Runtime error
Runtime error
Philippe Potvin commited on
Commit ·
f1d60fc
1
Parent(s): 5d5959d
Thu, 02 Jul 2026 19:47 - Optimize Qwen enhancement stage
Browse files- README.md +2 -2
- app.py +196 -157
- requirements.txt +12 -16
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.
|
| 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.1
|
| 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`.
|
app.py
CHANGED
|
@@ -5,12 +5,11 @@ Pro Realism Edit Studio - Enhanced Edition
|
|
| 5 |
|
| 6 |
Advanced image editing and enhancement studio powered by:
|
| 7 |
- Qwen-Image-Edit-2511 with Phr00t's Rapid-AIO v23 accelerated transformer
|
| 8 |
-
-
|
| 9 |
-
-
|
| 10 |
-
- Multi-stage detail enhancement pipeline
|
| 11 |
|
| 12 |
Author: Enhanced with Hugging Face CLI and image generation expertise
|
| 13 |
-
Version: 1.0.
|
| 14 |
"""
|
| 15 |
|
| 16 |
import gradio as gr
|
|
@@ -43,7 +42,7 @@ from gradio_client import Client, handle_file
|
|
| 43 |
|
| 44 |
# Base model configuration
|
| 45 |
BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
|
| 46 |
-
APP_VERSION = "1.0.
|
| 47 |
PHR00T_REPO_ID = os.environ.get("PHR00T_REPO_ID", "Phr00t/Qwen-Image-Edit-Rapid-AIO").strip()
|
| 48 |
RAPID_TRANSFORMER_FILENAME = os.environ.get(
|
| 49 |
"RAPID_TRANSFORMER_FILENAME",
|
|
@@ -52,17 +51,15 @@ RAPID_TRANSFORMER_FILENAME = os.environ.get(
|
|
| 52 |
PHR00T_TRANSFORMER_PREFIX = "model.diffusion_model."
|
| 53 |
VIDEO_SPACE_ID = os.environ.get("VIDEO_SPACE_ID", "").strip()
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
UPSCALER_MODEL_ID = os.environ.get("UPSCALER_MODEL_ID", "
|
| 57 |
-
UPSCALER_MODEL_FILENAME = os.environ.get("UPSCALER_MODEL_FILENAME", "
|
| 58 |
UPSCALER_TILE_SIZE = int(os.environ.get("UPSCALER_TILE_SIZE", "512"))
|
| 59 |
UPSCALER_TILE_OVERLAP = int(os.environ.get("UPSCALER_TILE_OVERLAP", "64"))
|
| 60 |
ENHANCE_MAX_INPUT_EDGE = int(os.environ.get("ENHANCE_MAX_INPUT_EDGE", "2048"))
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
FACE_RESTORATION_MODEL = os.environ.get("FACE_RESTORATION_MODEL", "Xintao/GFPGAN").strip()
|
| 65 |
-
FACE_RESTORATION_WEIGHTS = os.environ.get("FACE_RESTORATION_WEIGHTS", "GFPGANv1.3.pth").strip()
|
| 66 |
|
| 67 |
# Advanced Detail Enhancement Configuration
|
| 68 |
DETAIL_ENHANCEMENT_ENABLED = os.environ.get("DETAIL_ENHANCEMENT_ENABLED", "true").lower() == "true"
|
|
@@ -73,26 +70,16 @@ SMART_SHARPENING_STRENGTH = float(os.environ.get("SMART_SHARPENING_STRENGTH", "1
|
|
| 73 |
# ============================================================================
|
| 74 |
|
| 75 |
ENHANCE_MODE_OFF = "Off"
|
| 76 |
-
ENHANCE_MODE_UPSCALE = "Upscale
|
| 77 |
-
ENHANCE_MODE_CLEAN = "Clean
|
| 78 |
ENHANCE_MODE_MAX_DETAIL = "Max Detail"
|
| 79 |
-
|
| 80 |
-
ENHANCE_MODE_FULL_ENHANCE = "Full Enhance"
|
| 81 |
-
ENHANCE_MODE_CHOICES = [
|
| 82 |
-
ENHANCE_MODE_OFF,
|
| 83 |
-
ENHANCE_MODE_UPSCALE,
|
| 84 |
-
ENHANCE_MODE_CLEAN,
|
| 85 |
-
ENHANCE_MODE_MAX_DETAIL,
|
| 86 |
-
ENHANCE_MODE_FACE_ENHANCE,
|
| 87 |
-
ENHANCE_MODE_FULL_ENHANCE
|
| 88 |
-
]
|
| 89 |
|
| 90 |
# ============================================================================
|
| 91 |
# GLOBAL MODEL CACHE
|
| 92 |
# ============================================================================
|
| 93 |
|
| 94 |
_upscaler_model = None
|
| 95 |
-
_face_restoration_model = None
|
| 96 |
_detail_enhancement_model = None
|
| 97 |
|
| 98 |
# ============================================================================
|
|
@@ -261,11 +248,11 @@ pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
|
|
| 261 |
print("Applied FA3 attention processor optimization")
|
| 262 |
|
| 263 |
# ============================================================================
|
| 264 |
-
#
|
| 265 |
# ============================================================================
|
| 266 |
|
| 267 |
def load_upscaler_model():
|
| 268 |
-
"""Load
|
| 269 |
global _upscaler_model
|
| 270 |
if _upscaler_model is not None:
|
| 271 |
return _upscaler_model
|
|
@@ -278,24 +265,29 @@ def load_upscaler_model():
|
|
| 278 |
raise gr.Error("Enhance mode requires spandrel and spandrel_extra_arches to be installed. "
|
| 279 |
"Install with: pip install spandrel spandrel_extra_arches") from exc
|
| 280 |
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
|
|
|
| 291 |
try:
|
| 292 |
-
model_path = download_model_with_retry(
|
| 293 |
model = spandrel.ModelLoader().load_from_file(model_path)
|
| 294 |
model.eval().to(device)
|
| 295 |
_upscaler_model = model
|
|
|
|
| 296 |
return _upscaler_model
|
| 297 |
-
except Exception as
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
def image_to_tensor(image):
|
| 301 |
"""Convert PIL Image to tensor"""
|
|
@@ -318,11 +310,24 @@ def validate_enhance_input_size(image):
|
|
| 318 |
f"Consider resizing your image first."
|
| 319 |
)
|
| 320 |
|
| 321 |
-
def
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
validate_enhance_input_size(image)
|
| 327 |
model = load_upscaler_model()
|
| 328 |
tensor = image_to_tensor(image)
|
|
@@ -355,7 +360,10 @@ def advanced_tile_upscale(image, scale=4):
|
|
| 355 |
x1 = min(x + tile_size, width)
|
| 356 |
tile = tensor[:, :, y:y1, x:x1]
|
| 357 |
|
| 358 |
-
upscaled_tile = model(tile)
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
scale_y = upscaled_tile.shape[-2] // tile.shape[-2]
|
| 361 |
scale_x = upscaled_tile.shape[-1] // tile.shape[-1]
|
|
@@ -370,12 +378,27 @@ def advanced_tile_upscale(image, scale=4):
|
|
| 370 |
|
| 371 |
oy0, oy1 = y * scale_y, y1 * scale_y
|
| 372 |
ox0, ox1 = x * scale_x, x1 * scale_x
|
| 373 |
-
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
output = output / weights.clamp_min(1)
|
| 377 |
return tensor_to_image(output)
|
| 378 |
|
|
|
|
|
|
|
|
|
|
| 379 |
# ============================================================================
|
| 380 |
# ENHANCED DETAILER (Multi-stage processing)
|
| 381 |
# ============================================================================
|
|
@@ -446,54 +469,9 @@ def apply_high_frequency_details(image, amount=0.6):
|
|
| 446 |
return result
|
| 447 |
|
| 448 |
# ============================================================================
|
| 449 |
-
#
|
| 450 |
# ============================================================================
|
| 451 |
|
| 452 |
-
def load_face_restoration_model():
|
| 453 |
-
"""Load GFPGAN model for face restoration"""
|
| 454 |
-
global _face_restoration_model
|
| 455 |
-
if _face_restoration_model is not None:
|
| 456 |
-
return _face_restoration_model
|
| 457 |
-
|
| 458 |
-
try:
|
| 459 |
-
from gfpgan import GFPGANer
|
| 460 |
-
model_path = download_model_with_retry(FACE_RESTORATION_MODEL, FACE_RESTORATION_WEIGHTS)
|
| 461 |
-
|
| 462 |
-
restorer = GFPGANer(
|
| 463 |
-
model_path=model_path,
|
| 464 |
-
upscale=1,
|
| 465 |
-
arch='clean',
|
| 466 |
-
channel_multiplier=2,
|
| 467 |
-
bg_upsampler=None
|
| 468 |
-
)
|
| 469 |
-
|
| 470 |
-
_face_restoration_model = restorer
|
| 471 |
-
print("Successfully loaded GFPGAN face restoration model")
|
| 472 |
-
return _face_restoration_model
|
| 473 |
-
|
| 474 |
-
except ImportError:
|
| 475 |
-
print("GFPGAN not available, face restoration will use fallback methods")
|
| 476 |
-
return None
|
| 477 |
-
except Exception as e:
|
| 478 |
-
print(f"Failed to load face restoration model: {e}")
|
| 479 |
-
return None
|
| 480 |
-
|
| 481 |
-
def restore_faces(image):
|
| 482 |
-
"""Restore faces in an image using GFPGAN"""
|
| 483 |
-
restorer = load_face_restoration_model()
|
| 484 |
-
if restorer is None:
|
| 485 |
-
print("Face restoration model not available, using skin repair fallback")
|
| 486 |
-
return repair_skin_texture(image)
|
| 487 |
-
|
| 488 |
-
try:
|
| 489 |
-
img_array = np.array(image.convert("RGB"))
|
| 490 |
-
restored_array, _ = restorer.enhance(img_array, has_aligned=False, only_center_face=False, paste_back=True)
|
| 491 |
-
restored_image = Image.fromarray(restored_array.astype(np.uint8))
|
| 492 |
-
return restored_image
|
| 493 |
-
except Exception as e:
|
| 494 |
-
print(f"Face restoration failed: {e}, using skin repair fallback")
|
| 495 |
-
return repair_skin_texture(image)
|
| 496 |
-
|
| 497 |
def remove_artifacts(image):
|
| 498 |
"""Remove compression artifacts and noise"""
|
| 499 |
denoised = image.filter(ImageFilter.MedianFilter(size=3))
|
|
@@ -563,14 +541,96 @@ def add_film_grain(image, seed):
|
|
| 563 |
array = np.clip(array + grain, 0, 255)
|
| 564 |
return Image.fromarray(array.astype(np.uint8), mode="RGB")
|
| 565 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
# ============================================================================
|
| 567 |
# ENHANCED APPLY ENHANCEMENT (Main enhancement pipeline)
|
| 568 |
# ============================================================================
|
| 569 |
|
| 570 |
def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
| 571 |
-
"""
|
| 572 |
-
Apply various enhancement modes to the image
|
| 573 |
-
"""
|
| 574 |
mode = enhance_mode or ENHANCE_MODE_OFF
|
| 575 |
if mode not in ENHANCE_MODE_CHOICES:
|
| 576 |
raise gr.Error(f"Unknown enhance mode: {mode}")
|
|
@@ -578,61 +638,43 @@ def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
|
| 578 |
return image
|
| 579 |
|
| 580 |
enhanced = image.convert("RGB")
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
total_steps = 3
|
| 587 |
-
elif mode == ENHANCE_MODE_MAX_DETAIL:
|
| 588 |
-
total_steps = 4
|
| 589 |
-
elif mode == ENHANCE_MODE_FACE_ENHANCE:
|
| 590 |
-
total_steps = 2
|
| 591 |
-
elif mode == ENHANCE_MODE_FULL_ENHANCE:
|
| 592 |
-
total_steps = 5
|
| 593 |
-
|
| 594 |
step = 0
|
| 595 |
-
|
| 596 |
-
if
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
step += 1
|
| 603 |
-
progress(0.5 * step / total_steps, desc="Upscaling...")
|
| 604 |
-
enhanced = advanced_tile_upscale(enhanced)
|
| 605 |
-
return enhanced
|
| 606 |
-
|
| 607 |
-
if mode in (ENHANCE_MODE_CLEAN, ENHANCE_MODE_FULL_ENHANCE):
|
| 608 |
-
if progress:
|
| 609 |
-
step += 1
|
| 610 |
-
progress(0.7 * step / total_steps, desc="Removing artifacts...")
|
| 611 |
-
enhanced = remove_artifacts(enhanced)
|
| 612 |
-
if progress:
|
| 613 |
-
step += 1
|
| 614 |
-
progress(0.7 * step / total_steps, desc="Repairing skin and faces...")
|
| 615 |
-
enhanced = enhanced_skin_repair(enhanced)
|
| 616 |
-
enhanced = restore_faces(enhanced)
|
| 617 |
-
|
| 618 |
-
if mode in (ENHANCE_MODE_UPSCALE, ENHANCE_MODE_CLEAN, ENHANCE_MODE_MAX_DETAIL, ENHANCE_MODE_FULL_ENHANCE):
|
| 619 |
if progress:
|
| 620 |
step += 1
|
| 621 |
-
progress(0.
|
| 622 |
-
enhanced =
|
| 623 |
-
|
| 624 |
-
|
|
|
|
| 625 |
if progress:
|
| 626 |
step += 1
|
| 627 |
-
progress(0.
|
| 628 |
-
enhanced =
|
| 629 |
-
|
| 630 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
if progress:
|
| 632 |
step += 1
|
| 633 |
-
progress(0.
|
| 634 |
-
enhanced =
|
| 635 |
-
|
|
|
|
| 636 |
return enhanced
|
| 637 |
|
| 638 |
# ============================================================================
|
|
@@ -796,9 +838,10 @@ css = """
|
|
| 796 |
with gr.Blocks(css=css) as demo:
|
| 797 |
with gr.Column(elem_id="col-container"):
|
| 798 |
gr.HTML(f"""
|
|
|
|
| 799 |
<div id="logo-title">
|
| 800 |
<h1>Pro Realism Edit Studio - Enhanced</h1>
|
| 801 |
-
<h2>Rapid Edit with
|
| 802 |
</div>
|
| 803 |
""")
|
| 804 |
|
|
@@ -806,15 +849,13 @@ with gr.Blocks(css=css) as demo:
|
|
| 806 |
Powered by:
|
| 807 |
- [Qwen-Image-Edit-2511](https://huggingface.co/Qwen/Qwen-Image-Edit-2511)
|
| 808 |
- [Phr00t's Rapid-AIO v23](https://huggingface.co/Phr00t/Qwen-Image-Edit-Rapid-AIO) accelerated transformer
|
| 809 |
-
-
|
| 810 |
-
- [GFPGAN](https://github.com/TencentARC/GFPGAN) for face restoration
|
| 811 |
|
| 812 |
Upload an image and enter your prompt to edit it.
|
| 813 |
|
| 814 |
Pro Tips:
|
| 815 |
-
- Use
|
| 816 |
-
- Use Max Detail for
|
| 817 |
-
- Use Full Enhance for comprehensive improvement
|
| 818 |
""")
|
| 819 |
|
| 820 |
with gr.Row():
|
|
@@ -840,11 +881,9 @@ with gr.Blocks(css=css) as demo:
|
|
| 840 |
enhance_info = gr.Markdown("""
|
| 841 |
Enhancement Options:
|
| 842 |
- Off: No post-processing
|
| 843 |
-
- Upscale
|
| 844 |
-
- Clean
|
| 845 |
-
- Max Detail:
|
| 846 |
-
- Face Enhance: Specialized face restoration + upscaling
|
| 847 |
-
- Full Enhance: Complete pipeline (clean + detail + face + upscale)
|
| 848 |
""", visible=False)
|
| 849 |
|
| 850 |
run_button = gr.Button("Generate!", variant="primary")
|
|
|
|
| 5 |
|
| 6 |
Advanced image editing and enhancement studio powered by:
|
| 7 |
- Qwen-Image-Edit-2511 with Phr00t's Rapid-AIO v23 accelerated transformer
|
| 8 |
+
- Nomos-family tiled upscaling
|
| 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.1
|
| 13 |
"""
|
| 14 |
|
| 15 |
import gradio as gr
|
|
|
|
| 42 |
|
| 43 |
# Base model configuration
|
| 44 |
BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
|
| 45 |
+
APP_VERSION = "1.0.1"
|
| 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",
|
|
|
|
| 51 |
PHR00T_TRANSFORMER_PREFIX = "model.diffusion_model."
|
| 52 |
VIDEO_SPACE_ID = os.environ.get("VIDEO_SPACE_ID", "").strip()
|
| 53 |
|
| 54 |
+
# Qwen-optimized enhancement configuration
|
| 55 |
+
UPSCALER_MODEL_ID = os.environ.get("UPSCALER_MODEL_ID", "Phips/4xNomos8k_atd_jpg").strip()
|
| 56 |
+
UPSCALER_MODEL_FILENAME = os.environ.get("UPSCALER_MODEL_FILENAME", "4xNomos8k_atd_jpg.safetensors").strip()
|
| 57 |
UPSCALER_TILE_SIZE = int(os.environ.get("UPSCALER_TILE_SIZE", "512"))
|
| 58 |
UPSCALER_TILE_OVERLAP = int(os.environ.get("UPSCALER_TILE_OVERLAP", "64"))
|
| 59 |
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"
|
|
|
|
| 70 |
# ============================================================================
|
| 71 |
|
| 72 |
ENHANCE_MODE_OFF = "Off"
|
| 73 |
+
ENHANCE_MODE_UPSCALE = "Upscale"
|
| 74 |
+
ENHANCE_MODE_CLEAN = "Clean"
|
| 75 |
ENHANCE_MODE_MAX_DETAIL = "Max Detail"
|
| 76 |
+
ENHANCE_MODE_CHOICES = [ENHANCE_MODE_OFF, ENHANCE_MODE_UPSCALE, ENHANCE_MODE_CLEAN, ENHANCE_MODE_MAX_DETAIL]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
# ============================================================================
|
| 79 |
# GLOBAL MODEL CACHE
|
| 80 |
# ============================================================================
|
| 81 |
|
| 82 |
_upscaler_model = None
|
|
|
|
| 83 |
_detail_enhancement_model = None
|
| 84 |
|
| 85 |
# ============================================================================
|
|
|
|
| 248 |
print("Applied FA3 attention processor optimization")
|
| 249 |
|
| 250 |
# ============================================================================
|
| 251 |
+
# QWEN-OPTIMIZED UPSCALER
|
| 252 |
# ============================================================================
|
| 253 |
|
| 254 |
def load_upscaler_model():
|
| 255 |
+
"""Load a Spandrel upscaler, defaulting to the Nomos model used by this Space."""
|
| 256 |
global _upscaler_model
|
| 257 |
if _upscaler_model is not None:
|
| 258 |
return _upscaler_model
|
|
|
|
| 265 |
raise gr.Error("Enhance mode requires spandrel and spandrel_extra_arches to be installed. "
|
| 266 |
"Install with: pip install spandrel spandrel_extra_arches") from exc
|
| 267 |
|
| 268 |
+
candidates = [
|
| 269 |
+
(UPSCALER_MODEL_ID, UPSCALER_MODEL_FILENAME),
|
| 270 |
+
("Phips/4xNomos8k_atd_jpg", "4xNomos8k_atd_jpg.safetensors"),
|
| 271 |
+
]
|
| 272 |
+
seen = set()
|
| 273 |
+
errors = []
|
| 274 |
+
for repo_id, filename in candidates:
|
| 275 |
+
key = (repo_id, filename)
|
| 276 |
+
if key in seen:
|
| 277 |
+
continue
|
| 278 |
+
seen.add(key)
|
| 279 |
try:
|
| 280 |
+
model_path = download_model_with_retry(repo_id, filename)
|
| 281 |
model = spandrel.ModelLoader().load_from_file(model_path)
|
| 282 |
model.eval().to(device)
|
| 283 |
_upscaler_model = model
|
| 284 |
+
print(f"Successfully loaded upscaler: {repo_id}/{filename}")
|
| 285 |
return _upscaler_model
|
| 286 |
+
except Exception as exc:
|
| 287 |
+
errors.append(f"{repo_id}/{filename}: {exc}")
|
| 288 |
+
print(f"Failed to load upscaler {repo_id}/{filename}: {exc}")
|
| 289 |
+
|
| 290 |
+
raise gr.Error(f"Failed to load all upscaler models: {' | '.join(errors)}")
|
| 291 |
|
| 292 |
def image_to_tensor(image):
|
| 293 |
"""Convert PIL Image to tensor"""
|
|
|
|
| 310 |
f"Consider resizing your image first."
|
| 311 |
)
|
| 312 |
|
| 313 |
+
def _tile_weight(height, width, overlap_y, overlap_x, touches_top, touches_bottom, touches_left, touches_right, dtype, device):
|
| 314 |
+
weight = torch.ones((1, 1, height, width), dtype=dtype, device=device)
|
| 315 |
+
if overlap_y > 1 and not touches_top:
|
| 316 |
+
ramp = torch.linspace(0.0, 1.0, overlap_y, dtype=dtype, device=device).view(1, 1, overlap_y, 1)
|
| 317 |
+
weight[:, :, :overlap_y, :] *= ramp
|
| 318 |
+
if overlap_y > 1 and not touches_bottom:
|
| 319 |
+
ramp = torch.linspace(1.0, 0.0, overlap_y, dtype=dtype, device=device).view(1, 1, overlap_y, 1)
|
| 320 |
+
weight[:, :, -overlap_y:, :] *= ramp
|
| 321 |
+
if overlap_x > 1 and not touches_left:
|
| 322 |
+
ramp = torch.linspace(0.0, 1.0, overlap_x, dtype=dtype, device=device).view(1, 1, 1, overlap_x)
|
| 323 |
+
weight[:, :, :, :overlap_x] *= ramp
|
| 324 |
+
if overlap_x > 1 and not touches_right:
|
| 325 |
+
ramp = torch.linspace(1.0, 0.0, overlap_x, dtype=dtype, device=device).view(1, 1, 1, overlap_x)
|
| 326 |
+
weight[:, :, :, -overlap_x:] *= ramp
|
| 327 |
+
return weight
|
| 328 |
+
|
| 329 |
+
def tile_upscale(image, scale=4):
|
| 330 |
+
"""Tiled Spandrel upscaling with feathered overlaps to avoid visible seams."""
|
| 331 |
validate_enhance_input_size(image)
|
| 332 |
model = load_upscaler_model()
|
| 333 |
tensor = image_to_tensor(image)
|
|
|
|
| 360 |
x1 = min(x + tile_size, width)
|
| 361 |
tile = tensor[:, :, y:y1, x:x1]
|
| 362 |
|
| 363 |
+
upscaled_tile = model(tile)
|
| 364 |
+
if isinstance(upscaled_tile, (tuple, list)):
|
| 365 |
+
upscaled_tile = upscaled_tile[0]
|
| 366 |
+
upscaled_tile = upscaled_tile.clamp(0, 1)
|
| 367 |
|
| 368 |
scale_y = upscaled_tile.shape[-2] // tile.shape[-2]
|
| 369 |
scale_x = upscaled_tile.shape[-1] // tile.shape[-1]
|
|
|
|
| 378 |
|
| 379 |
oy0, oy1 = y * scale_y, y1 * scale_y
|
| 380 |
ox0, ox1 = x * scale_x, x1 * scale_x
|
| 381 |
+
blend = _tile_weight(
|
| 382 |
+
upscaled_tile.shape[-2],
|
| 383 |
+
upscaled_tile.shape[-1],
|
| 384 |
+
min(overlap * scale_y, max(1, upscaled_tile.shape[-2] // 2)),
|
| 385 |
+
min(overlap * scale_x, max(1, upscaled_tile.shape[-1] // 2)),
|
| 386 |
+
y == 0,
|
| 387 |
+
y1 == height,
|
| 388 |
+
x == 0,
|
| 389 |
+
x1 == width,
|
| 390 |
+
upscaled_tile.dtype,
|
| 391 |
+
upscaled_tile.device,
|
| 392 |
+
)
|
| 393 |
+
output[:, :, oy0:oy1, ox0:ox1] += upscaled_tile * blend
|
| 394 |
+
weights[:, :, oy0:oy1, ox0:ox1] += blend
|
| 395 |
|
| 396 |
output = output / weights.clamp_min(1)
|
| 397 |
return tensor_to_image(output)
|
| 398 |
|
| 399 |
+
def advanced_tile_upscale(image, scale=4):
|
| 400 |
+
return tile_upscale(image, scale=scale)
|
| 401 |
+
|
| 402 |
# ============================================================================
|
| 403 |
# ENHANCED DETAILER (Multi-stage processing)
|
| 404 |
# ============================================================================
|
|
|
|
| 469 |
return result
|
| 470 |
|
| 471 |
# ============================================================================
|
| 472 |
+
# LEGACY CLEANUP HELPERS
|
| 473 |
# ============================================================================
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
def remove_artifacts(image):
|
| 476 |
"""Remove compression artifacts and noise"""
|
| 477 |
denoised = image.filter(ImageFilter.MedianFilter(size=3))
|
|
|
|
| 541 |
array = np.clip(array + grain, 0, 255)
|
| 542 |
return Image.fromarray(array.astype(np.uint8), mode="RGB")
|
| 543 |
|
| 544 |
+
def _resize_to_even_dimensions(width, height):
|
| 545 |
+
return max(2, width - (width % 2)), max(2, height - (height % 2))
|
| 546 |
+
|
| 547 |
+
def qwen_artifact_precondition(image):
|
| 548 |
+
"""
|
| 549 |
+
Qwen edit outputs can show halftone/plastic texture at larger dimensions.
|
| 550 |
+
A small Lanczos downsample before the SR pass suppresses that pattern while
|
| 551 |
+
preserving prompt structure for the upscaler.
|
| 552 |
+
"""
|
| 553 |
+
base = image.convert("RGB")
|
| 554 |
+
long_edge = max(base.size)
|
| 555 |
+
if long_edge <= ENHANCE_QWEN_DOWNSCALE_TRIGGER_EDGE:
|
| 556 |
+
return base
|
| 557 |
+
|
| 558 |
+
factor = min(0.95, max(0.50, ENHANCE_QWEN_DOWNSCALE_FACTOR))
|
| 559 |
+
new_width, new_height = _resize_to_even_dimensions(
|
| 560 |
+
int(round(base.width * factor)),
|
| 561 |
+
int(round(base.height * factor)),
|
| 562 |
+
)
|
| 563 |
+
if new_width >= base.width or new_height >= base.height:
|
| 564 |
+
return base
|
| 565 |
+
return base.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
| 566 |
+
|
| 567 |
+
def qwen_skin_mask(image):
|
| 568 |
+
base = image.convert("RGB")
|
| 569 |
+
ycbcr = np.asarray(base.convert("YCbCr"))
|
| 570 |
+
y = ycbcr[:, :, 0]
|
| 571 |
+
cb = ycbcr[:, :, 1]
|
| 572 |
+
cr = ycbcr[:, :, 2]
|
| 573 |
+
mask = (
|
| 574 |
+
(y > 45)
|
| 575 |
+
& (cb >= 72)
|
| 576 |
+
& (cb <= 145)
|
| 577 |
+
& (cr >= 128)
|
| 578 |
+
& (cr <= 182)
|
| 579 |
+
).astype(np.uint8) * 255
|
| 580 |
+
|
| 581 |
+
try:
|
| 582 |
+
import cv2
|
| 583 |
+
kernel = np.ones((3, 3), np.uint8)
|
| 584 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
|
| 585 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
| 586 |
+
mask = cv2.GaussianBlur(mask, (0, 0), 1.6)
|
| 587 |
+
except ImportError:
|
| 588 |
+
mask_image = Image.fromarray(mask, mode="L")
|
| 589 |
+
mask_image = mask_image.filter(ImageFilter.MinFilter(size=3))
|
| 590 |
+
mask_image = mask_image.filter(ImageFilter.MaxFilter(size=5))
|
| 591 |
+
mask_image = mask_image.filter(ImageFilter.GaussianBlur(radius=1.4))
|
| 592 |
+
mask = np.asarray(mask_image)
|
| 593 |
+
|
| 594 |
+
return Image.fromarray(mask.astype(np.uint8), mode="L")
|
| 595 |
+
|
| 596 |
+
def qwen_defect_mask(image):
|
| 597 |
+
base = image.convert("RGB")
|
| 598 |
+
skin = np.asarray(qwen_skin_mask(base)).astype(np.float32) / 255.0
|
| 599 |
+
gray = np.asarray(base.convert("L")).astype(np.float32)
|
| 600 |
+
local = np.asarray(base.convert("L").filter(ImageFilter.MedianFilter(size=5))).astype(np.float32)
|
| 601 |
+
pits = np.maximum(local - gray, 0.0)
|
| 602 |
+
smears = np.abs(gray - local)
|
| 603 |
+
mask = ((pits > 14.0) | (smears > 22.0)) & (skin > 0.12)
|
| 604 |
+
mask = (mask.astype(np.uint8) * 255)
|
| 605 |
+
mask_image = Image.fromarray(mask, mode="L")
|
| 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)
|
| 612 |
+
repaired = base.filter(ImageFilter.MedianFilter(size=3)).filter(ImageFilter.GaussianBlur(radius=0.22))
|
| 613 |
+
repaired = ImageEnhance.Sharpness(repaired).enhance(1.06)
|
| 614 |
+
mask = mask.point(lambda value: int(value * max(0.0, min(1.0, strength))))
|
| 615 |
+
return Image.composite(repaired, base, mask)
|
| 616 |
+
|
| 617 |
+
def qwen_micro_detail(image, amount=0.35):
|
| 618 |
+
if amount <= 0:
|
| 619 |
+
return image.convert("RGB")
|
| 620 |
+
base = image.convert("RGB")
|
| 621 |
+
sharpened = base.filter(ImageFilter.UnsharpMask(radius=0.9, percent=int(95 * amount), threshold=3))
|
| 622 |
+
contrast = ImageEnhance.Contrast(sharpened).enhance(1.0 + amount * 0.05)
|
| 623 |
+
return Image.blend(base, contrast, alpha=min(0.65, amount))
|
| 624 |
+
|
| 625 |
+
def add_photographic_grain(image, seed):
|
| 626 |
+
return add_film_grain(image, seed)
|
| 627 |
+
|
| 628 |
# ============================================================================
|
| 629 |
# ENHANCED APPLY ENHANCEMENT (Main enhancement pipeline)
|
| 630 |
# ============================================================================
|
| 631 |
|
| 632 |
def apply_enhancement(image, enhance_mode, seed=0, progress=None):
|
| 633 |
+
"""Apply the Qwen 2511 / Rapid-AIO v23 post-process pipeline."""
|
|
|
|
|
|
|
| 634 |
mode = enhance_mode or ENHANCE_MODE_OFF
|
| 635 |
if mode not in ENHANCE_MODE_CHOICES:
|
| 636 |
raise gr.Error(f"Unknown enhance mode: {mode}")
|
|
|
|
| 638 |
return image
|
| 639 |
|
| 640 |
enhanced = image.convert("RGB")
|
| 641 |
+
total_steps = {
|
| 642 |
+
ENHANCE_MODE_UPSCALE: 2,
|
| 643 |
+
ENHANCE_MODE_CLEAN: 3,
|
| 644 |
+
ENHANCE_MODE_MAX_DETAIL: 5,
|
| 645 |
+
}[mode]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
step = 0
|
| 647 |
+
|
| 648 |
+
if progress:
|
| 649 |
+
step += 1
|
| 650 |
+
progress(0.85 * step / total_steps, desc="Preparing Qwen output...")
|
| 651 |
+
enhanced = qwen_artifact_precondition(enhanced)
|
| 652 |
+
|
| 653 |
+
if mode in (ENHANCE_MODE_CLEAN, ENHANCE_MODE_MAX_DETAIL):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
if progress:
|
| 655 |
step += 1
|
| 656 |
+
progress(0.85 * step / total_steps, desc="Repairing Qwen skin artifacts...")
|
| 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
|
| 663 |
+
progress(0.85 * step / total_steps, desc="Building micro detail...")
|
| 664 |
+
enhanced = qwen_micro_detail(enhanced, amount=0.36)
|
| 665 |
+
|
| 666 |
+
if progress:
|
| 667 |
+
step += 1
|
| 668 |
+
progress(0.85 * step / total_steps, desc="Upscaling with Nomos tiles...")
|
| 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...")
|
| 675 |
+
enhanced = qwen_micro_detail(enhanced, amount=0.22)
|
| 676 |
+
enhanced = add_photographic_grain(enhanced, seed)
|
| 677 |
+
|
| 678 |
return enhanced
|
| 679 |
|
| 680 |
# ============================================================================
|
|
|
|
| 838 |
with gr.Blocks(css=css) as demo:
|
| 839 |
with gr.Column(elem_id="col-container"):
|
| 840 |
gr.HTML(f"""
|
| 841 |
+
<!-- v{APP_VERSION} -->
|
| 842 |
<div id="logo-title">
|
| 843 |
<h1>Pro Realism Edit Studio - Enhanced</h1>
|
| 844 |
+
<h2>Rapid Edit with Qwen-aware Nomos enhancement</h2>
|
| 845 |
</div>
|
| 846 |
""")
|
| 847 |
|
|
|
|
| 849 |
Powered by:
|
| 850 |
- [Qwen-Image-Edit-2511](https://huggingface.co/Qwen/Qwen-Image-Edit-2511)
|
| 851 |
- [Phr00t's Rapid-AIO v23](https://huggingface.co/Phr00t/Qwen-Image-Edit-Rapid-AIO) accelerated transformer
|
| 852 |
+
- Nomos-family 4x tiled upscaling with masked Qwen artifact repair
|
|
|
|
| 853 |
|
| 854 |
Upload an image and enter your prompt to edit it.
|
| 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():
|
|
|
|
| 881 |
enhance_info = gr.Markdown("""
|
| 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: Masked repair + micro detail + upscaling + final grain
|
|
|
|
|
|
|
| 887 |
""", visible=False)
|
| 888 |
|
| 889 |
run_button = gr.Button("Generate!", variant="primary")
|
requirements.txt
CHANGED
|
@@ -16,22 +16,18 @@ Peft
|
|
| 16 |
Torchao==0.11.0
|
| 17 |
|
| 18 |
# Image processing and upscaling
|
| 19 |
-
|
| 20 |
-
|
| 21 |
Pillow
|
| 22 |
Numpy
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
# Install with: pip install
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
#
|
| 29 |
-
# Install with: pip install
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
# SciPy for advanced image processing (fallback for face detection)
|
| 33 |
-
# Install with: pip install scipy
|
| 34 |
-
# scipy>=1.7.0
|
| 35 |
|
| 36 |
# Gradio and utilities
|
| 37 |
Gradio
|
|
@@ -45,6 +41,6 @@ Gradio_client
|
|
| 45 |
# Performance monitoring (optional)
|
| 46 |
# Install with: pip install psutil
|
| 47 |
|
| 48 |
-
# Note: For
|
| 49 |
-
# pip install -r requirements_enhanced.txt
|
| 50 |
-
# pip install
|
|
|
|
| 16 |
Torchao==0.11.0
|
| 17 |
|
| 18 |
# Image processing and upscaling
|
| 19 |
+
spandrel
|
| 20 |
+
spandrel_extra_arches
|
| 21 |
Pillow
|
| 22 |
Numpy
|
| 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
|
| 30 |
+
# scipy>=1.7.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
# Gradio and utilities
|
| 33 |
Gradio
|
|
|
|
| 41 |
# Performance monitoring (optional)
|
| 42 |
# Install with: pip install psutil
|
| 43 |
|
| 44 |
+
# Note: For optional CPU-side mask acceleration, run:
|
| 45 |
+
# pip install -r requirements_enhanced.txt
|
| 46 |
+
# pip install opencv-python scipy
|