Deddy commited on
Commit
6ac308a
·
verified ·
1 Parent(s): 6d61fb0

Upload 8 files

Browse files
Files changed (8) hide show
  1. app.py +79 -0
  2. app_enhance.py +221 -0
  3. app_upscale.py +148 -0
  4. croper.py +135 -0
  5. enhance_utils.py +95 -0
  6. requirements.txt +12 -0
  7. segment_utils.py +123 -0
  8. themes.py +54 -0
app.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Image Enhancer Main App
3
+ -----------------------
4
+ Aplikasi utama Gradio untuk menjalankan UI image enhancer berbasis tab.
5
+ Untuk versi ini, hanya ada 1 tab "Enhance" yang memanggil demo dari modul app_enhance.
6
+
7
+ Created by _drat | 2025
8
+ """
9
+
10
+ import gradio as gr
11
+
12
+ from app_enhance import create_demo as create_demo_enhance # Import fungsi untuk membangun demo UI 'Enhance'
13
+ # from app_upscale import create_demo as create_demo_upscale # Import fungsi untuk membangun demo UI 'Upscale'
14
+ from themes import IndonesiaTheme # Impor tema custom
15
+
16
+ import warnings
17
+ warnings.filterwarnings("ignore")
18
+
19
+ # CSS untuk styling antarmuka
20
+ css = """
21
+ #col-left, #col-mid {
22
+ margin: 0 auto;
23
+ max-width: 400px;
24
+ padding: 10px;
25
+ border-radius: 15px;
26
+ background-color: #f9f9f9;
27
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
28
+ }
29
+ #col-right {
30
+ margin: 0 auto;
31
+ max-width: 400px;
32
+ padding: 10px;
33
+ border-radius: 15px;
34
+ background: linear-gradient(180deg, #B6BBC4, #EEEEEE);
35
+ color: white;
36
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
37
+ }
38
+ #col-bott {
39
+ margin: 0 auto;
40
+ padding: 10px;
41
+ border-radius: 15px;
42
+ background-color: #f9f9f9;
43
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
44
+ }
45
+ #banner {
46
+ width: 100%;
47
+ text-align: center;
48
+ margin-bottom: 20px;
49
+ }
50
+ #run-button {
51
+ background-color: #ff4b5c;
52
+ color: white;
53
+ font-weight: bold;
54
+ padding: 30px;
55
+ border-radius: 10px;
56
+ cursor: pointer;
57
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
58
+ }
59
+ #footer {
60
+ text-align: center;
61
+ margin-top: 20px;
62
+ color: silver;
63
+ }
64
+ #markdown-silver {
65
+ color: silver; /* Mengatur warna font Markdown menjadi silver */
66
+ }
67
+ """
68
+
69
+
70
+ # Inisialisasi Gradio Blocks dengan custom CSS (style.css)
71
+ with gr.Blocks(css=css, theme=IndonesiaTheme()) as demo:
72
+ with gr.Tabs():
73
+ with gr.Tab(label="✨ Enhance"):
74
+ create_demo_enhance() # Panggil UI Enhance dari app_enhance.py (atau sesuai modul Anda)
75
+ # with gr.Tab(label="🚀 Upscale"):
76
+ # create_demo_upscale() # Panggil UI Upscale dari app_upscale.py (atau sesuai modul Anda)
77
+
78
+ # Jalankan aplikasi Gradio (local/web)
79
+ demo.queue(api_open=False).launch(show_api=False)
app_enhance.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Image Enhancer App
3
+ ------------------
4
+ Aplikasi berbasis Gradio untuk meningkatkan kualitas gambar dengan berbagai teknik pemrosesan citra
5
+ seperti perbaikan resolusi, penajaman, dan penghilangan noise.
6
+ Dapat digunakan secara lokal maupun sebagai web-app AI image enhancement.
7
+
8
+ Created by _drat | 2025
9
+ """
10
+
11
+ # Import library standar dan eksternal yang dibutuhkan
12
+ import os
13
+ import subprocess
14
+ import spaces # Library dari HuggingFace untuk GPU management & dekorator
15
+ import torch # Library untuk deep learning, cek GPU
16
+ import cv2 # OpenCV untuk pengolahan gambar
17
+ import uuid # Untuk generate nama file unik
18
+ import gradio as gr # Untuk membuat UI web berbasis Python
19
+ import numpy as np # Operasi numerik, termasuk array
20
+
21
+ from PIL import Image # Untuk memproses dan menyimpan gambar
22
+ # Import arsitektur model dan utilitas untuk enhancement & upscaling
23
+ from basicsr.archs.srvgg_arch import SRVGGNetCompact
24
+ from gfpgan.utils import GFPGANer
25
+ from realesrgan.utils import RealESRGANer
26
+
27
+ import warnings
28
+ warnings.filterwarnings("ignore")
29
+
30
+ # Fungsi untuk menjalankan command shell (misal: wget file model)
31
+ def runcmd(cmd, verbose = False):
32
+ process = subprocess.Popen(
33
+ cmd,
34
+ stdout = subprocess.PIPE,
35
+ stderr = subprocess.PIPE,
36
+ text = True,
37
+ shell = True
38
+ )
39
+ std_out, std_err = process.communicate()
40
+ if verbose:
41
+ print(std_out.strip(), std_err)
42
+ pass
43
+
44
+ # Pastikan model AI (GFPGAN dan RealESRGAN) sudah terdownload. Jika belum, otomatis download.
45
+ if not os.path.exists('GFPGANv1.4.pth'):
46
+ runcmd("wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P .")
47
+ if not os.path.exists('realesr-general-x4v3.pth'):
48
+ runcmd("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P .")
49
+
50
+ # Inisialisasi model Real-ESRGAN untuk upscaling gambar
51
+ model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
52
+ model_path = 'realesr-general-x4v3.pth'
53
+ half = True if torch.cuda.is_available() else False # Pakai mode half-precision jika ada GPU (lebih cepat)
54
+ upsampler = RealESRGANer(
55
+ scale=4, model_path=model_path, model=model,
56
+ tile=0, tile_pad=10, pre_pad=0, half=half
57
+ )
58
+
59
+ # Fungsi utama untuk enhance gambar (dipanggil saat tombol di UI ditekan)
60
+ @spaces.GPU(duration=15)
61
+ def enhance_image(
62
+ input_image: Image, # Gambar input dari user (PIL Image)
63
+ scale: int, # Skala upscaling (misal: 2x, 4x)
64
+ enhance_mode: str, # Mode enhance: face saja, image saja, atau keduanya
65
+ ):
66
+ only_face = enhance_mode == "Only Face Enhance"
67
+
68
+ # Pilih mode enhancer: hanya wajah, hanya gambar, atau kombinasi
69
+ if enhance_mode == "Only Face Enhance":
70
+ face_enhancer = GFPGANer(
71
+ model_path='GFPGANv1.4.pth', upscale=scale,
72
+ arch='clean', channel_multiplier=2
73
+ )
74
+ elif enhance_mode == "Only Image Enhance":
75
+ face_enhancer = None # Tidak enhance wajah, hanya upscaling gambar saja
76
+ else:
77
+ face_enhancer = GFPGANer(
78
+ model_path='GFPGANv1.4.pth', upscale=scale,
79
+ arch='clean', channel_multiplier=2, bg_upsampler=upsampler
80
+ )
81
+
82
+ # Konversi gambar input ke format BGR (OpenCV)
83
+ img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
84
+ h, w = img.shape[0:2]
85
+
86
+ # Optional: perbesar gambar jika terlalu kecil (kurang dari 300px)
87
+ if h < 300:
88
+ img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
89
+
90
+ # Proses enhance gambar (tergantung mode)
91
+ if face_enhancer is not None:
92
+ # Enhance wajah (GFPGAN), output gambar hasil enhancement
93
+ _, _, output = face_enhancer.enhance(
94
+ img, has_aligned=False, only_center_face=only_face, paste_back=True
95
+ )
96
+ else:
97
+ # Hanya upscaling image (tanpa enhance wajah)
98
+ output, _ = upsampler.enhance(img, outscale=scale)
99
+
100
+ # --- Optional resize (tidak digunakan)
101
+ # if scale != 2:
102
+ # interpolation = cv2.INTER_AREA if scale < 2 else cv2.INTER_LANCZOS4
103
+ # h, w = img.shape[0:2]
104
+ # output = cv2.resize(output, (int(w * scale / 2), int(h * scale / 2)), interpolation=interpolation)
105
+
106
+ # Batasi hasil enhance agar tidak lebih dari 3480px (biar aman dan efisien)
107
+ h, w = output.shape[0:2]
108
+ max_size = 3480
109
+ if h > max_size:
110
+ w = int(w * max_size / h)
111
+ h = max_size
112
+ if w > max_size:
113
+ h = int(h * max_size / w)
114
+ w = max_size
115
+
116
+ # Resize hasil akhir
117
+ output = cv2.resize(output, (w, h), interpolation=cv2.INTER_LANCZOS4)
118
+
119
+ # Konversi kembali ke RGB & PIL Image (untuk output Gradio)
120
+ enhanced_image = Image.fromarray(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
121
+
122
+ # Simpan gambar hasil enhance ke folder output dengan nama unik
123
+ tmpPrefix = "/tmp/gradio/"
124
+ extension = 'png'
125
+ # targetDir = f"{tmpPrefix}output/"
126
+ targetDir = "output/"
127
+ if not os.path.exists(targetDir):
128
+ os.makedirs(targetDir)
129
+
130
+ enhanced_path = f"{targetDir}{uuid.uuid4()}.{extension}"
131
+ enhanced_image.save(enhanced_path, quality=100)
132
+
133
+ return enhanced_image, enhanced_path # Return: image untuk preview dan path file untuk download
134
+
135
+
136
+ #
137
+ # DEMO DI SINI
138
+ #
139
+
140
+ def create_demo() -> gr.Blocks:
141
+ with gr.Blocks() as demo:
142
+ # Feature Image dengan border subtle & shadow
143
+ gr.HTML(
144
+ """
145
+ <div style='display:flex; justify-content:center;'>
146
+ <img src='https://i.ibb.co/jkkT7MZQ/feature-image-enhancer.jpg'
147
+ alt='Feature Image'
148
+ style='height:200px; width:auto; border-radius:24px; box-shadow:0 4px 18px #ffa07430; border:2.5px solid #ffe0b3;'
149
+ id='feature-image'/>
150
+ </div>
151
+ """
152
+ )
153
+ # Judul & Tagline dengan emoji & gradient text
154
+ gr.Markdown("""
155
+ <div style="text-align:center; margin-top: -10px; margin-bottom: 14px;">
156
+ <span style="font-size:2.1rem; font-family:'Quicksand',sans-serif; font-weight:800; background: linear-gradient(90deg,#d84040,#ff914d 60%); -webkit-background-clip: text; color:transparent; display:inline-block;">
157
+ <span style="vertical-align:middle;">📸</span> Image Enhancer <b>Pro</b> <span style="font-size:1.1em;vertical-align:middle;">✨</span>
158
+ </span><br>
159
+ <span style="font-size:1.12rem; color:#d84040; font-family:'Inter',sans-serif;">
160
+ <span style="vertical-align:middle;">🤖</span> Foto blur? Jadikan jernih & tajam pakai AI!
161
+ </span>
162
+ </div>
163
+ """)
164
+
165
+ # Input dan Output dalam card, dengan icon pada title
166
+ with gr.Row():
167
+ with gr.Column(scale=1):
168
+ with gr.Group(elem_id="input-card"):
169
+ gr.Markdown("<div class='card-title'><span style='font-size:1.3em;vertical-align:middle;'>🖼️</span> <b>Gambar Asli</b></div>")
170
+ input_image = gr.Image(label="", type="pil", elem_id="input-image", show_label=False)
171
+ gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>⬆️</span> Upload foto format <b>JPG/PNG</b>, max 5MB</div>")
172
+ with gr.Column(scale=1):
173
+ with gr.Group(elem_id="output-card"):
174
+ gr.Markdown("<div class='card-title'><span style='font-size:1.3em;vertical-align:middle;'>🌈</span> <b>Hasil AI Enhance</b></div>")
175
+ output_image = gr.Image(label="", type="pil", interactive=False, elem_id="output-image", show_label=False)
176
+ enhance_image_path = gr.File(label="⬇️ Download (.png)", interactive=False, elem_id="download-btn")
177
+ gr.Markdown("""
178
+ <div class='hint' style="color:#ff914d;"><span style='font-size:1.2em;'>💡</span> Klik untuk download hasilnya!</div>
179
+ """)
180
+
181
+ # Kontrol dengan ikon pada label
182
+ with gr.Group(elem_id="control-card"):
183
+ with gr.Row():
184
+ scale = gr.Slider(
185
+ minimum=1,
186
+ maximum=4,
187
+ value=2,
188
+ step=1,
189
+ label="🔎 Upscale x",
190
+ elem_id="scale-slider"
191
+ )
192
+ enhance_mode = gr.Dropdown(
193
+ label="🛠️ Mode Enhance",
194
+ choices=[
195
+ "Only Face Enhance",
196
+ "Only Image Enhance",
197
+ "Face Enhance + Image Enhance",
198
+ ],
199
+ value="Face Enhance + Image Enhance",
200
+ elem_id="mode-dropdown"
201
+ )
202
+ g_btn = gr.Button("🚀 Enhance Sekarang!", elem_id="enhance-btn", size="lg")
203
+
204
+ g_btn.click(
205
+ fn=enhance_image,
206
+ inputs=[input_image, scale, enhance_mode],
207
+ outputs=[output_image, enhance_image_path],
208
+ )
209
+ # Footer dengan ikon & sentuhan branding
210
+ # Tambahkan footer di bagian bawah
211
+ gr.HTML("""
212
+ <footer id="footer">
213
+ Transfer Energi Semesta Digital © 2024 | 🇮🇩 Untuk Indonesia Jaya!
214
+ </footer>
215
+ """)
216
+
217
+ return demo
218
+
219
+
220
+
221
+
app_upscale.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Image Upscaler App
3
+ ------------------
4
+ Aplikasi AI berbasis Gradio yang memanfaatkan Stable Diffusion Upscaler untuk meningkatkan resolusi gambar.
5
+ Tersedia juga fitur segmentasi & restorasi area tertentu pada gambar (misal: wajah).
6
+ Aplikasi mendukung input prompt teks untuk conditioning hasil upscaling.
7
+
8
+ Created by _drat | 2025
9
+ """
10
+
11
+ # Import library eksternal yang diperlukan
12
+ import requests
13
+ from PIL import Image
14
+ from io import BytesIO
15
+ from diffusers import StableDiffusionUpscalePipeline # Pipeline Stable Diffusion untuk upscaling gambar
16
+ import torch
17
+ import gradio as gr
18
+ import time
19
+ import spaces
20
+
21
+ # Import fungsi segmentasi dan restorasi (definisi di segment_utils.py)
22
+ from segment_utils import(
23
+ segment_image, # Untuk segmentasi area penting pada gambar (misal: wajah)
24
+ restore_result, # Untuk menggabungkan hasil upscaling dengan gambar asli
25
+ )
26
+
27
+ # Setup device: gunakan CUDA (GPU) jika tersedia, jika tidak fallback ke CPU
28
+ device = "cuda" if torch.cuda.is_available() else "cpu"
29
+ print(f'{device} is available') # Debug: print device yang digunakan
30
+
31
+ # Load model Stable Diffusion Upscaler dari HuggingFace
32
+ model_id = "stabilityai/stable-diffusion-x4-upscaler"
33
+ upscale_pipe = StableDiffusionUpscalePipeline.from_pretrained(model_id, torch_dtype=torch.float16)
34
+ upscale_pipe = upscale_pipe.to(device)
35
+
36
+ # Default prompt dan kategori (untuk input Gradio)
37
+ DEFAULT_SRC_PROMPT = "a person with pefect face"
38
+ DEFAULT_CATEGORY = "face"
39
+
40
+ # Fungsi utama untuk membuat UI aplikasi Gradio
41
+ def create_demo() -> gr.Blocks:
42
+
43
+ # --- [ Function Definitions Tetap Seperti Asli Anda ] ---
44
+ @spaces.GPU(duration=30)
45
+ def upscale_image(
46
+ input_image: Image,
47
+ prompt: str,
48
+ num_inference_steps: int = 10,
49
+ ):
50
+ time_cost_str = ''
51
+ run_task_time = 0
52
+ run_task_time, time_cost_str = get_time_cost(run_task_time, time_cost_str)
53
+ upscaled_image = upscale_pipe(
54
+ prompt=prompt,
55
+ image=input_image,
56
+ num_inference_steps=num_inference_steps,
57
+ ).images[0]
58
+ run_task_time, time_cost_str = get_time_cost(run_task_time, time_cost_str)
59
+ return upscaled_image, time_cost_str
60
+
61
+ def get_time_cost(run_task_time, time_cost_str):
62
+ now_time = int(time.time()*1000)
63
+ if run_task_time == 0:
64
+ time_cost_str = 'start'
65
+ else:
66
+ if time_cost_str != '':
67
+ time_cost_str += f'-->'
68
+ time_cost_str += f'{now_time - run_task_time}'
69
+ run_task_time = now_time
70
+ return run_task_time, time_cost_str
71
+
72
+ # --- [ UI Section ] ---
73
+ with gr.Blocks(css="creative_enhance.css") as demo:
74
+ gr.HTML("""
75
+ <div style='display:flex; justify-content:center;'>
76
+ <img src='https://i.ibb.co/pvWSfsMJ/feature-image-upscaler.jpg' alt='Feature Image' style='height:200px; width:auto; border-radius:24px; box-shadow:0 4px 18px #ffa07430; border:2.5px solid #ffe0b3;' id='feature-image'/>
77
+ </div>
78
+ <div style='text-align:center;margin-top:6px;'>
79
+ <span style='font-size:2.0rem;font-weight:800;color:#d84040;letter-spacing:0.04em;font-family:Quicksand,sans-serif;'>
80
+ 🔬 <b>AI Image Upscaler</b> <span style='font-size:1.2em;vertical-align:middle;'>🚀</span>
81
+ </span>
82
+ <br>
83
+ <span style='font-size:1.1rem;color:#ff914d;font-family:Inter,sans-serif;'>Perbesar gambar <b>HD</b> otomatis, detail makin nyata!</span>
84
+ </div>
85
+ """)
86
+
87
+ # Input prompt & parameter di satu card
88
+ with gr.Row():
89
+ with gr.Group(elem_id="control-card"):
90
+ with gr.Row():
91
+ input_image_prompt = gr.Textbox(
92
+ lines=1, label="🎯 Prompt AI (opsional)",
93
+ value=DEFAULT_SRC_PROMPT, elem_id="input-image-prompt"
94
+ )
95
+ num_inference_steps = gr.Number(
96
+ label="⚙️ Steps (Quality)", value=5, elem_id="num-inference"
97
+ )
98
+ generate_size = gr.Number(
99
+ label="📐 Size (px)", value=512, elem_id="generate-size"
100
+ )
101
+ g_btn = gr.Button("🪄 Upscale Sekarang", elem_id="upscale-btn")
102
+
103
+ # Input & Output Gambar
104
+ with gr.Row():
105
+ with gr.Column(scale=1):
106
+ with gr.Group(elem_id="input-card"):
107
+ gr.Markdown("<div class='card-title'><span style='font-size:1.2em;'>🖼️</span> Gambar Asli</div>")
108
+ input_image = gr.Image(label="", type="pil", elem_id="input-image", show_label=False)
109
+ gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>⬆️</span> JPG/PNG max 5MB</div>")
110
+ with gr.Column(scale=1):
111
+ with gr.Group(elem_id="output-card"):
112
+ gr.Markdown("<div class='card-title'><span style='font-size:1.2em;'>💡</span> Upscale Preview</div>")
113
+ restored_image = gr.Image(label="Hasil Akhir", format="png", type="pil", interactive=False)
114
+ origin_area_image = gr.Image(label="", format="png", type="pil", interactive=False, visible=False)
115
+ upscaled_image = gr.Image(label="Upscaled", format="png", type="pil", interactive=False)
116
+ download_path = gr.File(label="⬇️ Download Image", interactive=False, elem_id="download-btn")
117
+ gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>💾</span> Download hasil upscale PNG</div>")
118
+ generated_cost = gr.Textbox(label="⏱️ Time (ms)", visible=True, interactive=False)
119
+
120
+ # Hidden inputs untuk workflow
121
+ category = gr.Textbox(label="Category", value=DEFAULT_CATEGORY, visible=False)
122
+ mask_expansion = gr.Number(label="Mask Expansion", value=20, visible=False)
123
+ mask_dilation = gr.Slider(minimum=0, maximum=10, value=2, step=1, label="Mask Dilation", visible=False)
124
+ croper = gr.State()
125
+
126
+ # Workflow chaining
127
+ g_btn.click(
128
+ fn=segment_image,
129
+ inputs=[input_image, category, generate_size, mask_expansion, mask_dilation],
130
+ outputs=[origin_area_image, croper],
131
+ ).success(
132
+ fn=upscale_image,
133
+ inputs=[origin_area_image, input_image_prompt, num_inference_steps],
134
+ outputs=[upscaled_image, generated_cost],
135
+ ).success(
136
+ fn=restore_result,
137
+ inputs=[croper, category, upscaled_image],
138
+ outputs=[restored_image, download_path],
139
+ )
140
+
141
+ gr.Markdown("""
142
+ <div style='text-align:center;color:#aaa;font-size:0.98rem;margin-top:14px;'>
143
+ &copy; 2025 <b>AI Image Upscaler</b> • Powered by <b>_drat</b> 🚀
144
+ </div>
145
+ """)
146
+
147
+ return demo
148
+
croper.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Croper Utility
3
+ --------------
4
+ Kelas utilitas untuk melakukan crop area tertentu (berbasis mask) pada gambar, lalu menyiapkan square crop image dan square mask.
5
+ Juga menyediakan fungsi untuk mengembalikan (restore) hasil modifikasi ke gambar asli.
6
+
7
+ Created by _drat | 2025
8
+ """
9
+
10
+ import PIL
11
+ import numpy as np
12
+ from PIL import Image
13
+
14
+ class Croper:
15
+ def __init__(
16
+ self,
17
+ input_image: PIL.Image, # Gambar input (format PIL Image)
18
+ target_mask: np.ndarray, # Mask biner area target (array numpy)
19
+ mask_size: int = 256, # Ukuran akhir mask & image yang dihasilkan (default 256x256)
20
+ mask_expansion: int = 20, # Ekspansi area mask (agar crop lebih lebar)
21
+ ):
22
+ self.input_image = input_image
23
+ self.target_mask = target_mask
24
+ self.mask_size = mask_size
25
+ self.mask_expansion = mask_expansion
26
+
27
+ # Fungsi utama untuk crop area sesuai mask dan membentuk square crop & mask
28
+ def corp_mask_image(self):
29
+ target_mask = self.target_mask
30
+ input_image = self.input_image
31
+ mask_expansion = self.mask_expansion
32
+ original_width, original_height = input_image.size
33
+
34
+ # Cari koordinat bounding box area mask (area bertanda True/1)
35
+ mask_indices = np.where(target_mask)
36
+ start_y = np.min(mask_indices[0])
37
+ end_y = np.max(mask_indices[0])
38
+ start_x = np.min(mask_indices[1])
39
+ end_x = np.max(mask_indices[1])
40
+
41
+ mask_height = end_y - start_y
42
+ mask_width = end_x - start_x
43
+
44
+ # Ambil sisi terpanjang untuk square crop
45
+ max_side_length = max(mask_height, mask_width)
46
+
47
+ # Ekspansi area mask agar crop tidak terlalu sempit
48
+ height_diff = (max_side_length - mask_height) // 2
49
+ width_diff = (max_side_length - mask_width) // 2
50
+ start_y = start_y - mask_expansion - height_diff
51
+ if start_y < 0:
52
+ start_y = 0
53
+ end_y = end_y + mask_expansion + height_diff
54
+ if end_y > original_height:
55
+ end_y = original_height
56
+ start_x = start_x - mask_expansion - width_diff
57
+ if start_x < 0:
58
+ start_x = 0
59
+ end_x = end_x + mask_expansion + width_diff
60
+ if end_x > original_width:
61
+ end_x = original_width
62
+
63
+ expanded_height = end_y - start_y
64
+ expanded_width = end_x - start_x
65
+ expanded_max_side_length = max(expanded_height, expanded_width)
66
+
67
+ # Crop mask dari area yang sudah diekspansi
68
+ crop_mask = target_mask[start_y:end_y, start_x:end_x]
69
+
70
+ # Hitung posisi crop mask pada square mask
71
+ crop_mask_start_y = (expanded_max_side_length - expanded_height) // 2
72
+ crop_mask_end_y = crop_mask_start_y + expanded_height
73
+ crop_mask_start_x = (expanded_max_side_length - expanded_width) // 2
74
+ crop_mask_end_x = crop_mask_start_x + expanded_width
75
+
76
+ # Buat square mask dengan area crop mask di tengah
77
+ square_mask = np.zeros((expanded_max_side_length, expanded_max_side_length), dtype=target_mask.dtype)
78
+ square_mask[crop_mask_start_y:crop_mask_end_y, crop_mask_start_x:crop_mask_end_x] = crop_mask
79
+ square_mask_image = Image.fromarray((square_mask * 255).astype(np.uint8))
80
+
81
+ # Crop image asli sesuai area ekspansi
82
+ crop_image = input_image.crop((start_x, start_y, end_x, end_y))
83
+ # Buat square image, lalu paste crop image di tengah
84
+ square_image = Image.new("RGB", (expanded_max_side_length, expanded_max_side_length))
85
+ square_image.paste(crop_image, (crop_mask_start_x, crop_mask_start_y))
86
+
87
+ # Simpan koordinat/ukuran untuk keperluan restore nanti
88
+ self.origin_start_x = start_x
89
+ self.origin_start_y = start_y
90
+ self.origin_end_x = end_x
91
+ self.origin_end_y = end_y
92
+
93
+ self.square_start_x = crop_mask_start_x
94
+ self.square_start_y = crop_mask_start_y
95
+ self.square_end_x = crop_mask_end_x
96
+ self.square_end_y = crop_mask_end_y
97
+
98
+ self.square_length = expanded_max_side_length
99
+ self.square_mask_image = square_mask_image
100
+ self.square_image = square_image
101
+ self.corp_mask = crop_mask
102
+
103
+ # Resize hasil square mask & image ke mask_size (misal: 256x256) untuk kebutuhan pipeline AI
104
+ mask_size = self.mask_size
105
+ self.resized_square_mask_image = square_mask_image.resize((mask_size, mask_size))
106
+ self.resized_square_image = square_image.resize((mask_size, mask_size))
107
+
108
+ return self.resized_square_mask_image # Kembalikan mask hasil resize
109
+
110
+ # Fungsi untuk restore hasil generate ke gambar asli dengan bantuan mask transparan
111
+ def restore_result(self, generated_image):
112
+ square_length = self.square_length
113
+ # Resize hasil generate ke ukuran square original
114
+ generated_image = generated_image.resize((square_length, square_length))
115
+ square_mask_image = self.square_mask_image
116
+ # Crop area yang sudah di-generate sesuai posisi awal
117
+ cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
118
+ cropped_square_mask_image = square_mask_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
119
+
120
+ # Tempel hasil generate ke gambar asli dengan mask transparan
121
+ restored_image = self.input_image.copy()
122
+ restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y), cropped_square_mask_image)
123
+
124
+ return restored_image
125
+
126
+ # Versi restore tanpa mask (area hasil tempelkan langsung tanpa transparansi)
127
+ def restore_result_v2(self, generated_image):
128
+ square_length = self.square_length
129
+ generated_image = generated_image.resize((square_length, square_length))
130
+ cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
131
+
132
+ restored_image = self.input_image.copy()
133
+ restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y))
134
+
135
+ return restored_image
enhance_utils.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Enhance Utils
3
+ -------------
4
+ Utility functions untuk meningkatkan kualitas gambar dengan menggunakan model AI:
5
+ - Real-ESRGAN untuk upscaling gambar umum
6
+ - GFPGAN untuk enhancement wajah (face restoration)
7
+
8
+ Created by _drat | 2025
9
+ """
10
+
11
+ import os
12
+ import torch
13
+ import cv2
14
+ import numpy as np
15
+ import subprocess
16
+
17
+ from PIL import Image
18
+ from gfpgan.utils import GFPGANer
19
+ from basicsr.archs.srvgg_arch import SRVGGNetCompact
20
+ from realesrgan.utils import RealESRGANer
21
+
22
+ # Fungsi untuk menjalankan perintah shell (misal: pip, wget)
23
+ def runcmd(cmd, verbose = False, *args, **kwargs):
24
+ process = subprocess.Popen(
25
+ cmd,
26
+ stdout = subprocess.PIPE,
27
+ stderr = subprocess.PIPE,
28
+ text = True,
29
+ shell = True
30
+ )
31
+ std_out, std_err = process.communicate()
32
+ if verbose:
33
+ print(std_out.strip(), std_err)
34
+ pass
35
+
36
+ # Debug: Menampilkan daftar library yang terinstall di environment
37
+ runcmd("pip freeze")
38
+
39
+ # Download model GFPGAN jika belum ada di direktori saat ini
40
+ if not os.path.exists('GFPGANv1.4.pth'):
41
+ runcmd("wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P .")
42
+
43
+ # Download model RealESRGAN jika belum ada di direktori saat ini
44
+ if not os.path.exists('realesr-general-x4v3.pth'):
45
+ runcmd("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P .")
46
+
47
+ # Inisialisasi model Real-ESRGAN (untuk upscaling gambar umum)
48
+ model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
49
+ model_path = 'realesr-general-x4v3.pth'
50
+ half = True if torch.cuda.is_available() else False # Pakai half precision jika tersedia GPU
51
+ upsampler = RealESRGANer(
52
+ scale=4,
53
+ model_path=model_path,
54
+ model=model,
55
+ tile=0,
56
+ tile_pad=10,
57
+ pre_pad=0,
58
+ half=half
59
+ )
60
+
61
+ # Inisialisasi enhancer wajah (GFPGAN)
62
+ face_enhancer = GFPGANer(
63
+ model_path='GFPGANv1.4.pth',
64
+ upscale=1, # Default tidak melakukan upscaling (hanya enhance wajah)
65
+ arch='clean',
66
+ channel_multiplier=2
67
+ )
68
+
69
+ # Fungsi utama enhancement gambar
70
+ def enhance_image(
71
+ pil_image: Image, # Input gambar dalam format PIL
72
+ enhance_face: bool = False, # Apakah perlu enhance wajah?
73
+ ):
74
+ # Konversi PIL image ke array BGR (OpenCV)
75
+ img = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)
76
+
77
+ h, w = img.shape[0:2]
78
+ # Optional: jika gambar kecil, perbesar dulu agar hasil enhancement lebih maksimal
79
+ if h < 300:
80
+ img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
81
+
82
+ # Enhance menggunakan GFPGAN (wajah) atau Real-ESRGAN (umum)
83
+ if enhance_face:
84
+ # Hanya enhance bagian wajah utama di tengah gambar
85
+ _, _, output = face_enhancer.enhance(
86
+ img, has_aligned=False, only_center_face=True, paste_back=True
87
+ )
88
+ else:
89
+ # Upscale seluruh gambar dengan Real-ESRGAN (outscale=2x)
90
+ output, _ = upsampler.enhance(img, outscale=2)
91
+
92
+ # Konversi hasil output ke format PIL RGB untuk siap digunakan di aplikasi
93
+ pil_output = Image.fromarray(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
94
+
95
+ return pil_output
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ torchvision
3
+ diffusers
4
+ transformers
5
+ accelerate
6
+ mediapipe
7
+ gradio
8
+ spaces
9
+ gfpgan
10
+ git+https://github.com/XPixelGroup/BasicSR@master
11
+ facexlib
12
+ realesrgan
segment_utils.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Segment Utils
3
+ -------------
4
+ Utilitas segmentasi gambar berbasis MediaPipe dan mask biner untuk aplikasi image enhancer/upscaler:
5
+ - Segmentasi area: wajah, rambut, pakaian, dll.
6
+ - Membuat dan mengelola mask hasil segmentasi serta integrasi dengan class Croper
7
+ - Mendukung proses restore hasil generate ke gambar asli
8
+
9
+ Created by _drat | 2025
10
+ """
11
+
12
+ import numpy as np
13
+ import mediapipe as mp
14
+ import uuid
15
+ import os
16
+
17
+ from PIL import Image
18
+ from mediapipe.tasks import python
19
+ from mediapipe.tasks.python import vision
20
+ from scipy.ndimage import binary_dilation
21
+ from croper import Croper
22
+
23
+ # Inisialisasi model segmentasi MediaPipe (load tflite model untuk multiclass selfie segmentation)
24
+ segment_model = "checkpoints/selfie_multiclass_256x256.tflite"
25
+ base_options = python.BaseOptions(model_asset_path=segment_model)
26
+ options = vision.ImageSegmenterOptions(base_options=base_options, output_category_mask=True)
27
+ segmenter = vision.ImageSegmenter.create_from_options(options)
28
+
29
+ # Fungsi untuk mengembalikan hasil generate ke gambar asli, dengan mask transparan hasil segmentasi
30
+ def restore_result(croper, category, generated_image):
31
+ square_length = croper.square_length
32
+ generated_image = generated_image.resize((square_length, square_length))
33
+
34
+ # Crop area hasil generate, lalu buat mask hasil restore
35
+ cropped_generated_image = generated_image.crop((croper.square_start_x, croper.square_start_y, croper.square_end_x, croper.square_end_y))
36
+ cropped_square_mask_image = get_restore_mask_image(croper, category, cropped_generated_image)
37
+
38
+ # Tempel hasil generate ke gambar asli dengan mask transparan
39
+ restored_image = croper.input_image.copy()
40
+ restored_image.paste(cropped_generated_image, (croper.origin_start_x, croper.origin_start_y), cropped_square_mask_image)
41
+
42
+ extension = 'png'
43
+ tmpPrefix = "/tmp/gradio/"
44
+ # targetDir = f"{tmpPrefix}output/"
45
+ targetDir = "output/"
46
+ if not os.path.exists(targetDir):
47
+ os.makedirs(targetDir)
48
+
49
+ path = f"{targetDir}{uuid.uuid4()}.{extension}"
50
+ restored_image.save(path, quality=100)
51
+
52
+ return restored_image, path
53
+
54
+ # Fungsi utama segmentasi gambar sesuai kategori
55
+ def segment_image(input_image, category, input_size, mask_expansion, mask_dilation):
56
+ mask_size = int(input_size)
57
+ mask_expansion = int(mask_expansion)
58
+
59
+ # Konversi input image ke format yang sesuai untuk MediaPipe
60
+ image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(input_image))
61
+ segmentation_result = segmenter.segment(image)
62
+ category_mask = segmentation_result.category_mask
63
+ category_mask_np = category_mask.numpy_view()
64
+
65
+ # Pilih fungsi pembentukan mask sesuai kategori segmentasi
66
+ if category == "hair":
67
+ target_mask = get_hair_mask(category_mask_np, mask_dilation)
68
+ elif category == "clothes":
69
+ target_mask = get_clothes_mask(category_mask_np, mask_dilation)
70
+ elif category == "face":
71
+ target_mask = get_face_mask(category_mask_np, mask_dilation)
72
+ else:
73
+ target_mask = get_face_mask(category_mask_np, mask_dilation)
74
+
75
+ # Buat objek Croper dengan mask hasil segmentasi, untuk crop & resize
76
+ croper = Croper(input_image, target_mask, mask_size, mask_expansion)
77
+ croper.corp_mask_image()
78
+ origin_area_image = croper.resized_square_image
79
+
80
+ return origin_area_image, croper # Kembalikan area hasil crop + objek croper
81
+
82
+ # Fungsi pembuat mask khusus area wajah (+opsi dilasi mask)
83
+ def get_face_mask(category_mask_np, dilation=1):
84
+ face_skin_mask = category_mask_np == 3
85
+ if dilation > 0:
86
+ face_skin_mask = binary_dilation(face_skin_mask, iterations=dilation)
87
+ return face_skin_mask
88
+
89
+ # Fungsi pembuat mask khusus area baju (dan kulit tubuh)
90
+ def get_clothes_mask(category_mask_np, dilation=1):
91
+ body_skin_mask = category_mask_np == 2
92
+ clothes_mask = category_mask_np == 4
93
+ combined_mask = np.logical_or(body_skin_mask, clothes_mask)
94
+ combined_mask = binary_dilation(combined_mask, iterations=4)
95
+ if dilation > 0:
96
+ combined_mask = binary_dilation(combined_mask, iterations=dilation)
97
+ return combined_mask
98
+
99
+ # Fungsi pembuat mask khusus area rambut
100
+ def get_hair_mask(category_mask_np, dilation=1):
101
+ hair_mask = category_mask_np == 1
102
+ if dilation > 0:
103
+ hair_mask = binary_dilation(hair_mask, iterations=dilation)
104
+ return hair_mask
105
+
106
+ # Fungsi untuk membuat mask kombinasi hasil generate dan area awal (untuk proses restore)
107
+ def get_restore_mask_image(croper, category, generated_image):
108
+ image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(generated_image))
109
+ segmentation_result = segmenter.segment(image)
110
+ category_mask = segmentation_result.category_mask
111
+ category_mask_np = category_mask.numpy_view()
112
+
113
+ if category == "hair":
114
+ target_mask = get_hair_mask(category_mask_np, 0)
115
+ elif category == "clothes":
116
+ target_mask = get_clothes_mask(category_mask_np, 0)
117
+ elif category == "face":
118
+ target_mask = get_face_mask(category_mask_np, 0)
119
+
120
+ # Gabungkan mask hasil segmentasi dan mask area awal croper
121
+ combined_mask = np.logical_or(target_mask, croper.corp_mask)
122
+ mask_image = Image.fromarray((combined_mask * 255).astype(np.uint8))
123
+ return mask_image
themes.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import Iterable
3
+ from gradio.themes.base import Base
4
+ from gradio.themes.utils import colors, fonts, sizes
5
+
6
+ class IndonesiaTheme(Base):
7
+ def __init__(
8
+ self,
9
+ *,
10
+ primary_hue: colors.Color | str = colors.red,
11
+ secondary_hue: colors.Color | str = colors.orange,
12
+ neutral_hue: colors.Color | str = colors.gray,
13
+ spacing_size: sizes.Size | str = sizes.spacing_md,
14
+ radius_size: sizes.Size | str = sizes.radius_md,
15
+ text_size: sizes.Size | str = sizes.text_lg,
16
+ font: fonts.Font
17
+ | str
18
+ | Iterable[fonts.Font | str] = (
19
+ fonts.GoogleFont("Quicksand"),
20
+ "ui-sans-serif",
21
+ "sans-serif",
22
+ ),
23
+ font_mono: fonts.Font
24
+ | str
25
+ | Iterable[fonts.Font | str] = (
26
+ fonts.GoogleFont("IBM Plex Mono"),
27
+ "ui-monospace",
28
+ "monospace",
29
+ ),
30
+ ):
31
+ super().__init__(
32
+ primary_hue=primary_hue,
33
+ secondary_hue=secondary_hue,
34
+ neutral_hue=neutral_hue,
35
+ spacing_size=spacing_size,
36
+ radius_size=radius_size,
37
+ text_size=text_size,
38
+ font=font,
39
+ font_mono=font_mono,
40
+ )
41
+ super().set(
42
+ # Ganti background dengan gradasi putih ke coklat muda (contoh: #fff ke #fbeee0)
43
+ body_background_fill="linear-gradient(to bottom, #fff 65%, #fbeee0 100%)",
44
+ body_background_fill_dark="linear-gradient(to bottom, #c8b7a6 70%, #7c6957 100%)", # gradasi coklat untuk dark mode
45
+ button_primary_background_fill="linear-gradient(90deg, #d84a4a, #b33030)",
46
+ button_primary_background_fill_hover="linear-gradient(90deg, #e85b5b, #cc4b4b)",
47
+ button_primary_text_color="white",
48
+ button_primary_background_fill_dark="linear-gradient(90deg, #b33030, #8f1f1f)",
49
+ slider_color="*secondary_300",
50
+ slider_color_dark="*secondary_600",
51
+ block_title_text_weight="600",
52
+ block_border_width="2px",
53
+ block_shadow="*shadow_drop_lg",
54
+ )