Spaces:
Running
Running
Updated app.py
Browse files
app.py
CHANGED
@@ -67,7 +67,9 @@ def download_image_for_gradio(url: str, timeout: int = 20) -> Image.Image | None
|
|
67 |
"""Downloads an image from a URL for Gradio, returns PIL Image or raises gr.Error."""
|
68 |
print(f"Attempting to download image from: {url}")
|
69 |
if not url or not url.startswith(('http://', 'https://')):
|
70 |
-
raise
|
|
|
|
|
71 |
|
72 |
try:
|
73 |
headers = {'User-Agent': 'Gradio-Image-Processor/1.1'}
|
@@ -108,19 +110,27 @@ def download_image_for_gradio(url: str, timeout: int = 20) -> Image.Image | None
|
|
108 |
|
109 |
# --- Processing Function (Handles the ML part with progress) ---
|
110 |
def run_processing(input_pil_image: Image.Image, progress=gr.Progress(track_tqdm=True)):
|
111 |
-
"""Processes the
|
112 |
if MODEL is None:
|
113 |
# Include the more specific error message if loading failed
|
114 |
error_msg = f"Model is not loaded. Cannot process image. Load error: {MODEL_LOAD_ERROR}" if MODEL_LOAD_ERROR else "Model is not loaded. Cannot process image."
|
115 |
raise gr.Error(error_msg)
|
116 |
if input_pil_image is None:
|
117 |
-
|
|
|
|
|
|
|
118 |
|
119 |
start_time = time.time()
|
120 |
print("Starting image processing...")
|
121 |
progress(0, desc="Preparing for processing...")
|
122 |
|
123 |
try:
|
|
|
|
|
|
|
|
|
|
|
124 |
output_pil_image = process_image_from_data(
|
125 |
input_pil_image=input_pil_image,
|
126 |
model=MODEL,
|
@@ -150,26 +160,54 @@ def run_processing(input_pil_image: Image.Image, progress=gr.Progress(track_tqdm
|
|
150 |
|
151 |
return output_pil_image, str(save_path)
|
152 |
|
153 |
-
# --- Wrapper Function for Button Click ---
|
154 |
-
def
|
155 |
-
"""
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
|
174 |
|
175 |
def show_image(index):
|
@@ -192,68 +230,98 @@ with gr.Blocks() as demo:
|
|
192 |
gr.Markdown(f"""
|
193 |
# Watermark Remover
|
194 |
|
195 |
-
**Disclaimer: This project was made to showcase my Deep Learning skills with no intention to cause harm to any business or infringe on any IP and it will be decommissioned as soon as I get a decent job offer.
|
196 |
If you still have any issue, please reach out to me at [[email protected]](mailto:[email protected]).**
|
197 |
""")
|
198 |
|
199 |
-
gr.Markdown(f"""
|
200 |
-
This is a demo of a DL model which takes in URL of an image with watermark and gives out the image with its watermark removed.
|
201 |
-
The image is broken into overlapping patches of 256x256 pixels with a stride of 64 before feeding them to the model,
|
202 |
-
the model infers on each patch separately and then they are stitched together to form the whole image again to give the final output.
|
203 |
-
""")
|
204 |
-
|
205 |
-
if MODEL_LOAD_ERROR:
|
206 |
-
gr.Markdown(f"<font color='red'>**Model Loading Error:** {MODEL_LOAD_ERROR}</font>")
|
207 |
-
|
208 |
with gr.Row():
|
209 |
with gr.Column(scale=1):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
image_url = gr.Textbox(
|
211 |
label="Image URL",
|
212 |
-
placeholder="Paste image URL (https://www.shutterstock.com/shutterstock/photos
|
213 |
-
info="Enter the direct link to a publicly accessible image file
|
214 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
submit_button = gr.Button("Process Image", variant="primary")
|
216 |
-
gr.Markdown("---")
|
217 |
gr.Examples(
|
218 |
-
|
219 |
-
[
|
220 |
-
|
221 |
-
[
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
224 |
)
|
225 |
|
226 |
with gr.Column(scale=2):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
with gr.Row():
|
|
|
228 |
original_image = gr.Image(label="Original Input Image", type="pil", interactive=False)
|
229 |
processed_image = gr.Image(label="Processed Output Image", type="pil", interactive=False)
|
230 |
download_button = gr.DownloadButton("Download Processed Image", visible=True, variant="secondary")
|
231 |
-
gr.Markdown("---") # Separator
|
232 |
-
gr.Markdown(f"""
|
233 |
-
**A bit about the model:** In this project, I have trained a GAN network,
|
234 |
-
with the Generator being inspired from Pix2Pix and Pix2PixHD architectures and the Discriminator is very similar to PatchGAN in Pix2Pix and CycleGAN.
|
235 |
-
For the loss, I have also added Perceptual Loss using VGG like in Pix2PixHD and SRGAN papers apart from the L1 and BCE loss.
|
236 |
-
""")
|
237 |
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
239 |
|
|
|
|
|
240 |
|
241 |
# --- Event Handling Logic ---
|
242 |
|
243 |
# 1. When Button is clicked:
|
244 |
-
# - Input: URL from textbox
|
245 |
-
# - Action: Call '
|
246 |
-
#
|
|
|
|
|
|
|
247 |
submit_button.click(
|
248 |
-
fn=
|
249 |
-
inputs=image_url,
|
250 |
-
outputs=[original_image, processed_image] # Target
|
251 |
)
|
252 |
|
253 |
-
# 2. When 'original_image' component *changes* (i.e., after successful
|
254 |
# - Input: The PIL image data from 'original_image'
|
255 |
# - Action: Call 'run_processing' (this function has the progress bar)
|
256 |
-
# - Output: Update the 'processed_image' component.
|
257 |
original_image.change(
|
258 |
fn=run_processing,
|
259 |
inputs=original_image,
|
@@ -261,24 +329,26 @@ with gr.Blocks() as demo:
|
|
261 |
# concurrency_limit=1 # Optional: Prevent multiple simultaneous processing runs if needed
|
262 |
)
|
263 |
|
|
|
|
|
264 |
processed_image.change(
|
265 |
fn=lambda img: gr.update(variant="primary" if img is not None else "secondary"),
|
266 |
inputs=processed_image,
|
267 |
outputs=download_button
|
268 |
)
|
269 |
|
|
|
|
|
270 |
download_button.click(
|
271 |
fn=lambda path: path if path else None,
|
272 |
inputs=saved_image_path,
|
273 |
-
outputs=download_button
|
274 |
)
|
275 |
|
276 |
|
277 |
-
|
278 |
# --- Launch the Application ---
|
279 |
if __name__ == "__main__":
|
280 |
print("Launching Gradio Blocks interface...")
|
281 |
# Set queue=True for better handling under load, especially with long-running processing
|
282 |
-
|
283 |
-
demo.launch(share=False, server_name="0.0.0.0", show_api=False)
|
284 |
-
|
|
|
67 |
"""Downloads an image from a URL for Gradio, returns PIL Image or raises gr.Error."""
|
68 |
print(f"Attempting to download image from: {url}")
|
69 |
if not url or not url.startswith(('http://', 'https://')):
|
70 |
+
# Don't raise error here, let the caller decide if URL is optional
|
71 |
+
print("Invalid or empty URL provided.")
|
72 |
+
return None # Indicate failure without raising error immediately
|
73 |
|
74 |
try:
|
75 |
headers = {'User-Agent': 'Gradio-Image-Processor/1.1'}
|
|
|
110 |
|
111 |
# --- Processing Function (Handles the ML part with progress) ---
|
112 |
def run_processing(input_pil_image: Image.Image, progress=gr.Progress(track_tqdm=True)):
|
113 |
+
"""Processes the input PIL image (from upload or download) and returns the result."""
|
114 |
if MODEL is None:
|
115 |
# Include the more specific error message if loading failed
|
116 |
error_msg = f"Model is not loaded. Cannot process image. Load error: {MODEL_LOAD_ERROR}" if MODEL_LOAD_ERROR else "Model is not loaded. Cannot process image."
|
117 |
raise gr.Error(error_msg)
|
118 |
if input_pil_image is None:
|
119 |
+
# This case might happen if the input component feeding this function is cleared
|
120 |
+
# before processing starts, or if the previous step failed silently.
|
121 |
+
print("run_processing called with None input.")
|
122 |
+
return None, None # Return None for both outputs
|
123 |
|
124 |
start_time = time.time()
|
125 |
print("Starting image processing...")
|
126 |
progress(0, desc="Preparing for processing...")
|
127 |
|
128 |
try:
|
129 |
+
# Ensure image is RGB before processing
|
130 |
+
if input_pil_image.mode != 'RGB':
|
131 |
+
print(f"Converting input image from {input_pil_image.mode} to RGB.")
|
132 |
+
input_pil_image = input_pil_image.convert('RGB')
|
133 |
+
|
134 |
output_pil_image = process_image_from_data(
|
135 |
input_pil_image=input_pil_image,
|
136 |
model=MODEL,
|
|
|
160 |
|
161 |
return output_pil_image, str(save_path)
|
162 |
|
163 |
+
# --- NEW Wrapper Function for Button Click (Handles URL or Upload) ---
|
164 |
+
def handle_input(url, uploaded_image):
|
165 |
+
"""
|
166 |
+
Determines the input source (URL or Upload), prepares the image,
|
167 |
+
and returns it for the 'original_image' display.
|
168 |
+
Clears the other input method and the processed image output.
|
169 |
+
Raises gr.Error if input is invalid or download fails.
|
170 |
+
"""
|
171 |
+
print("Handle input triggered.") # Debug print
|
172 |
+
input_image = None
|
173 |
+
final_url = url # Keep original url unless cleared
|
174 |
+
final_uploaded = None # Clear upload by default
|
175 |
+
|
176 |
+
if uploaded_image is not None:
|
177 |
+
print("Processing uploaded image.")
|
178 |
+
input_image = uploaded_image
|
179 |
+
final_url = "" # Clear URL field if upload is used
|
180 |
+
|
181 |
+
elif url:
|
182 |
+
print("Processing URL.")
|
183 |
+
try:
|
184 |
+
input_image = download_image_for_gradio(url)
|
185 |
+
if input_image is None:
|
186 |
+
# download_image_for_gradio returns None for non-http/https URLs
|
187 |
+
raise gr.Error("Invalid URL provided. Please enter a valid HTTP or HTTPS image URL.")
|
188 |
+
# Keep the url in the box if download succeeds
|
189 |
+
except gr.Error as e:
|
190 |
+
# If download fails, raise the Gradio error again
|
191 |
+
print(f"Download failed: {e}") # Debug print
|
192 |
+
raise e
|
193 |
+
except Exception as e:
|
194 |
+
# Catch other unexpected errors during the wrapper logic
|
195 |
+
print(f"Unexpected error in handle_input (download branch): {e}") # Debug print
|
196 |
+
import traceback
|
197 |
+
traceback.print_exc()
|
198 |
+
# Raise a Gradio error so the user sees something meaningful
|
199 |
+
raise gr.Error(f"An unexpected error occurred preparing the image from URL: {e}")
|
200 |
+
else:
|
201 |
+
# Neither URL nor upload provided
|
202 |
+
raise gr.Error("Please provide an image URL or upload an image file.")
|
203 |
+
|
204 |
+
# At this point, input_image should be a valid PIL image or an error was raised.
|
205 |
+
# Return:
|
206 |
+
# 1. The input PIL image for 'original_image'
|
207 |
+
# 2. None to clear 'processed_image'
|
208 |
+
# 3. The final URL value (empty if upload was used, original if URL was used)
|
209 |
+
# 4. None to clear the file upload input
|
210 |
+
return input_image, None, final_url, final_uploaded
|
211 |
|
212 |
|
213 |
def show_image(index):
|
|
|
230 |
gr.Markdown(f"""
|
231 |
# Watermark Remover
|
232 |
|
233 |
+
**Disclaimer: This project was made to showcase my Deep Learning skills with no intention to cause harm to any business or infringe on any IP and it will be decommissioned as soon as I get a decent job offer.
|
234 |
If you still have any issue, please reach out to me at [[email protected]](mailto:[email protected]).**
|
235 |
""")
|
236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
with gr.Row():
|
238 |
with gr.Column(scale=1):
|
239 |
+
gr.Markdown(f"""
|
240 |
+
This is a demo of a DL model which takes in an image with a watermark (either via URL or direct upload) and gives out the image with its watermark removed.
|
241 |
+
The image is broken into overlapping patches of {PATCH_KERNEL_SIZE}x{PATCH_KERNEL_SIZE} pixels with a stride of {PATCH_STRIDE} before feeding them to the model,
|
242 |
+
the model infers on each patch separately and then they are stitched together to form the whole image again to give the final output.
|
243 |
+
""")
|
244 |
+
|
245 |
+
if MODEL_LOAD_ERROR:
|
246 |
+
gr.Markdown(f"<font color='red'>**Model Loading Error:** {MODEL_LOAD_ERROR}</font>")
|
247 |
+
|
248 |
+
# Input Method 1: URL
|
249 |
image_url = gr.Textbox(
|
250 |
label="Image URL",
|
251 |
+
placeholder="Paste image URL (e.g., https://www.shutterstock.com/shutterstock/photos/.../image.jpg)",
|
252 |
+
info="Enter the direct link to a publicly accessible image file."
|
253 |
)
|
254 |
+
|
255 |
+
# Input Method 2: File Upload
|
256 |
+
file_upload_input = gr.Image(
|
257 |
+
label="Or Upload Image File",
|
258 |
+
type="pil", # Keep as PIL for direct use
|
259 |
+
sources=["upload"], # Specify only upload source
|
260 |
+
height=130
|
261 |
+
)
|
262 |
+
|
263 |
submit_button = gr.Button("Process Image", variant="primary")
|
264 |
+
gr.Markdown("---") # Separator
|
265 |
gr.Examples(
|
266 |
+
examples=[
|
267 |
+
[
|
268 |
+
"https://www.shutterstock.com/shutterstock/photos/2429728793/display_1500/stock-photo-monkey-funny-open-mouth-and-showing-teeth-crazy-nature-angry-short-hair-brown-grand-bassin-2429728793.jpg", None], # Need None for the upload input
|
269 |
+
[
|
270 |
+
"https://www.shutterstock.com/shutterstock/photos/1905929728/display_1500/stock-photo-skeptic-surprised-cat-thinking-dont-know-what-to-do-big-eyes-closeup-tabby-cat-look-side-dont-1905929728.jpg", None],
|
271 |
+
[
|
272 |
+
"https://www.shutterstock.com/shutterstock/photos/2501926843/display_1500/stock-photo-brunette-woman-laying-on-couch-cuddling-light-brown-dog-and-brown-tabby-cat-happy-2501926843.jpg", None]
|
273 |
+
],
|
274 |
+
inputs=[image_url, file_upload_input] # Link examples to both inputs
|
275 |
)
|
276 |
|
277 |
with gr.Column(scale=2):
|
278 |
+
gr.Markdown(f"""### Some Samples""")
|
279 |
+
img_display = gr.Image(type="pil")
|
280 |
+
with gr.Row():
|
281 |
+
prev_btn = gr.Button("<")
|
282 |
+
next_btn = gr.Button(">")
|
283 |
+
|
284 |
+
prev_btn.click(fn=prev_img, inputs=index_state, outputs=[img_display, index_state])
|
285 |
+
next_btn.click(fn=next_img, inputs=index_state, outputs=[img_display, index_state])
|
286 |
+
|
287 |
+
# Load the first image
|
288 |
+
demo.load(fn=show_image, inputs=index_state, outputs=[img_display, index_state])
|
289 |
+
|
290 |
with gr.Row():
|
291 |
+
# Make original_image interactive=False as it's now purely a display
|
292 |
original_image = gr.Image(label="Original Input Image", type="pil", interactive=False)
|
293 |
processed_image = gr.Image(label="Processed Output Image", type="pil", interactive=False)
|
294 |
download_button = gr.DownloadButton("Download Processed Image", visible=True, variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
|
296 |
+
gr.Markdown("---") # Separator
|
297 |
+
gr.Markdown(f"""
|
298 |
+
**A bit about the model:** In this project, I have trained a GAN network,
|
299 |
+
with the Generator being inspired from Pix2Pix and Pix2PixHD architectures and the Discriminator is very similar to PatchGAN in Pix2Pix.
|
300 |
+
For the loss, I have also added Perceptual Loss using VGG like in Pix2PixHD and SRGAN papers apart from the L1 and BCE loss.
|
301 |
+
""")
|
302 |
|
303 |
+
gr.Markdown(
|
304 |
+
f"""If you liked this project, you can find my CV [here]() or reach me out at [[email protected]](mailto:[email protected]).""")
|
305 |
|
306 |
# --- Event Handling Logic ---
|
307 |
|
308 |
# 1. When Button is clicked:
|
309 |
+
# - Input: URL from textbox AND image from file upload
|
310 |
+
# - Action: Call 'handle_input' wrapper function to decide which input to use,
|
311 |
+
# download if needed, and handle errors.
|
312 |
+
# - Output: Update 'original_image' with the chosen/downloaded image,
|
313 |
+
# clear 'processed_image', clear the URL field if upload was used,
|
314 |
+
# clear the file upload component.
|
315 |
submit_button.click(
|
316 |
+
fn=handle_input,
|
317 |
+
inputs=[image_url, file_upload_input],
|
318 |
+
outputs=[original_image, processed_image, image_url, file_upload_input] # Target outputs
|
319 |
)
|
320 |
|
321 |
+
# 2. When 'original_image' component *changes* (i.e., after successful input handling):
|
322 |
# - Input: The PIL image data from 'original_image'
|
323 |
# - Action: Call 'run_processing' (this function has the progress bar)
|
324 |
+
# - Output: Update the 'processed_image' component and the saved_image_path state.
|
325 |
original_image.change(
|
326 |
fn=run_processing,
|
327 |
inputs=original_image,
|
|
|
329 |
# concurrency_limit=1 # Optional: Prevent multiple simultaneous processing runs if needed
|
330 |
)
|
331 |
|
332 |
+
# 3. When 'processed_image' changes (after processing finishes or is cleared):
|
333 |
+
# - Action: Update the visibility/style of the download button.
|
334 |
processed_image.change(
|
335 |
fn=lambda img: gr.update(variant="primary" if img is not None else "secondary"),
|
336 |
inputs=processed_image,
|
337 |
outputs=download_button
|
338 |
)
|
339 |
|
340 |
+
# 4. When Download Button is clicked:
|
341 |
+
# - Action: Provide the file path stored in saved_image_path state to the download component.
|
342 |
download_button.click(
|
343 |
fn=lambda path: path if path else None,
|
344 |
inputs=saved_image_path,
|
345 |
+
outputs=download_button # The output of the click event for DownloadButton is the file path itself
|
346 |
)
|
347 |
|
348 |
|
|
|
349 |
# --- Launch the Application ---
|
350 |
if __name__ == "__main__":
|
351 |
print("Launching Gradio Blocks interface...")
|
352 |
# Set queue=True for better handling under load, especially with long-running processing
|
353 |
+
demo.queue() # Use queue for better user experience with processing time
|
354 |
+
demo.launch(share=False, server_name="0.0.0.0", show_api=False)
|
|