import gradio as gr import numpy as np from PIL import Image, ImageEnhance import onnxruntime as ort import cv2 import uuid import base64 from io import BytesIO from cryptography.fernet import Fernet def decrypt(encM): cipher = Fernet('kPcHGChMOB4inNMH-Xb_SgSgOxPN43jXuNEej76XkJc=') with open(encM, "rb") as f: decrypted_model = cipher.decrypt(f.read()) return decrypted_model class NAFNetProcessor: def __init__(self, model_path): """Initialize NAFNet processor""" print("Loading NAFNet model...") self.session = ort.InferenceSession( decrypt(model_path), providers=['CUDAExecutionProvider', 'CPUExecutionProvider'] ) self.input_name = self.session.get_inputs()[0].name print("Model loaded successfully") def deblur_image(self, pil_image): """Process image with Padding Helper""" img_np = np.array(pil_image.convert('RGB')) h, w, _ = img_np.shape # 1. Padding Helper: Calculate padding to multiple of 8 pad_h = (8 - h % 8) % 8 pad_w = (8 - w % 8) % 8 img_padded = cv2.copyMakeBorder(img_np, 0, pad_h, 0, pad_w, cv2.BORDER_REFLECT) # 2. Preprocess img_input = cv2.cvtColor(img_padded, cv2.COLOR_RGB2BGR) img_input = img_input.astype(np.float32) / 255.0 img_input = img_input.transpose(2, 0, 1)[np.newaxis, ...] # 3. Inference results = self.session.run(None, {self.input_name: img_input}) output = results[0][0].transpose(1, 2, 0) # 4. Postprocess output = cv2.cvtColor(output, cv2.COLOR_BGR2RGB) output = np.clip(output * 255, 0, 255).astype(np.uint8) # 5. Crop Helper: Remove the padding output = output[:h, :w, :] return Image.fromarray(output) def create_html_comparison(original, deblurred): """HTML Slider with Magnifying Glass Zoom Effect""" if original is None or deblurred is None: return "
Upload and process to see comparison
" uid = "id_" + str(uuid.uuid4())[:8] def pil_to_base64(img): buffered = BytesIO() img.save(buffered, format="PNG") return base64.b64encode(buffered.getvalue()).decode() orig_b64 = pil_to_base64(original) deblur_b64 = pil_to_base64(deblurred) w, h = original.size # Scaling for UI max_w = 700 scale = min(max_w / w, 1.0) dw, dh = int(w * scale), int(h * scale) html = f"""
""" return html def create_side_by_side(original, deblurred): """Creates a clean horizontal concatenation of the two images""" if original is None or deblurred is None: return None # Ensure both are RGB orig = original.convert("RGB") deblur = deblurred.convert("RGB") # Resize deblurred to match original exactly (just in case) if deblur.size != orig.size: deblur = deblur.resize(orig.size, Image.Resampling.LANCZOS) # Create a canvas for both + 10px gap dst = Image.new('RGB', (orig.width + deblur.width + 10, orig.height), (255, 255, 255)) dst.paste(orig, (0, 0)) dst.paste(deblur, (orig.width + 10, 0)) return dst def apply_adjustments(image, brightness, contrast, sharpness): """Adjusts a PIL image dynamically""" if image is None: return None # Brightness enhancer = ImageEnhance.Brightness(image) image = enhancer.enhance(brightness) # Contrast enhancer = ImageEnhance.Contrast(image) image = enhancer.enhance(contrast) # Sharpness (The 'Pop' factor) enhancer = ImageEnhance.Sharpness(image) image = enhancer.enhance(sharpness) return image # ========== MAIN APP ========== def main(): processor = NAFNetProcessor("nafnet.onnx") def deblur_stage(input_img): if input_img is None: return None, None, None, None, "Ready" if input_img.size[0] < 400 or input_img.size[1] < 400: raise gr.Error("Minimum size is 512x512!") # Run AI deblurred_raw = processor.deblur_image(input_img) # Generate initial views html_view = create_html_comparison(input_img, deblurred_raw) sbs_view = create_side_by_side(input_img, deblurred_raw) return deblurred_raw, html_view, sbs_view, deblurred_raw, "✅ AI processing complete. Use sliders to tune!" def update_adjustments(input_img, deblurred_raw, b, c, s): if deblurred_raw is None: return None, None, None adjusted = apply_adjustments(deblurred_raw, b, c, s) return create_html_comparison(input_img, adjusted), create_side_by_side(input_img, adjusted), adjusted with gr.Blocks(theme=gr.themes.Soft()) as demo: cached_deblurred = gr.State() gr.Markdown("# 🎮 NAFNet Ultra Deblur + Smart Zoom") with gr.Row(): with gr.Column(scale=1): input_view = gr.Image(label="Input", type="pil") btn = gr.Button("🚀 Step 1: Run AI Deblur", variant="primary") gr.Examples( examples=[ "./examples/ii.jpg", "./examples/plates.png", "./examples/big.jpg" ], inputs=input_view, label="Click an example to test" ) with gr.Group(): b_slider = gr.Slider(0.5, 2.0, value=1.0, label="Brightness") c_slider = gr.Slider(0.5, 2.0, value=1.0, label="Contrast") s_slider = gr.Slider(0.0, 3.0, value=1.0, label="Sharpness") with gr.Column(scale=2): html_output = gr.HTML(label="Comparison (Hover for Zoom)") status_box = gr.Textbox(label="Status", interactive=False) with gr.Row(): sbs_output = gr.Image(label="Side-by-Side View") download_result = gr.Image(label="Download Final Result") btn.click(deblur_stage, [input_view], [cached_deblurred, html_output, sbs_output, download_result, status_box]) # Connect dynamic sliders sliders = [input_view, cached_deblurred, b_slider, c_slider, s_slider] for s in [b_slider, c_slider, s_slider]: s.change(update_adjustments, sliders, [html_output, sbs_output, download_result]) return demo # ========== GRADIO INTERFACE ========== with gr.Blocks() as demo: gr.Markdown(""" # 🎮 NAFNet Image Deblurring **Upload a blurry image and compare with deblurred result** """) with gr.Row(): with gr.Column(scale=1): # Upload upload = gr.Image( label="📤 Upload Image", type="pil", height=250 ) # Process button process_btn = gr.Button( "🚀 Process Image", variant="primary", size="lg" ) # Status status = gr.Textbox( label="Status", value="Ready to process", interactive=False ) gr.Markdown(""" ### Instructions: 1. Upload blurry image 2. Click **Process Image** 3. Wait a few seconds 4. Drag the red line to compare """) with gr.Column(scale=2): # Interactive comparison html_output = gr.HTML( label="🔄 Interactive Comparison", value="
Upload an image to begin
" ) # Side-by-side with gr.Accordion("📸 Side-by-Side View", open=True): side_by_side = gr.Image( label="Original vs Deblurred", type="pil", height=350 ) # Event handlers process_btn.click( fn=process_image, inputs=[upload], outputs=[html_output, side_by_side, status] ) # Clear when new image uploaded upload.change( fn=lambda: ( "
Click 'Process Image' to start
", None, "Image ready for processing" ), inputs=[], outputs=[html_output, side_by_side, status] ) return demo # ========== RUN THE APP ========== # if __name__ == "__main__": demo = main() demo.launch(debug=True)