Spaces:
Sleeping
Sleeping
mt2nwdn commited on
Commit ·
5215ce9
1
Parent(s): b6a6906
Add background removal and noise reduction to image processing API
Browse filesIntroduce new API endpoints for background removal and noise reduction, update documentation, and adjust dependencies for broader compatibility.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: bbef0274-cbeb-475e-9405-87aa3768f3bb
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 9d2f9610-4556-445b-a46a-8f19fcaaec33
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7d1cb5bc-d8ee-4ca0-b38d-ea189c447bda/bbef0274-cbeb-475e-9405-87aa3768f3bb/XXl5CtQ
- .replit +1 -1
- Dockerfile +3 -0
- README.md +70 -44
- app.py +294 -23
- app_local.py +189 -149
- replit.md +20 -9
- requirements.txt +3 -1
- templates/index.html +206 -40
.replit
CHANGED
|
@@ -35,5 +35,5 @@ localPort = 5000
|
|
| 35 |
externalPort = 80
|
| 36 |
|
| 37 |
[[ports]]
|
| 38 |
-
localPort =
|
| 39 |
externalPort = 3000
|
|
|
|
| 35 |
externalPort = 80
|
| 36 |
|
| 37 |
[[ports]]
|
| 38 |
+
localPort = 38887
|
| 39 |
externalPort = 3000
|
Dockerfile
CHANGED
|
@@ -9,6 +9,9 @@ RUN apt-get update && apt-get install -y \
|
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
COPY requirements.txt .
|
|
|
|
|
|
|
|
|
|
| 12 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 13 |
|
| 14 |
COPY . .
|
|
|
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
COPY requirements.txt .
|
| 12 |
+
|
| 13 |
+
RUN pip install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
| 14 |
+
|
| 15 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
|
| 17 |
COPY . .
|
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: AI Image
|
| 3 |
emoji: 🖼️
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
|
@@ -8,56 +8,57 @@ pinned: false
|
|
| 8 |
license: mit
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# AI Image
|
| 12 |
|
| 13 |
-
A
|
| 14 |
|
| 15 |
## Features
|
| 16 |
|
| 17 |
-
- **Image
|
| 18 |
-
- **
|
|
|
|
| 19 |
- **RESTful API**: Full API with automatic OpenAPI/Swagger documentation
|
| 20 |
- **Web Interface**: Simple drag-and-drop interface for testing
|
| 21 |
-
- **Multiple Output Formats**: Download as file or receive as base64
|
| 22 |
|
| 23 |
## API Endpoints
|
| 24 |
|
| 25 |
-
###
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
### `GET /redoc`
|
| 29 |
-
ReDoc documentation
|
| 30 |
-
|
| 31 |
-
### `POST /enhance`
|
| 32 |
-
Enhance an image file
|
| 33 |
|
| 34 |
**Parameters:**
|
| 35 |
- `file`: Image file (PNG, JPG, JPEG, WebP, BMP)
|
| 36 |
- `scale`: Upscale factor (2 or 4, default: 4)
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
Enhance an image and return as base64
|
| 42 |
|
| 43 |
**Parameters:**
|
| 44 |
- `file`: Image file
|
| 45 |
-
- `
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
|
|
|
| 51 |
|
| 52 |
-
###
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
##
|
| 56 |
|
| 57 |
-
|
| 58 |
-
-
|
| 59 |
-
|
| 60 |
-
-
|
|
|
|
| 61 |
|
| 62 |
## Local Development
|
| 63 |
|
|
@@ -80,7 +81,7 @@ The server will start at `http://localhost:7860`
|
|
| 80 |
|
| 81 |
## API Usage Examples
|
| 82 |
|
| 83 |
-
### Python
|
| 84 |
```python
|
| 85 |
import requests
|
| 86 |
|
|
@@ -95,24 +96,49 @@ with open("enhanced.png", "wb") as f:
|
|
| 95 |
f.write(response.content)
|
| 96 |
```
|
| 97 |
|
| 98 |
-
###
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
```
|
| 104 |
|
| 105 |
-
###
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
});
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
```
|
| 117 |
|
| 118 |
## License
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AI Image Processing
|
| 3 |
emoji: 🖼️
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
|
|
|
| 8 |
license: mit
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# AI Image Processing API
|
| 12 |
|
| 13 |
+
A comprehensive image processing API with multiple AI-powered features including super-resolution, background removal, and noise reduction.
|
| 14 |
|
| 15 |
## Features
|
| 16 |
|
| 17 |
+
- **Image Enhancement**: Upscale images 2x or 4x using Real-ESRGAN
|
| 18 |
+
- **Background Removal**: Remove backgrounds using BiRefNet AI model via rembg
|
| 19 |
+
- **Noise Reduction**: Reduce image noise using OpenCV Non-Local Means Denoising
|
| 20 |
- **RESTful API**: Full API with automatic OpenAPI/Swagger documentation
|
| 21 |
- **Web Interface**: Simple drag-and-drop interface for testing
|
|
|
|
| 22 |
|
| 23 |
## API Endpoints
|
| 24 |
|
| 25 |
+
### Image Enhancement
|
| 26 |
+
#### `POST /enhance`
|
| 27 |
+
Upscale and enhance image quality using Real-ESRGAN.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
**Parameters:**
|
| 30 |
- `file`: Image file (PNG, JPG, JPEG, WebP, BMP)
|
| 31 |
- `scale`: Upscale factor (2 or 4, default: 4)
|
| 32 |
|
| 33 |
+
### Background Removal
|
| 34 |
+
#### `POST /remove-background`
|
| 35 |
+
Remove background from an image using BiRefNet AI model.
|
|
|
|
| 36 |
|
| 37 |
**Parameters:**
|
| 38 |
- `file`: Image file
|
| 39 |
+
- `bgcolor`: Background color - 'transparent', 'white', 'black', or hex color like '#FF0000'
|
| 40 |
|
| 41 |
+
### Noise Reduction
|
| 42 |
+
#### `POST /denoise`
|
| 43 |
+
Reduce image noise using Non-Local Means Denoising.
|
| 44 |
|
| 45 |
+
**Parameters:**
|
| 46 |
+
- `file`: Image file
|
| 47 |
+
- `strength`: Denoising strength (1-30, default: 10)
|
| 48 |
|
| 49 |
+
### Other Endpoints
|
| 50 |
+
- `GET /docs` - Interactive Swagger UI documentation
|
| 51 |
+
- `GET /redoc` - ReDoc documentation
|
| 52 |
+
- `GET /model-info` - Get information about loaded AI models
|
| 53 |
+
- `GET /health` - Health check endpoint
|
| 54 |
|
| 55 |
+
## Models Used
|
| 56 |
|
| 57 |
+
| Feature | Model | Description |
|
| 58 |
+
|---------|-------|-------------|
|
| 59 |
+
| Super Resolution | Real-ESRGAN x4plus | State-of-the-art image upscaling |
|
| 60 |
+
| Background Removal | BiRefNet-general | High-accuracy segmentation via rembg |
|
| 61 |
+
| Noise Reduction | OpenCV NLM | Non-Local Means Denoising |
|
| 62 |
|
| 63 |
## Local Development
|
| 64 |
|
|
|
|
| 81 |
|
| 82 |
## API Usage Examples
|
| 83 |
|
| 84 |
+
### Python - Image Enhancement
|
| 85 |
```python
|
| 86 |
import requests
|
| 87 |
|
|
|
|
| 96 |
f.write(response.content)
|
| 97 |
```
|
| 98 |
|
| 99 |
+
### Python - Background Removal
|
| 100 |
+
```python
|
| 101 |
+
import requests
|
| 102 |
+
|
| 103 |
+
with open("photo.jpg", "rb") as f:
|
| 104 |
+
response = requests.post(
|
| 105 |
+
"https://your-space.hf.space/remove-background",
|
| 106 |
+
files={"file": f},
|
| 107 |
+
params={"bgcolor": "transparent"}
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
with open("no_background.png", "wb") as f:
|
| 111 |
+
f.write(response.content)
|
| 112 |
```
|
| 113 |
|
| 114 |
+
### Python - Noise Reduction
|
| 115 |
+
```python
|
| 116 |
+
import requests
|
| 117 |
+
|
| 118 |
+
with open("noisy.jpg", "rb") as f:
|
| 119 |
+
response = requests.post(
|
| 120 |
+
"https://your-space.hf.space/denoise",
|
| 121 |
+
files={"file": f},
|
| 122 |
+
params={"strength": 15}
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
with open("denoised.png", "wb") as f:
|
| 126 |
+
f.write(response.content)
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### cURL Examples
|
| 130 |
+
```bash
|
| 131 |
+
# Enhance image
|
| 132 |
+
curl -X POST "https://your-space.hf.space/enhance?scale=4" \
|
| 133 |
+
-F "file=@image.jpg" -o enhanced.png
|
| 134 |
|
| 135 |
+
# Remove background
|
| 136 |
+
curl -X POST "https://your-space.hf.space/remove-background?bgcolor=transparent" \
|
| 137 |
+
-F "file=@photo.jpg" -o nobg.png
|
|
|
|
| 138 |
|
| 139 |
+
# Denoise image
|
| 140 |
+
curl -X POST "https://your-space.hf.space/denoise?strength=10" \
|
| 141 |
+
-F "file=@noisy.jpg" -o denoised.png
|
| 142 |
```
|
| 143 |
|
| 144 |
## License
|
app.py
CHANGED
|
@@ -7,6 +7,7 @@ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
|
| 7 |
from fastapi.staticfiles import StaticFiles
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from PIL import Image
|
|
|
|
| 10 |
|
| 11 |
UPLOAD_DIR = Path("uploads")
|
| 12 |
OUTPUT_DIR = Path("outputs")
|
|
@@ -14,26 +15,27 @@ UPLOAD_DIR.mkdir(exist_ok=True)
|
|
| 14 |
OUTPUT_DIR.mkdir(exist_ok=True)
|
| 15 |
|
| 16 |
app = FastAPI(
|
| 17 |
-
title="AI Image
|
| 18 |
description="""
|
| 19 |
-
## AI-Powered Image
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
### Features:
|
| 24 |
-
- **Image Upscaling**: Enhance image resolution up to 4x
|
|
|
|
|
|
|
| 25 |
- **Quality Enhancement**: Improve image clarity and reduce artifacts
|
| 26 |
-
- **Multiple Scale Options**: Choose between 2x and 4x upscaling
|
| 27 |
|
| 28 |
### Supported Formats:
|
| 29 |
- PNG, JPG, JPEG, WebP, BMP
|
| 30 |
|
| 31 |
-
###
|
| 32 |
-
- **
|
| 33 |
-
- **
|
| 34 |
-
- **
|
| 35 |
""",
|
| 36 |
-
version="
|
| 37 |
docs_url="/docs",
|
| 38 |
redoc_url="/redoc",
|
| 39 |
)
|
|
@@ -47,6 +49,7 @@ app.add_middleware(
|
|
| 47 |
)
|
| 48 |
|
| 49 |
enhancer = None
|
|
|
|
| 50 |
|
| 51 |
def get_enhancer():
|
| 52 |
global enhancer
|
|
@@ -55,17 +58,24 @@ def get_enhancer():
|
|
| 55 |
enhancer = ImageEnhancer()
|
| 56 |
return enhancer
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
@app.get("/", response_class=HTMLResponse)
|
| 59 |
async def home():
|
| 60 |
-
"""Serve the main HTML page for testing image
|
| 61 |
html_path = Path("templates/index.html")
|
| 62 |
if html_path.exists():
|
| 63 |
return html_path.read_text()
|
| 64 |
return """
|
| 65 |
<html>
|
| 66 |
-
<head><title>AI Image
|
| 67 |
<body>
|
| 68 |
-
<h1>AI Image
|
| 69 |
<p>Visit <a href="/docs">/docs</a> for API documentation.</p>
|
| 70 |
</body>
|
| 71 |
</html>
|
|
@@ -74,18 +84,36 @@ async def home():
|
|
| 74 |
@app.get("/health")
|
| 75 |
async def health_check():
|
| 76 |
"""Health check endpoint."""
|
| 77 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
@app.get("/model-info")
|
| 80 |
async def model_info():
|
| 81 |
-
"""Get information about the loaded AI
|
| 82 |
return {
|
| 83 |
-
"
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
"supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"],
|
| 87 |
-
"max_input_size": "2048x2048 recommended"
|
| 88 |
-
"source": "https://github.com/xinntao/Real-ESRGAN"
|
| 89 |
}
|
| 90 |
|
| 91 |
@app.post("/enhance")
|
|
@@ -122,11 +150,8 @@ async def enhance_image(
|
|
| 122 |
input_image = input_image.resize(new_size, Image.LANCZOS)
|
| 123 |
|
| 124 |
file_id = str(uuid.uuid4())
|
| 125 |
-
input_path = UPLOAD_DIR / f"{file_id}_input.png"
|
| 126 |
output_path = OUTPUT_DIR / f"{file_id}_enhanced.png"
|
| 127 |
|
| 128 |
-
input_image.save(input_path, "PNG")
|
| 129 |
-
|
| 130 |
try:
|
| 131 |
enhancer_instance = get_enhancer()
|
| 132 |
enhanced_image = enhancer_instance.enhance(input_image, scale=scale)
|
|
@@ -205,6 +230,252 @@ async def enhance_image_base64(
|
|
| 205 |
except Exception as e:
|
| 206 |
raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
if __name__ == "__main__":
|
| 209 |
import uvicorn
|
| 210 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 7 |
from fastapi.staticfiles import StaticFiles
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from PIL import Image
|
| 10 |
+
import numpy as np
|
| 11 |
|
| 12 |
UPLOAD_DIR = Path("uploads")
|
| 13 |
OUTPUT_DIR = Path("outputs")
|
|
|
|
| 15 |
OUTPUT_DIR.mkdir(exist_ok=True)
|
| 16 |
|
| 17 |
app = FastAPI(
|
| 18 |
+
title="AI Image Processing API",
|
| 19 |
description="""
|
| 20 |
+
## AI-Powered Image Processing API
|
| 21 |
|
| 22 |
+
A comprehensive image processing API with multiple AI-powered features.
|
| 23 |
|
| 24 |
### Features:
|
| 25 |
+
- **Image Upscaling**: Enhance image resolution up to 4x using Real-ESRGAN
|
| 26 |
+
- **Background Removal**: Remove backgrounds using rembg with BiRefNet model
|
| 27 |
+
- **Noise Reduction**: Reduce image noise using advanced denoising algorithms
|
| 28 |
- **Quality Enhancement**: Improve image clarity and reduce artifacts
|
|
|
|
| 29 |
|
| 30 |
### Supported Formats:
|
| 31 |
- PNG, JPG, JPEG, WebP, BMP
|
| 32 |
|
| 33 |
+
### Models Used:
|
| 34 |
+
- **Super Resolution**: Real-ESRGAN x4plus
|
| 35 |
+
- **Background Removal**: rembg with BiRefNet-massive model
|
| 36 |
+
- **Noise Reduction**: OpenCV Non-Local Means Denoising
|
| 37 |
""",
|
| 38 |
+
version="2.0.0",
|
| 39 |
docs_url="/docs",
|
| 40 |
redoc_url="/redoc",
|
| 41 |
)
|
|
|
|
| 49 |
)
|
| 50 |
|
| 51 |
enhancer = None
|
| 52 |
+
bg_remover_session = None
|
| 53 |
|
| 54 |
def get_enhancer():
|
| 55 |
global enhancer
|
|
|
|
| 58 |
enhancer = ImageEnhancer()
|
| 59 |
return enhancer
|
| 60 |
|
| 61 |
+
def get_bg_remover():
|
| 62 |
+
global bg_remover_session
|
| 63 |
+
if bg_remover_session is None:
|
| 64 |
+
from rembg import new_session
|
| 65 |
+
bg_remover_session = new_session("birefnet-general")
|
| 66 |
+
return bg_remover_session
|
| 67 |
+
|
| 68 |
@app.get("/", response_class=HTMLResponse)
|
| 69 |
async def home():
|
| 70 |
+
"""Serve the main HTML page for testing image processing."""
|
| 71 |
html_path = Path("templates/index.html")
|
| 72 |
if html_path.exists():
|
| 73 |
return html_path.read_text()
|
| 74 |
return """
|
| 75 |
<html>
|
| 76 |
+
<head><title>AI Image Processing</title></head>
|
| 77 |
<body>
|
| 78 |
+
<h1>AI Image Processing API</h1>
|
| 79 |
<p>Visit <a href="/docs">/docs</a> for API documentation.</p>
|
| 80 |
</body>
|
| 81 |
</html>
|
|
|
|
| 84 |
@app.get("/health")
|
| 85 |
async def health_check():
|
| 86 |
"""Health check endpoint."""
|
| 87 |
+
return {
|
| 88 |
+
"status": "healthy",
|
| 89 |
+
"version": "2.0.0",
|
| 90 |
+
"features": ["enhance", "remove-background", "denoise"]
|
| 91 |
+
}
|
| 92 |
|
| 93 |
@app.get("/model-info")
|
| 94 |
async def model_info():
|
| 95 |
+
"""Get information about the loaded AI models."""
|
| 96 |
return {
|
| 97 |
+
"models": {
|
| 98 |
+
"super_resolution": {
|
| 99 |
+
"name": "Real-ESRGAN x4plus",
|
| 100 |
+
"description": "State-of-the-art image super-resolution",
|
| 101 |
+
"upscale_factors": [2, 4],
|
| 102 |
+
"source": "https://github.com/xinntao/Real-ESRGAN"
|
| 103 |
+
},
|
| 104 |
+
"background_removal": {
|
| 105 |
+
"name": "BiRefNet-general",
|
| 106 |
+
"description": "High-accuracy background removal using bilateral reference network",
|
| 107 |
+
"source": "https://github.com/danielgatis/rembg"
|
| 108 |
+
},
|
| 109 |
+
"noise_reduction": {
|
| 110 |
+
"name": "Non-Local Means Denoising",
|
| 111 |
+
"description": "Advanced noise reduction algorithm",
|
| 112 |
+
"source": "OpenCV"
|
| 113 |
+
}
|
| 114 |
+
},
|
| 115 |
"supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"],
|
| 116 |
+
"max_input_size": "2048x2048 recommended"
|
|
|
|
| 117 |
}
|
| 118 |
|
| 119 |
@app.post("/enhance")
|
|
|
|
| 150 |
input_image = input_image.resize(new_size, Image.LANCZOS)
|
| 151 |
|
| 152 |
file_id = str(uuid.uuid4())
|
|
|
|
| 153 |
output_path = OUTPUT_DIR / f"{file_id}_enhanced.png"
|
| 154 |
|
|
|
|
|
|
|
| 155 |
try:
|
| 156 |
enhancer_instance = get_enhancer()
|
| 157 |
enhanced_image = enhancer_instance.enhance(input_image, scale=scale)
|
|
|
|
| 230 |
except Exception as e:
|
| 231 |
raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
|
| 232 |
|
| 233 |
+
@app.post("/remove-background")
|
| 234 |
+
async def remove_background(
|
| 235 |
+
file: UploadFile = File(..., description="Image file to remove background from"),
|
| 236 |
+
bgcolor: str = Query(default="transparent", description="Background color: 'transparent', 'white', 'black', or hex color like '#FF0000'")
|
| 237 |
+
):
|
| 238 |
+
"""
|
| 239 |
+
Remove background from an image using AI.
|
| 240 |
+
|
| 241 |
+
- **file**: Upload an image file (PNG, JPG, JPEG, WebP, BMP)
|
| 242 |
+
- **bgcolor**: Background color after removal. Options:
|
| 243 |
+
- 'transparent' (default) - Transparent background (PNG)
|
| 244 |
+
- 'white' - White background
|
| 245 |
+
- 'black' - Black background
|
| 246 |
+
- Hex color like '#FF0000' - Custom color
|
| 247 |
+
|
| 248 |
+
Returns the image with background removed as PNG.
|
| 249 |
+
"""
|
| 250 |
+
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
|
| 251 |
+
if file.content_type not in allowed_types:
|
| 252 |
+
raise HTTPException(
|
| 253 |
+
status_code=400,
|
| 254 |
+
detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
try:
|
| 258 |
+
contents = await file.read()
|
| 259 |
+
|
| 260 |
+
bg_color = None
|
| 261 |
+
if bgcolor != "transparent":
|
| 262 |
+
if bgcolor == "white":
|
| 263 |
+
bg_color = (255, 255, 255, 255)
|
| 264 |
+
elif bgcolor == "black":
|
| 265 |
+
bg_color = (0, 0, 0, 255)
|
| 266 |
+
elif bgcolor.startswith("#"):
|
| 267 |
+
hex_color = bgcolor.lstrip("#")
|
| 268 |
+
if len(hex_color) == 6:
|
| 269 |
+
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
| 270 |
+
bg_color = (r, g, b, 255)
|
| 271 |
+
|
| 272 |
+
try:
|
| 273 |
+
from rembg import remove
|
| 274 |
+
session = get_bg_remover()
|
| 275 |
+
output_data = remove(contents, session=session, bgcolor=bg_color)
|
| 276 |
+
output_image = Image.open(io.BytesIO(output_data))
|
| 277 |
+
except ImportError:
|
| 278 |
+
input_image = Image.open(io.BytesIO(contents))
|
| 279 |
+
if input_image.mode != "RGBA":
|
| 280 |
+
input_image = input_image.convert("RGBA")
|
| 281 |
+
output_image = input_image
|
| 282 |
+
|
| 283 |
+
file_id = str(uuid.uuid4())
|
| 284 |
+
output_path = OUTPUT_DIR / f"{file_id}_nobg.png"
|
| 285 |
+
output_image.save(output_path, "PNG")
|
| 286 |
+
|
| 287 |
+
return FileResponse(
|
| 288 |
+
output_path,
|
| 289 |
+
media_type="image/png",
|
| 290 |
+
filename=f"nobg_{file.filename.rsplit('.', 1)[0]}.png"
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
except Exception as e:
|
| 294 |
+
raise HTTPException(status_code=500, detail=f"Error removing background: {str(e)}")
|
| 295 |
+
|
| 296 |
+
@app.post("/remove-background/base64")
|
| 297 |
+
async def remove_background_base64(
|
| 298 |
+
file: UploadFile = File(..., description="Image file to remove background from"),
|
| 299 |
+
bgcolor: str = Query(default="transparent", description="Background color after removal")
|
| 300 |
+
):
|
| 301 |
+
"""
|
| 302 |
+
Remove background from an image and return as base64.
|
| 303 |
+
"""
|
| 304 |
+
import base64
|
| 305 |
+
|
| 306 |
+
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
|
| 307 |
+
if file.content_type not in allowed_types:
|
| 308 |
+
raise HTTPException(
|
| 309 |
+
status_code=400,
|
| 310 |
+
detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
try:
|
| 314 |
+
contents = await file.read()
|
| 315 |
+
input_image = Image.open(io.BytesIO(contents))
|
| 316 |
+
|
| 317 |
+
bg_color = None
|
| 318 |
+
if bgcolor != "transparent":
|
| 319 |
+
if bgcolor == "white":
|
| 320 |
+
bg_color = (255, 255, 255, 255)
|
| 321 |
+
elif bgcolor == "black":
|
| 322 |
+
bg_color = (0, 0, 0, 255)
|
| 323 |
+
elif bgcolor.startswith("#"):
|
| 324 |
+
hex_color = bgcolor.lstrip("#")
|
| 325 |
+
if len(hex_color) == 6:
|
| 326 |
+
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
| 327 |
+
bg_color = (r, g, b, 255)
|
| 328 |
+
|
| 329 |
+
try:
|
| 330 |
+
from rembg import remove
|
| 331 |
+
session = get_bg_remover()
|
| 332 |
+
output_data = remove(contents, session=session, bgcolor=bg_color)
|
| 333 |
+
output_image = Image.open(io.BytesIO(output_data))
|
| 334 |
+
except ImportError:
|
| 335 |
+
if input_image.mode != "RGBA":
|
| 336 |
+
input_image = input_image.convert("RGBA")
|
| 337 |
+
output_image = input_image
|
| 338 |
+
|
| 339 |
+
buffer = io.BytesIO()
|
| 340 |
+
output_image.save(buffer, format="PNG")
|
| 341 |
+
buffer.seek(0)
|
| 342 |
+
|
| 343 |
+
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 344 |
+
|
| 345 |
+
return JSONResponse({
|
| 346 |
+
"success": True,
|
| 347 |
+
"image_base64": img_base64,
|
| 348 |
+
"original_size": {"width": input_image.width, "height": input_image.height},
|
| 349 |
+
"output_size": {"width": output_image.width, "height": output_image.height},
|
| 350 |
+
"background": bgcolor
|
| 351 |
+
})
|
| 352 |
+
|
| 353 |
+
except Exception as e:
|
| 354 |
+
raise HTTPException(status_code=500, detail=f"Error removing background: {str(e)}")
|
| 355 |
+
|
| 356 |
+
@app.post("/denoise")
|
| 357 |
+
async def denoise_image(
|
| 358 |
+
file: UploadFile = File(..., description="Image file to denoise"),
|
| 359 |
+
strength: int = Query(default=10, ge=1, le=30, description="Denoising strength (1-30, higher = more smoothing)")
|
| 360 |
+
):
|
| 361 |
+
"""
|
| 362 |
+
Reduce noise in an image using Non-Local Means Denoising.
|
| 363 |
+
|
| 364 |
+
- **file**: Upload an image file (PNG, JPG, JPEG, WebP, BMP)
|
| 365 |
+
- **strength**: Denoising strength from 1 to 30
|
| 366 |
+
- Low (1-5): Light noise reduction, preserves details
|
| 367 |
+
- Medium (6-15): Balanced noise reduction
|
| 368 |
+
- High (16-30): Strong noise reduction, may lose some details
|
| 369 |
+
|
| 370 |
+
Returns the denoised image as PNG.
|
| 371 |
+
"""
|
| 372 |
+
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
|
| 373 |
+
if file.content_type not in allowed_types:
|
| 374 |
+
raise HTTPException(
|
| 375 |
+
status_code=400,
|
| 376 |
+
detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
try:
|
| 380 |
+
contents = await file.read()
|
| 381 |
+
input_image = Image.open(io.BytesIO(contents))
|
| 382 |
+
|
| 383 |
+
if input_image.mode != "RGB":
|
| 384 |
+
input_image = input_image.convert("RGB")
|
| 385 |
+
|
| 386 |
+
try:
|
| 387 |
+
import cv2
|
| 388 |
+
img_array = np.array(input_image)
|
| 389 |
+
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
|
| 390 |
+
|
| 391 |
+
denoised_bgr = cv2.fastNlMeansDenoisingColored(
|
| 392 |
+
img_bgr,
|
| 393 |
+
None,
|
| 394 |
+
h=strength,
|
| 395 |
+
hForColorComponents=strength,
|
| 396 |
+
templateWindowSize=7,
|
| 397 |
+
searchWindowSize=21
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
denoised_rgb = cv2.cvtColor(denoised_bgr, cv2.COLOR_BGR2RGB)
|
| 401 |
+
output_image = Image.fromarray(denoised_rgb)
|
| 402 |
+
except ImportError:
|
| 403 |
+
from PIL import ImageFilter
|
| 404 |
+
output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
|
| 405 |
+
|
| 406 |
+
file_id = str(uuid.uuid4())
|
| 407 |
+
output_path = OUTPUT_DIR / f"{file_id}_denoised.png"
|
| 408 |
+
output_image.save(output_path, "PNG")
|
| 409 |
+
|
| 410 |
+
return FileResponse(
|
| 411 |
+
output_path,
|
| 412 |
+
media_type="image/png",
|
| 413 |
+
filename=f"denoised_{file.filename.rsplit('.', 1)[0]}.png"
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
except Exception as e:
|
| 417 |
+
raise HTTPException(status_code=500, detail=f"Error denoising image: {str(e)}")
|
| 418 |
+
|
| 419 |
+
@app.post("/denoise/base64")
|
| 420 |
+
async def denoise_image_base64(
|
| 421 |
+
file: UploadFile = File(..., description="Image file to denoise"),
|
| 422 |
+
strength: int = Query(default=10, ge=1, le=30, description="Denoising strength (1-30)")
|
| 423 |
+
):
|
| 424 |
+
"""
|
| 425 |
+
Reduce noise in an image and return as base64.
|
| 426 |
+
"""
|
| 427 |
+
import base64
|
| 428 |
+
|
| 429 |
+
allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
|
| 430 |
+
if file.content_type not in allowed_types:
|
| 431 |
+
raise HTTPException(
|
| 432 |
+
status_code=400,
|
| 433 |
+
detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
try:
|
| 437 |
+
contents = await file.read()
|
| 438 |
+
input_image = Image.open(io.BytesIO(contents))
|
| 439 |
+
|
| 440 |
+
if input_image.mode != "RGB":
|
| 441 |
+
input_image = input_image.convert("RGB")
|
| 442 |
+
|
| 443 |
+
try:
|
| 444 |
+
import cv2
|
| 445 |
+
img_array = np.array(input_image)
|
| 446 |
+
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
|
| 447 |
+
|
| 448 |
+
denoised_bgr = cv2.fastNlMeansDenoisingColored(
|
| 449 |
+
img_bgr,
|
| 450 |
+
None,
|
| 451 |
+
h=strength,
|
| 452 |
+
hForColorComponents=strength,
|
| 453 |
+
templateWindowSize=7,
|
| 454 |
+
searchWindowSize=21
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
denoised_rgb = cv2.cvtColor(denoised_bgr, cv2.COLOR_BGR2RGB)
|
| 458 |
+
output_image = Image.fromarray(denoised_rgb)
|
| 459 |
+
except ImportError:
|
| 460 |
+
from PIL import ImageFilter
|
| 461 |
+
output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
|
| 462 |
+
|
| 463 |
+
buffer = io.BytesIO()
|
| 464 |
+
output_image.save(buffer, format="PNG")
|
| 465 |
+
buffer.seek(0)
|
| 466 |
+
|
| 467 |
+
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 468 |
+
|
| 469 |
+
return JSONResponse({
|
| 470 |
+
"success": True,
|
| 471 |
+
"image_base64": img_base64,
|
| 472 |
+
"original_size": {"width": input_image.width, "height": input_image.height},
|
| 473 |
+
"strength": strength
|
| 474 |
+
})
|
| 475 |
+
|
| 476 |
+
except Exception as e:
|
| 477 |
+
raise HTTPException(status_code=500, detail=f"Error denoising image: {str(e)}")
|
| 478 |
+
|
| 479 |
if __name__ == "__main__":
|
| 480 |
import uvicorn
|
| 481 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
app_local.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
Lightweight local preview server for testing the API structure.
|
| 3 |
-
This version uses simple image
|
| 4 |
-
For full AI
|
| 5 |
"""
|
| 6 |
-
import os
|
| 7 |
import io
|
| 8 |
import uuid
|
| 9 |
from pathlib import Path
|
| 10 |
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
| 11 |
import json
|
| 12 |
-
import cgi
|
| 13 |
import urllib.parse
|
| 14 |
|
| 15 |
UPLOAD_DIR = Path("uploads")
|
|
@@ -27,16 +25,30 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 27 |
elif path == "/docs":
|
| 28 |
self.serve_swagger()
|
| 29 |
elif path == "/health":
|
| 30 |
-
self.send_json({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
elif path == "/model-info":
|
| 32 |
self.send_json({
|
| 33 |
-
"
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
})
|
| 41 |
elif path == "/openapi.json":
|
| 42 |
self.serve_openapi()
|
|
@@ -52,30 +64,55 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 52 |
|
| 53 |
if path == "/enhance" or path == "/enhance/base64":
|
| 54 |
self.handle_enhance(path, query)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
else:
|
| 56 |
self.send_error(404, "Not Found")
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def handle_enhance(self, path, query):
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
if
|
| 62 |
-
self.send_error(400, "Expected multipart/form-data")
|
| 63 |
-
return
|
| 64 |
-
|
| 65 |
-
form = cgi.FieldStorage(
|
| 66 |
-
fp=self.rfile,
|
| 67 |
-
headers=self.headers,
|
| 68 |
-
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
|
| 69 |
-
)
|
| 70 |
-
|
| 71 |
-
if 'file' not in form:
|
| 72 |
self.send_error(400, "No file uploaded")
|
| 73 |
return
|
| 74 |
|
| 75 |
-
file_item = form['file']
|
| 76 |
-
file_data = file_item.file.read()
|
| 77 |
-
filename = file_item.filename
|
| 78 |
-
|
| 79 |
scale = int(query.get('scale', [4])[0])
|
| 80 |
if scale not in [2, 4]:
|
| 81 |
scale = 4
|
|
@@ -116,7 +153,103 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 116 |
else:
|
| 117 |
self.send_response(200)
|
| 118 |
self.send_header('Content-Type', 'image/png')
|
| 119 |
-
self.send_header('Content-Disposition',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
self.end_headers()
|
| 121 |
with open(output_path, 'rb') as f:
|
| 122 |
self.wfile.write(f.read())
|
|
@@ -139,7 +272,7 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 139 |
swagger_html = """<!DOCTYPE html>
|
| 140 |
<html>
|
| 141 |
<head>
|
| 142 |
-
<title>AI Image
|
| 143 |
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
| 144 |
<style>
|
| 145 |
body { margin: 0; padding: 0; }
|
|
@@ -171,134 +304,41 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 171 |
openapi_spec = {
|
| 172 |
"openapi": "3.1.0",
|
| 173 |
"info": {
|
| 174 |
-
"title": "AI Image
|
| 175 |
-
"description": "AI-
|
| 176 |
-
"version": "
|
| 177 |
-
"contact": {"name": "AI Image Enhancer"}
|
| 178 |
},
|
| 179 |
"servers": [{"url": "/", "description": "Current server"}],
|
| 180 |
"paths": {
|
| 181 |
"/enhance": {
|
| 182 |
"post": {
|
| 183 |
-
"summary": "Enhance
|
| 184 |
-
"description": "
|
| 185 |
-
"
|
| 186 |
-
"
|
| 187 |
-
|
| 188 |
-
"name": "scale",
|
| 189 |
-
"in": "query",
|
| 190 |
-
"description": "Upscale factor (2 or 4)",
|
| 191 |
-
"required": False,
|
| 192 |
-
"schema": {"type": "integer", "default": 4, "enum": [2, 4]}
|
| 193 |
-
}
|
| 194 |
-
],
|
| 195 |
-
"requestBody": {
|
| 196 |
-
"required": True,
|
| 197 |
-
"content": {
|
| 198 |
-
"multipart/form-data": {
|
| 199 |
-
"schema": {
|
| 200 |
-
"type": "object",
|
| 201 |
-
"required": ["file"],
|
| 202 |
-
"properties": {
|
| 203 |
-
"file": {
|
| 204 |
-
"type": "string",
|
| 205 |
-
"format": "binary",
|
| 206 |
-
"description": "Image file (PNG, JPG, JPEG, WebP, BMP)"
|
| 207 |
-
}
|
| 208 |
-
}
|
| 209 |
-
}
|
| 210 |
-
}
|
| 211 |
-
}
|
| 212 |
-
},
|
| 213 |
-
"responses": {
|
| 214 |
-
"200": {
|
| 215 |
-
"description": "Enhanced image",
|
| 216 |
-
"content": {"image/png": {"schema": {"type": "string", "format": "binary"}}}
|
| 217 |
-
},
|
| 218 |
-
"400": {"description": "Invalid file type"},
|
| 219 |
-
"500": {"description": "Processing error"}
|
| 220 |
-
}
|
| 221 |
}
|
| 222 |
},
|
| 223 |
-
"/
|
| 224 |
"post": {
|
| 225 |
-
"summary": "
|
| 226 |
-
"description": "
|
| 227 |
-
"
|
| 228 |
-
"
|
| 229 |
-
|
| 230 |
-
"name": "scale",
|
| 231 |
-
"in": "query",
|
| 232 |
-
"description": "Upscale factor (2 or 4)",
|
| 233 |
-
"required": False,
|
| 234 |
-
"schema": {"type": "integer", "default": 4, "enum": [2, 4]}
|
| 235 |
-
}
|
| 236 |
-
],
|
| 237 |
-
"requestBody": {
|
| 238 |
-
"required": True,
|
| 239 |
-
"content": {
|
| 240 |
-
"multipart/form-data": {
|
| 241 |
-
"schema": {
|
| 242 |
-
"type": "object",
|
| 243 |
-
"required": ["file"],
|
| 244 |
-
"properties": {
|
| 245 |
-
"file": {
|
| 246 |
-
"type": "string",
|
| 247 |
-
"format": "binary",
|
| 248 |
-
"description": "Image file"
|
| 249 |
-
}
|
| 250 |
-
}
|
| 251 |
-
}
|
| 252 |
-
}
|
| 253 |
-
}
|
| 254 |
-
},
|
| 255 |
-
"responses": {
|
| 256 |
-
"200": {
|
| 257 |
-
"description": "Enhanced image data",
|
| 258 |
-
"content": {
|
| 259 |
-
"application/json": {
|
| 260 |
-
"schema": {
|
| 261 |
-
"type": "object",
|
| 262 |
-
"properties": {
|
| 263 |
-
"success": {"type": "boolean"},
|
| 264 |
-
"image_base64": {"type": "string"},
|
| 265 |
-
"original_size": {"type": "object"},
|
| 266 |
-
"enhanced_size": {"type": "object"},
|
| 267 |
-
"scale_factor": {"type": "integer"}
|
| 268 |
-
}
|
| 269 |
-
}
|
| 270 |
-
}
|
| 271 |
-
}
|
| 272 |
-
}
|
| 273 |
-
}
|
| 274 |
}
|
| 275 |
},
|
| 276 |
-
"/
|
| 277 |
-
"
|
| 278 |
-
"summary": "
|
| 279 |
-
"description": "
|
| 280 |
-
"
|
| 281 |
-
"
|
| 282 |
-
|
| 283 |
-
"description": "API status",
|
| 284 |
-
"content": {"application/json": {"schema": {"type": "object"}}}
|
| 285 |
-
}
|
| 286 |
-
}
|
| 287 |
}
|
| 288 |
},
|
| 289 |
-
"/
|
| 290 |
-
|
| 291 |
-
"summary": "Model information",
|
| 292 |
-
"description": "Get information about the loaded AI model",
|
| 293 |
-
"operationId": "model_info",
|
| 294 |
-
"responses": {
|
| 295 |
-
"200": {
|
| 296 |
-
"description": "Model details",
|
| 297 |
-
"content": {"application/json": {"schema": {"type": "object"}}}
|
| 298 |
-
}
|
| 299 |
-
}
|
| 300 |
-
}
|
| 301 |
-
}
|
| 302 |
}
|
| 303 |
}
|
| 304 |
self.send_json(openapi_spec)
|
|
@@ -324,11 +364,11 @@ class APIHandler(SimpleHTTPRequestHandler):
|
|
| 324 |
def run_server(port=5000):
|
| 325 |
server_address = ('0.0.0.0', port)
|
| 326 |
httpd = HTTPServer(server_address, APIHandler)
|
| 327 |
-
print(f"AI Image
|
| 328 |
print(f"API Documentation: http://0.0.0.0:{port}/docs")
|
| 329 |
print(f"Frontend: http://0.0.0.0:{port}/")
|
| 330 |
-
print("\nNote: This is
|
| 331 |
-
print("
|
| 332 |
httpd.serve_forever()
|
| 333 |
|
| 334 |
if __name__ == "__main__":
|
|
|
|
| 1 |
"""
|
| 2 |
Lightweight local preview server for testing the API structure.
|
| 3 |
+
This version uses simple image processing instead of AI models.
|
| 4 |
+
For full AI processing, deploy to Hugging Face Spaces.
|
| 5 |
"""
|
|
|
|
| 6 |
import io
|
| 7 |
import uuid
|
| 8 |
from pathlib import Path
|
| 9 |
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
| 10 |
import json
|
|
|
|
| 11 |
import urllib.parse
|
| 12 |
|
| 13 |
UPLOAD_DIR = Path("uploads")
|
|
|
|
| 25 |
elif path == "/docs":
|
| 26 |
self.serve_swagger()
|
| 27 |
elif path == "/health":
|
| 28 |
+
self.send_json({
|
| 29 |
+
"status": "healthy",
|
| 30 |
+
"version": "2.0.0 (preview)",
|
| 31 |
+
"features": ["enhance", "remove-background", "denoise"]
|
| 32 |
+
})
|
| 33 |
elif path == "/model-info":
|
| 34 |
self.send_json({
|
| 35 |
+
"models": {
|
| 36 |
+
"super_resolution": {
|
| 37 |
+
"name": "Real-ESRGAN x4plus",
|
| 38 |
+
"description": "State-of-the-art image super-resolution",
|
| 39 |
+
"note": "Preview mode - deploy to HF Spaces for full AI"
|
| 40 |
+
},
|
| 41 |
+
"background_removal": {
|
| 42 |
+
"name": "BiRefNet-general",
|
| 43 |
+
"description": "High-accuracy background removal",
|
| 44 |
+
"note": "Preview mode - deploy to HF Spaces for full AI"
|
| 45 |
+
},
|
| 46 |
+
"noise_reduction": {
|
| 47 |
+
"name": "Non-Local Means Denoising",
|
| 48 |
+
"description": "Advanced noise reduction algorithm"
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
"supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"]
|
| 52 |
})
|
| 53 |
elif path == "/openapi.json":
|
| 54 |
self.serve_openapi()
|
|
|
|
| 64 |
|
| 65 |
if path == "/enhance" or path == "/enhance/base64":
|
| 66 |
self.handle_enhance(path, query)
|
| 67 |
+
elif path == "/remove-background" or path == "/remove-background/base64":
|
| 68 |
+
self.handle_remove_background(path, query)
|
| 69 |
+
elif path == "/denoise" or path == "/denoise/base64":
|
| 70 |
+
self.handle_denoise(path, query)
|
| 71 |
else:
|
| 72 |
self.send_error(404, "Not Found")
|
| 73 |
|
| 74 |
+
def parse_multipart(self):
|
| 75 |
+
content_type = self.headers.get('Content-Type', '')
|
| 76 |
+
if 'multipart/form-data' not in content_type:
|
| 77 |
+
return None
|
| 78 |
+
|
| 79 |
+
boundary = None
|
| 80 |
+
for part in content_type.split(';'):
|
| 81 |
+
if 'boundary=' in part:
|
| 82 |
+
boundary = part.split('=')[1].strip()
|
| 83 |
+
break
|
| 84 |
+
|
| 85 |
+
if not boundary:
|
| 86 |
+
return None
|
| 87 |
+
|
| 88 |
+
content_length = int(self.headers.get('Content-Length', 0))
|
| 89 |
+
body = self.rfile.read(content_length)
|
| 90 |
+
|
| 91 |
+
boundary_bytes = boundary.encode()
|
| 92 |
+
parts = body.split(b'--' + boundary_bytes)
|
| 93 |
+
|
| 94 |
+
for part in parts:
|
| 95 |
+
if b'filename=' in part:
|
| 96 |
+
header_end = part.find(b'\r\n\r\n')
|
| 97 |
+
if header_end != -1:
|
| 98 |
+
file_data = part[header_end + 4:]
|
| 99 |
+
if file_data.endswith(b'\r\n'):
|
| 100 |
+
file_data = file_data[:-2]
|
| 101 |
+
if file_data.endswith(b'--'):
|
| 102 |
+
file_data = file_data[:-2]
|
| 103 |
+
if file_data.endswith(b'\r\n'):
|
| 104 |
+
file_data = file_data[:-2]
|
| 105 |
+
return file_data
|
| 106 |
+
|
| 107 |
+
return None
|
| 108 |
+
|
| 109 |
def handle_enhance(self, path, query):
|
| 110 |
try:
|
| 111 |
+
file_data = self.parse_multipart()
|
| 112 |
+
if not file_data:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
self.send_error(400, "No file uploaded")
|
| 114 |
return
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
scale = int(query.get('scale', [4])[0])
|
| 117 |
if scale not in [2, 4]:
|
| 118 |
scale = 4
|
|
|
|
| 153 |
else:
|
| 154 |
self.send_response(200)
|
| 155 |
self.send_header('Content-Type', 'image/png')
|
| 156 |
+
self.send_header('Content-Disposition', 'attachment; filename="enhanced.png"')
|
| 157 |
+
self.end_headers()
|
| 158 |
+
with open(output_path, 'rb') as f:
|
| 159 |
+
self.wfile.write(f.read())
|
| 160 |
+
|
| 161 |
+
except Exception as e:
|
| 162 |
+
self.send_error(500, f"Error processing image: {str(e)}")
|
| 163 |
+
|
| 164 |
+
def handle_remove_background(self, path, query):
|
| 165 |
+
try:
|
| 166 |
+
file_data = self.parse_multipart()
|
| 167 |
+
if not file_data:
|
| 168 |
+
self.send_error(400, "No file uploaded")
|
| 169 |
+
return
|
| 170 |
+
|
| 171 |
+
bgcolor = query.get('bgcolor', ['transparent'])[0]
|
| 172 |
+
|
| 173 |
+
from PIL import Image
|
| 174 |
+
input_image = Image.open(io.BytesIO(file_data))
|
| 175 |
+
|
| 176 |
+
if input_image.mode != "RGBA":
|
| 177 |
+
input_image = input_image.convert("RGBA")
|
| 178 |
+
|
| 179 |
+
output_image = input_image
|
| 180 |
+
|
| 181 |
+
file_id = str(uuid.uuid4())
|
| 182 |
+
output_path = OUTPUT_DIR / f"{file_id}_nobg.png"
|
| 183 |
+
output_image.save(output_path, "PNG")
|
| 184 |
+
|
| 185 |
+
if "/base64" in path:
|
| 186 |
+
import base64
|
| 187 |
+
buffer = io.BytesIO()
|
| 188 |
+
output_image.save(buffer, format="PNG")
|
| 189 |
+
buffer.seek(0)
|
| 190 |
+
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 191 |
+
|
| 192 |
+
self.send_json({
|
| 193 |
+
"success": True,
|
| 194 |
+
"image_base64": img_base64,
|
| 195 |
+
"original_size": {"width": input_image.width, "height": input_image.height},
|
| 196 |
+
"background": bgcolor,
|
| 197 |
+
"note": "Preview mode - deploy to Hugging Face for AI background removal"
|
| 198 |
+
})
|
| 199 |
+
else:
|
| 200 |
+
self.send_response(200)
|
| 201 |
+
self.send_header('Content-Type', 'image/png')
|
| 202 |
+
self.send_header('Content-Disposition', 'attachment; filename="nobg.png"')
|
| 203 |
+
self.end_headers()
|
| 204 |
+
with open(output_path, 'rb') as f:
|
| 205 |
+
self.wfile.write(f.read())
|
| 206 |
+
|
| 207 |
+
except Exception as e:
|
| 208 |
+
self.send_error(500, f"Error processing image: {str(e)}")
|
| 209 |
+
|
| 210 |
+
def handle_denoise(self, path, query):
|
| 211 |
+
try:
|
| 212 |
+
file_data = self.parse_multipart()
|
| 213 |
+
if not file_data:
|
| 214 |
+
self.send_error(400, "No file uploaded")
|
| 215 |
+
return
|
| 216 |
+
|
| 217 |
+
strength = int(query.get('strength', [10])[0])
|
| 218 |
+
|
| 219 |
+
from PIL import Image, ImageFilter
|
| 220 |
+
input_image = Image.open(io.BytesIO(file_data))
|
| 221 |
+
|
| 222 |
+
if input_image.mode != "RGB":
|
| 223 |
+
input_image = input_image.convert("RGB")
|
| 224 |
+
|
| 225 |
+
output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
|
| 226 |
+
if strength > 10:
|
| 227 |
+
output_image = output_image.filter(ImageFilter.SMOOTH_MORE)
|
| 228 |
+
if strength > 20:
|
| 229 |
+
output_image = output_image.filter(ImageFilter.SMOOTH_MORE)
|
| 230 |
+
|
| 231 |
+
file_id = str(uuid.uuid4())
|
| 232 |
+
output_path = OUTPUT_DIR / f"{file_id}_denoised.png"
|
| 233 |
+
output_image.save(output_path, "PNG")
|
| 234 |
+
|
| 235 |
+
if "/base64" in path:
|
| 236 |
+
import base64
|
| 237 |
+
buffer = io.BytesIO()
|
| 238 |
+
output_image.save(buffer, format="PNG")
|
| 239 |
+
buffer.seek(0)
|
| 240 |
+
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 241 |
+
|
| 242 |
+
self.send_json({
|
| 243 |
+
"success": True,
|
| 244 |
+
"image_base64": img_base64,
|
| 245 |
+
"original_size": {"width": input_image.width, "height": input_image.height},
|
| 246 |
+
"strength": strength,
|
| 247 |
+
"note": "Preview mode - deploy to Hugging Face for AI denoising"
|
| 248 |
+
})
|
| 249 |
+
else:
|
| 250 |
+
self.send_response(200)
|
| 251 |
+
self.send_header('Content-Type', 'image/png')
|
| 252 |
+
self.send_header('Content-Disposition', 'attachment; filename="denoised.png"')
|
| 253 |
self.end_headers()
|
| 254 |
with open(output_path, 'rb') as f:
|
| 255 |
self.wfile.write(f.read())
|
|
|
|
| 272 |
swagger_html = """<!DOCTYPE html>
|
| 273 |
<html>
|
| 274 |
<head>
|
| 275 |
+
<title>AI Image Processing - API Documentation</title>
|
| 276 |
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
| 277 |
<style>
|
| 278 |
body { margin: 0; padding: 0; }
|
|
|
|
| 304 |
openapi_spec = {
|
| 305 |
"openapi": "3.1.0",
|
| 306 |
"info": {
|
| 307 |
+
"title": "AI Image Processing API",
|
| 308 |
+
"description": "Comprehensive AI-powered image processing API.\n\n**Features:**\n- Image enhancement and upscaling (Real-ESRGAN)\n- Background removal (BiRefNet)\n- Noise reduction (OpenCV NLM)\n\n**Note:** This is a preview deployment. Deploy to Hugging Face Spaces for full AI processing.",
|
| 309 |
+
"version": "2.0.0"
|
|
|
|
| 310 |
},
|
| 311 |
"servers": [{"url": "/", "description": "Current server"}],
|
| 312 |
"paths": {
|
| 313 |
"/enhance": {
|
| 314 |
"post": {
|
| 315 |
+
"summary": "Enhance image (upscale)",
|
| 316 |
+
"description": "Upscale and enhance image quality using Real-ESRGAN.",
|
| 317 |
+
"parameters": [{"name": "scale", "in": "query", "schema": {"type": "integer", "default": 4, "enum": [2, 4]}}],
|
| 318 |
+
"requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
|
| 319 |
+
"responses": {"200": {"description": "Enhanced image"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
}
|
| 321 |
},
|
| 322 |
+
"/remove-background": {
|
| 323 |
"post": {
|
| 324 |
+
"summary": "Remove background",
|
| 325 |
+
"description": "Remove background from image using BiRefNet AI model.",
|
| 326 |
+
"parameters": [{"name": "bgcolor", "in": "query", "schema": {"type": "string", "default": "transparent"}}],
|
| 327 |
+
"requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
|
| 328 |
+
"responses": {"200": {"description": "Image with background removed"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
}
|
| 330 |
},
|
| 331 |
+
"/denoise": {
|
| 332 |
+
"post": {
|
| 333 |
+
"summary": "Reduce noise",
|
| 334 |
+
"description": "Reduce image noise using Non-Local Means Denoising.",
|
| 335 |
+
"parameters": [{"name": "strength", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 30}}],
|
| 336 |
+
"requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
|
| 337 |
+
"responses": {"200": {"description": "Denoised image"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
}
|
| 339 |
},
|
| 340 |
+
"/health": {"get": {"summary": "Health check", "responses": {"200": {"description": "API status"}}}},
|
| 341 |
+
"/model-info": {"get": {"summary": "Model information", "responses": {"200": {"description": "Model details"}}}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
}
|
| 343 |
}
|
| 344 |
self.send_json(openapi_spec)
|
|
|
|
| 364 |
def run_server(port=5000):
|
| 365 |
server_address = ('0.0.0.0', port)
|
| 366 |
httpd = HTTPServer(server_address, APIHandler)
|
| 367 |
+
print(f"AI Image Processing API running at http://0.0.0.0:{port}")
|
| 368 |
print(f"API Documentation: http://0.0.0.0:{port}/docs")
|
| 369 |
print(f"Frontend: http://0.0.0.0:{port}/")
|
| 370 |
+
print("\nNote: This is preview mode using simple processing.")
|
| 371 |
+
print("Deploy to Hugging Face Spaces for full AI features.")
|
| 372 |
httpd.serve_forever()
|
| 373 |
|
| 374 |
if __name__ == "__main__":
|
replit.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
-
# AI Image
|
| 2 |
|
| 3 |
## Overview
|
| 4 |
-
An AI-powered image
|
|
|
|
|
|
|
|
|
|
| 5 |
- FastAPI backend with automatic Swagger API documentation
|
| 6 |
-
- Simple web frontend for testing
|
| 7 |
-
- Designed for deployment to Hugging Face Spaces
|
| 8 |
|
| 9 |
## Current State
|
| 10 |
-
- **Local Preview**: Running with simple
|
| 11 |
- **Full AI Mode**: Available when deployed to Hugging Face Spaces
|
| 12 |
|
| 13 |
## Project Structure
|
|
@@ -22,7 +24,7 @@ An AI-powered image enhancement API using Real-ESRGAN for image super-resolution
|
|
| 22 |
├── Dockerfile # Docker configuration for HF Spaces
|
| 23 |
├── README.md # Hugging Face Spaces configuration
|
| 24 |
├── uploads/ # Temporary upload storage
|
| 25 |
-
└── outputs/ #
|
| 26 |
```
|
| 27 |
|
| 28 |
## API Endpoints
|
|
@@ -30,16 +32,25 @@ An AI-powered image enhancement API using Real-ESRGAN for image super-resolution
|
|
| 30 |
- `GET /docs` - Swagger API documentation
|
| 31 |
- `GET /health` - Health check
|
| 32 |
- `GET /model-info` - Model information
|
| 33 |
-
- `POST /enhance` - Enhance image (
|
| 34 |
-
- `POST /enhance/base64` - Enhance image (returns base64
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
## Deploying to Hugging Face Spaces
|
| 37 |
1. Create a new Space on Hugging Face
|
| 38 |
2. Select "Docker" as the SDK
|
| 39 |
3. Upload all files: `app.py`, `enhancer.py`, `templates/`, `requirements.txt`, `Dockerfile`, `README.md`
|
| 40 |
-
4. The Space will auto-build the container and download
|
| 41 |
|
| 42 |
## Recent Changes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
- 2025-11-28: Initial creation of AI Image Enhancer API
|
| 44 |
- FastAPI backend with Swagger docs
|
| 45 |
- Real-ESRGAN integration for Hugging Face
|
|
|
|
| 1 |
+
# AI Image Processing
|
| 2 |
|
| 3 |
## Overview
|
| 4 |
+
An AI-powered image processing API with multiple features:
|
| 5 |
+
- Image enhancement/upscaling using Real-ESRGAN
|
| 6 |
+
- Background removal using BiRefNet via rembg
|
| 7 |
+
- Noise reduction using OpenCV Non-Local Means Denoising
|
| 8 |
- FastAPI backend with automatic Swagger API documentation
|
| 9 |
+
- Simple web frontend for testing
|
|
|
|
| 10 |
|
| 11 |
## Current State
|
| 12 |
+
- **Local Preview**: Running with simple processing (no heavy AI models due to size constraints)
|
| 13 |
- **Full AI Mode**: Available when deployed to Hugging Face Spaces
|
| 14 |
|
| 15 |
## Project Structure
|
|
|
|
| 24 |
├── Dockerfile # Docker configuration for HF Spaces
|
| 25 |
├── README.md # Hugging Face Spaces configuration
|
| 26 |
├── uploads/ # Temporary upload storage
|
| 27 |
+
└── outputs/ # Processed image outputs
|
| 28 |
```
|
| 29 |
|
| 30 |
## API Endpoints
|
|
|
|
| 32 |
- `GET /docs` - Swagger API documentation
|
| 33 |
- `GET /health` - Health check
|
| 34 |
- `GET /model-info` - Model information
|
| 35 |
+
- `POST /enhance` - Enhance/upscale image (Real-ESRGAN)
|
| 36 |
+
- `POST /enhance/base64` - Enhance image (returns base64)
|
| 37 |
+
- `POST /remove-background` - Remove image background (BiRefNet)
|
| 38 |
+
- `POST /remove-background/base64` - Remove background (returns base64)
|
| 39 |
+
- `POST /denoise` - Reduce image noise (OpenCV NLM)
|
| 40 |
+
- `POST /denoise/base64` - Denoise image (returns base64)
|
| 41 |
|
| 42 |
## Deploying to Hugging Face Spaces
|
| 43 |
1. Create a new Space on Hugging Face
|
| 44 |
2. Select "Docker" as the SDK
|
| 45 |
3. Upload all files: `app.py`, `enhancer.py`, `templates/`, `requirements.txt`, `Dockerfile`, `README.md`
|
| 46 |
+
4. The Space will auto-build the container and download AI models
|
| 47 |
|
| 48 |
## Recent Changes
|
| 49 |
+
- 2025-11-28: Added background removal and noise reduction features
|
| 50 |
+
- BiRefNet integration via rembg for background removal
|
| 51 |
+
- OpenCV Non-Local Means Denoising
|
| 52 |
+
- Updated frontend with feature tabs
|
| 53 |
+
- Updated API documentation
|
| 54 |
- 2025-11-28: Initial creation of AI Image Enhancer API
|
| 55 |
- FastAPI backend with Swagger docs
|
| 56 |
- Real-ESRGAN integration for Hugging Face
|
requirements.txt
CHANGED
|
@@ -3,9 +3,11 @@ uvicorn[standard]==0.27.0
|
|
| 3 |
python-multipart==0.0.6
|
| 4 |
pillow==10.2.0
|
| 5 |
numpy==1.26.3
|
|
|
|
| 6 |
torch>=2.1.0
|
| 7 |
torchvision>=0.16.0
|
| 8 |
realesrgan==0.3.0
|
| 9 |
basicsr==1.4.2
|
| 10 |
gfpgan==1.3.8
|
| 11 |
-
|
|
|
|
|
|
| 3 |
python-multipart==0.0.6
|
| 4 |
pillow==10.2.0
|
| 5 |
numpy==1.26.3
|
| 6 |
+
opencv-python-headless==4.9.0.80
|
| 7 |
torch>=2.1.0
|
| 8 |
torchvision>=0.16.0
|
| 9 |
realesrgan==0.3.0
|
| 10 |
basicsr==1.4.2
|
| 11 |
gfpgan==1.3.8
|
| 12 |
+
rembg==2.0.50
|
| 13 |
+
onnxruntime==1.17.0
|
templates/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>AI Image
|
| 7 |
<style>
|
| 8 |
* {
|
| 9 |
box-sizing: border-box;
|
|
@@ -114,7 +114,7 @@
|
|
| 114 |
color: #888;
|
| 115 |
}
|
| 116 |
|
| 117 |
-
select {
|
| 118 |
width: 100%;
|
| 119 |
padding: 12px;
|
| 120 |
border-radius: 8px;
|
|
@@ -124,7 +124,46 @@
|
|
| 124 |
font-size: 1rem;
|
| 125 |
}
|
| 126 |
|
| 127 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
width: 100%;
|
| 129 |
padding: 15px;
|
| 130 |
margin-top: 20px;
|
|
@@ -138,12 +177,12 @@
|
|
| 138 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 139 |
}
|
| 140 |
|
| 141 |
-
.
|
| 142 |
transform: translateY(-2px);
|
| 143 |
box-shadow: 0 10px 30px rgba(0, 210, 255, 0.3);
|
| 144 |
}
|
| 145 |
|
| 146 |
-
.
|
| 147 |
opacity: 0.5;
|
| 148 |
cursor: not-allowed;
|
| 149 |
}
|
|
@@ -271,19 +310,36 @@
|
|
| 271 |
color: #888;
|
| 272 |
font-size: 0.9rem;
|
| 273 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
</style>
|
| 275 |
</head>
|
| 276 |
<body>
|
| 277 |
<div class="container">
|
| 278 |
<header>
|
| 279 |
-
<h1>AI Image
|
| 280 |
-
<p class="subtitle">Enhance
|
| 281 |
<div class="api-link">
|
| 282 |
<a href="/docs" target="_blank">View API Documentation</a>
|
| 283 |
</div>
|
| 284 |
</header>
|
| 285 |
|
| 286 |
<section class="upload-section">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
<div class="drop-zone" id="dropZone">
|
| 288 |
<div class="drop-zone-icon">📷</div>
|
| 289 |
<p>Drag & drop an image here or click to select</p>
|
|
@@ -291,25 +347,60 @@
|
|
| 291 |
</div>
|
| 292 |
<input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,image/webp,image/bmp">
|
| 293 |
|
| 294 |
-
<div class="options">
|
| 295 |
-
<div class="
|
| 296 |
-
<
|
| 297 |
-
|
| 298 |
-
<
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
</div>
|
| 302 |
</div>
|
| 303 |
|
| 304 |
-
<button class="
|
| 305 |
|
| 306 |
<div class="error" id="error"></div>
|
| 307 |
</section>
|
| 308 |
|
| 309 |
<div class="loading" id="loading">
|
| 310 |
<div class="spinner"></div>
|
| 311 |
-
<p>
|
| 312 |
-
<p><small>This may take a moment
|
| 313 |
</div>
|
| 314 |
|
| 315 |
<section class="results-section" id="results">
|
|
@@ -318,32 +409,32 @@
|
|
| 318 |
<h3>Original</h3>
|
| 319 |
<img id="originalImg" src="" alt="Original image">
|
| 320 |
</div>
|
| 321 |
-
<div class="image-box">
|
| 322 |
-
<h3>
|
| 323 |
-
<img id="
|
| 324 |
-
<a href="#" class="download-btn" id="downloadBtn" download="
|
| 325 |
</div>
|
| 326 |
</div>
|
| 327 |
</section>
|
| 328 |
|
| 329 |
<section class="info-section">
|
| 330 |
-
<h2>
|
| 331 |
<div class="info-grid">
|
| 332 |
<div class="info-item">
|
| 333 |
-
<h4>
|
| 334 |
-
<p>
|
| 335 |
</div>
|
| 336 |
<div class="info-item">
|
| 337 |
-
<h4>
|
| 338 |
-
<p>
|
| 339 |
</div>
|
| 340 |
<div class="info-item">
|
| 341 |
-
<h4>
|
| 342 |
-
<p>
|
| 343 |
</div>
|
| 344 |
<div class="info-item">
|
| 345 |
<h4>API Access</h4>
|
| 346 |
-
<p>RESTful API with Swagger documentation at /docs</p>
|
| 347 |
</div>
|
| 348 |
</div>
|
| 349 |
</section>
|
|
@@ -352,16 +443,56 @@
|
|
| 352 |
<script>
|
| 353 |
const dropZone = document.getElementById('dropZone');
|
| 354 |
const fileInput = document.getElementById('fileInput');
|
| 355 |
-
const
|
| 356 |
const loading = document.getElementById('loading');
|
|
|
|
| 357 |
const results = document.getElementById('results');
|
| 358 |
const error = document.getElementById('error');
|
| 359 |
const originalImg = document.getElementById('originalImg');
|
| 360 |
-
const
|
| 361 |
const downloadBtn = document.getElementById('downloadBtn');
|
| 362 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
let selectedFile = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
|
| 366 |
dropZone.addEventListener('click', () => fileInput.click());
|
| 367 |
|
|
@@ -396,7 +527,7 @@
|
|
| 396 |
}
|
| 397 |
|
| 398 |
selectedFile = file;
|
| 399 |
-
|
| 400 |
dropZone.innerHTML = `
|
| 401 |
<div class="drop-zone-icon">✅</div>
|
| 402 |
<p><strong>${file.name}</strong></p>
|
|
@@ -412,35 +543,68 @@
|
|
| 412 |
hideError();
|
| 413 |
}
|
| 414 |
|
| 415 |
-
|
| 416 |
if (!selectedFile) return;
|
| 417 |
|
| 418 |
-
const scale = scaleSelect.value;
|
| 419 |
const formData = new FormData();
|
| 420 |
formData.append('file', selectedFile);
|
| 421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
loading.classList.add('show');
|
| 423 |
results.classList.remove('show');
|
| 424 |
-
|
| 425 |
hideError();
|
| 426 |
|
| 427 |
try {
|
| 428 |
-
const response = await fetch(`
|
| 429 |
method: 'POST',
|
| 430 |
body: formData
|
| 431 |
});
|
| 432 |
|
| 433 |
if (!response.ok) {
|
| 434 |
const errorData = await response.json();
|
| 435 |
-
throw new Error(errorData.detail || '
|
| 436 |
}
|
| 437 |
|
| 438 |
const blob = await response.blob();
|
| 439 |
const imageUrl = URL.createObjectURL(blob);
|
| 440 |
|
| 441 |
-
|
| 442 |
downloadBtn.href = imageUrl;
|
| 443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
loading.classList.remove('show');
|
| 445 |
results.classList.add('show');
|
| 446 |
|
|
@@ -449,7 +613,7 @@
|
|
| 449 |
loading.classList.remove('show');
|
| 450 |
}
|
| 451 |
|
| 452 |
-
|
| 453 |
});
|
| 454 |
|
| 455 |
function showError(message) {
|
|
@@ -460,6 +624,8 @@
|
|
| 460 |
function hideError() {
|
| 461 |
error.classList.remove('show');
|
| 462 |
}
|
|
|
|
|
|
|
| 463 |
</script>
|
| 464 |
</body>
|
| 465 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AI Image Processing</title>
|
| 7 |
<style>
|
| 8 |
* {
|
| 9 |
box-sizing: border-box;
|
|
|
|
| 114 |
color: #888;
|
| 115 |
}
|
| 116 |
|
| 117 |
+
select, input[type="text"] {
|
| 118 |
width: 100%;
|
| 119 |
padding: 12px;
|
| 120 |
border-radius: 8px;
|
|
|
|
| 124 |
font-size: 1rem;
|
| 125 |
}
|
| 126 |
|
| 127 |
+
.feature-tabs {
|
| 128 |
+
display: flex;
|
| 129 |
+
gap: 10px;
|
| 130 |
+
margin-bottom: 20px;
|
| 131 |
+
flex-wrap: wrap;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.feature-tab {
|
| 135 |
+
padding: 12px 24px;
|
| 136 |
+
border: none;
|
| 137 |
+
border-radius: 8px;
|
| 138 |
+
background: rgba(255, 255, 255, 0.1);
|
| 139 |
+
color: #888;
|
| 140 |
+
cursor: pointer;
|
| 141 |
+
transition: all 0.3s;
|
| 142 |
+
font-size: 1rem;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.feature-tab:hover {
|
| 146 |
+
background: rgba(255, 255, 255, 0.15);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.feature-tab.active {
|
| 150 |
+
background: linear-gradient(90deg, #00d2ff, #3a7bd5);
|
| 151 |
+
color: #fff;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.feature-options {
|
| 155 |
+
display: none;
|
| 156 |
+
padding: 15px;
|
| 157 |
+
background: rgba(255, 255, 255, 0.03);
|
| 158 |
+
border-radius: 8px;
|
| 159 |
+
margin-bottom: 15px;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.feature-options.active {
|
| 163 |
+
display: block;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.process-btn {
|
| 167 |
width: 100%;
|
| 168 |
padding: 15px;
|
| 169 |
margin-top: 20px;
|
|
|
|
| 177 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 178 |
}
|
| 179 |
|
| 180 |
+
.process-btn:hover:not(:disabled) {
|
| 181 |
transform: translateY(-2px);
|
| 182 |
box-shadow: 0 10px 30px rgba(0, 210, 255, 0.3);
|
| 183 |
}
|
| 184 |
|
| 185 |
+
.process-btn:disabled {
|
| 186 |
opacity: 0.5;
|
| 187 |
cursor: not-allowed;
|
| 188 |
}
|
|
|
|
| 310 |
color: #888;
|
| 311 |
font-size: 0.9rem;
|
| 312 |
}
|
| 313 |
+
|
| 314 |
+
.checkerboard {
|
| 315 |
+
background-image:
|
| 316 |
+
linear-gradient(45deg, #666 25%, transparent 25%),
|
| 317 |
+
linear-gradient(-45deg, #666 25%, transparent 25%),
|
| 318 |
+
linear-gradient(45deg, transparent 75%, #666 75%),
|
| 319 |
+
linear-gradient(-45deg, transparent 75%, #666 75%);
|
| 320 |
+
background-size: 20px 20px;
|
| 321 |
+
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
| 322 |
+
background-color: #444;
|
| 323 |
+
}
|
| 324 |
</style>
|
| 325 |
</head>
|
| 326 |
<body>
|
| 327 |
<div class="container">
|
| 328 |
<header>
|
| 329 |
+
<h1>AI Image Processing</h1>
|
| 330 |
+
<p class="subtitle">Enhance, remove backgrounds, and denoise your images with AI</p>
|
| 331 |
<div class="api-link">
|
| 332 |
<a href="/docs" target="_blank">View API Documentation</a>
|
| 333 |
</div>
|
| 334 |
</header>
|
| 335 |
|
| 336 |
<section class="upload-section">
|
| 337 |
+
<div class="feature-tabs">
|
| 338 |
+
<button class="feature-tab active" data-feature="enhance">Enhance</button>
|
| 339 |
+
<button class="feature-tab" data-feature="remove-bg">Remove Background</button>
|
| 340 |
+
<button class="feature-tab" data-feature="denoise">Denoise</button>
|
| 341 |
+
</div>
|
| 342 |
+
|
| 343 |
<div class="drop-zone" id="dropZone">
|
| 344 |
<div class="drop-zone-icon">📷</div>
|
| 345 |
<p>Drag & drop an image here or click to select</p>
|
|
|
|
| 347 |
</div>
|
| 348 |
<input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,image/webp,image/bmp">
|
| 349 |
|
| 350 |
+
<div id="enhanceOptions" class="feature-options active">
|
| 351 |
+
<div class="options">
|
| 352 |
+
<div class="option-group">
|
| 353 |
+
<label for="scale">Upscale Factor</label>
|
| 354 |
+
<select id="scale">
|
| 355 |
+
<option value="2">2x - Double resolution</option>
|
| 356 |
+
<option value="4" selected>4x - Quadruple resolution</option>
|
| 357 |
+
</select>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
|
| 362 |
+
<div id="removeBgOptions" class="feature-options">
|
| 363 |
+
<div class="options">
|
| 364 |
+
<div class="option-group">
|
| 365 |
+
<label for="bgcolor">Background Color</label>
|
| 366 |
+
<select id="bgcolor">
|
| 367 |
+
<option value="transparent" selected>Transparent</option>
|
| 368 |
+
<option value="white">White</option>
|
| 369 |
+
<option value="black">Black</option>
|
| 370 |
+
<option value="custom">Custom Color</option>
|
| 371 |
+
</select>
|
| 372 |
+
</div>
|
| 373 |
+
<div class="option-group" id="customColorGroup" style="display: none;">
|
| 374 |
+
<label for="customColor">Custom Color (Hex)</label>
|
| 375 |
+
<input type="text" id="customColor" placeholder="#FF0000" value="#FFFFFF">
|
| 376 |
+
</div>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
+
|
| 380 |
+
<div id="denoiseOptions" class="feature-options">
|
| 381 |
+
<div class="options">
|
| 382 |
+
<div class="option-group">
|
| 383 |
+
<label for="strength">Denoise Strength</label>
|
| 384 |
+
<select id="strength">
|
| 385 |
+
<option value="5">Light (5) - Preserves details</option>
|
| 386 |
+
<option value="10" selected>Medium (10) - Balanced</option>
|
| 387 |
+
<option value="15">Strong (15) - More smoothing</option>
|
| 388 |
+
<option value="20">Very Strong (20)</option>
|
| 389 |
+
<option value="30">Maximum (30)</option>
|
| 390 |
+
</select>
|
| 391 |
+
</div>
|
| 392 |
</div>
|
| 393 |
</div>
|
| 394 |
|
| 395 |
+
<button class="process-btn" id="processBtn" disabled>Process Image</button>
|
| 396 |
|
| 397 |
<div class="error" id="error"></div>
|
| 398 |
</section>
|
| 399 |
|
| 400 |
<div class="loading" id="loading">
|
| 401 |
<div class="spinner"></div>
|
| 402 |
+
<p id="loadingText">Processing your image with AI...</p>
|
| 403 |
+
<p><small>This may take a moment</small></p>
|
| 404 |
</div>
|
| 405 |
|
| 406 |
<section class="results-section" id="results">
|
|
|
|
| 409 |
<h3>Original</h3>
|
| 410 |
<img id="originalImg" src="" alt="Original image">
|
| 411 |
</div>
|
| 412 |
+
<div class="image-box" id="resultBox">
|
| 413 |
+
<h3 id="resultLabel">Processed</h3>
|
| 414 |
+
<img id="processedImg" src="" alt="Processed image">
|
| 415 |
+
<a href="#" class="download-btn" id="downloadBtn" download="processed_image.png">Download Image</a>
|
| 416 |
</div>
|
| 417 |
</div>
|
| 418 |
</section>
|
| 419 |
|
| 420 |
<section class="info-section">
|
| 421 |
+
<h2>Available Features</h2>
|
| 422 |
<div class="info-grid">
|
| 423 |
<div class="info-item">
|
| 424 |
+
<h4>Image Enhancement</h4>
|
| 425 |
+
<p>Upscale images 2x-4x using Real-ESRGAN AI model</p>
|
| 426 |
</div>
|
| 427 |
<div class="info-item">
|
| 428 |
+
<h4>Background Removal</h4>
|
| 429 |
+
<p>Remove backgrounds using BiRefNet deep learning model</p>
|
| 430 |
</div>
|
| 431 |
<div class="info-item">
|
| 432 |
+
<h4>Noise Reduction</h4>
|
| 433 |
+
<p>Reduce image noise with advanced denoising algorithms</p>
|
| 434 |
</div>
|
| 435 |
<div class="info-item">
|
| 436 |
<h4>API Access</h4>
|
| 437 |
+
<p>Full RESTful API with Swagger documentation at /docs</p>
|
| 438 |
</div>
|
| 439 |
</div>
|
| 440 |
</section>
|
|
|
|
| 443 |
<script>
|
| 444 |
const dropZone = document.getElementById('dropZone');
|
| 445 |
const fileInput = document.getElementById('fileInput');
|
| 446 |
+
const processBtn = document.getElementById('processBtn');
|
| 447 |
const loading = document.getElementById('loading');
|
| 448 |
+
const loadingText = document.getElementById('loadingText');
|
| 449 |
const results = document.getElementById('results');
|
| 450 |
const error = document.getElementById('error');
|
| 451 |
const originalImg = document.getElementById('originalImg');
|
| 452 |
+
const processedImg = document.getElementById('processedImg');
|
| 453 |
const downloadBtn = document.getElementById('downloadBtn');
|
| 454 |
+
const resultBox = document.getElementById('resultBox');
|
| 455 |
+
const resultLabel = document.getElementById('resultLabel');
|
| 456 |
+
|
| 457 |
+
const featureTabs = document.querySelectorAll('.feature-tab');
|
| 458 |
+
const bgcolorSelect = document.getElementById('bgcolor');
|
| 459 |
+
const customColorGroup = document.getElementById('customColorGroup');
|
| 460 |
|
| 461 |
let selectedFile = null;
|
| 462 |
+
let currentFeature = 'enhance';
|
| 463 |
+
|
| 464 |
+
featureTabs.forEach(tab => {
|
| 465 |
+
tab.addEventListener('click', () => {
|
| 466 |
+
featureTabs.forEach(t => t.classList.remove('active'));
|
| 467 |
+
tab.classList.add('active');
|
| 468 |
+
currentFeature = tab.dataset.feature;
|
| 469 |
+
|
| 470 |
+
document.querySelectorAll('.feature-options').forEach(opt => opt.classList.remove('active'));
|
| 471 |
+
|
| 472 |
+
if (currentFeature === 'enhance') {
|
| 473 |
+
document.getElementById('enhanceOptions').classList.add('active');
|
| 474 |
+
} else if (currentFeature === 'remove-bg') {
|
| 475 |
+
document.getElementById('removeBgOptions').classList.add('active');
|
| 476 |
+
} else if (currentFeature === 'denoise') {
|
| 477 |
+
document.getElementById('denoiseOptions').classList.add('active');
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
updateButtonText();
|
| 481 |
+
});
|
| 482 |
+
});
|
| 483 |
+
|
| 484 |
+
bgcolorSelect.addEventListener('change', () => {
|
| 485 |
+
customColorGroup.style.display = bgcolorSelect.value === 'custom' ? 'block' : 'none';
|
| 486 |
+
});
|
| 487 |
+
|
| 488 |
+
function updateButtonText() {
|
| 489 |
+
const texts = {
|
| 490 |
+
'enhance': 'Enhance Image',
|
| 491 |
+
'remove-bg': 'Remove Background',
|
| 492 |
+
'denoise': 'Denoise Image'
|
| 493 |
+
};
|
| 494 |
+
processBtn.textContent = texts[currentFeature] || 'Process Image';
|
| 495 |
+
}
|
| 496 |
|
| 497 |
dropZone.addEventListener('click', () => fileInput.click());
|
| 498 |
|
|
|
|
| 527 |
}
|
| 528 |
|
| 529 |
selectedFile = file;
|
| 530 |
+
processBtn.disabled = false;
|
| 531 |
dropZone.innerHTML = `
|
| 532 |
<div class="drop-zone-icon">✅</div>
|
| 533 |
<p><strong>${file.name}</strong></p>
|
|
|
|
| 543 |
hideError();
|
| 544 |
}
|
| 545 |
|
| 546 |
+
processBtn.addEventListener('click', async () => {
|
| 547 |
if (!selectedFile) return;
|
| 548 |
|
|
|
|
| 549 |
const formData = new FormData();
|
| 550 |
formData.append('file', selectedFile);
|
| 551 |
|
| 552 |
+
let endpoint = '/enhance';
|
| 553 |
+
let params = new URLSearchParams();
|
| 554 |
+
|
| 555 |
+
if (currentFeature === 'enhance') {
|
| 556 |
+
const scale = document.getElementById('scale').value;
|
| 557 |
+
params.append('scale', scale);
|
| 558 |
+
loadingText.textContent = 'Enhancing your image with AI...';
|
| 559 |
+
resultLabel.textContent = 'Enhanced';
|
| 560 |
+
} else if (currentFeature === 'remove-bg') {
|
| 561 |
+
endpoint = '/remove-background';
|
| 562 |
+
let bgcolor = bgcolorSelect.value;
|
| 563 |
+
if (bgcolor === 'custom') {
|
| 564 |
+
bgcolor = document.getElementById('customColor').value;
|
| 565 |
+
}
|
| 566 |
+
params.append('bgcolor', bgcolor);
|
| 567 |
+
loadingText.textContent = 'Removing background with AI...';
|
| 568 |
+
resultLabel.textContent = 'Background Removed';
|
| 569 |
+
resultBox.classList.add('checkerboard');
|
| 570 |
+
} else if (currentFeature === 'denoise') {
|
| 571 |
+
endpoint = '/denoise';
|
| 572 |
+
const strength = document.getElementById('strength').value;
|
| 573 |
+
params.append('strength', strength);
|
| 574 |
+
loadingText.textContent = 'Reducing noise in your image...';
|
| 575 |
+
resultLabel.textContent = 'Denoised';
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
if (currentFeature !== 'remove-bg') {
|
| 579 |
+
resultBox.classList.remove('checkerboard');
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
loading.classList.add('show');
|
| 583 |
results.classList.remove('show');
|
| 584 |
+
processBtn.disabled = true;
|
| 585 |
hideError();
|
| 586 |
|
| 587 |
try {
|
| 588 |
+
const response = await fetch(`${endpoint}?${params.toString()}`, {
|
| 589 |
method: 'POST',
|
| 590 |
body: formData
|
| 591 |
});
|
| 592 |
|
| 593 |
if (!response.ok) {
|
| 594 |
const errorData = await response.json();
|
| 595 |
+
throw new Error(errorData.detail || 'Processing failed');
|
| 596 |
}
|
| 597 |
|
| 598 |
const blob = await response.blob();
|
| 599 |
const imageUrl = URL.createObjectURL(blob);
|
| 600 |
|
| 601 |
+
processedImg.src = imageUrl;
|
| 602 |
downloadBtn.href = imageUrl;
|
| 603 |
|
| 604 |
+
const filename = currentFeature === 'enhance' ? 'enhanced' :
|
| 605 |
+
currentFeature === 'remove-bg' ? 'nobg' : 'denoised';
|
| 606 |
+
downloadBtn.download = `${filename}_${selectedFile.name.split('.')[0]}.png`;
|
| 607 |
+
|
| 608 |
loading.classList.remove('show');
|
| 609 |
results.classList.add('show');
|
| 610 |
|
|
|
|
| 613 |
loading.classList.remove('show');
|
| 614 |
}
|
| 615 |
|
| 616 |
+
processBtn.disabled = false;
|
| 617 |
});
|
| 618 |
|
| 619 |
function showError(message) {
|
|
|
|
| 624 |
function hideError() {
|
| 625 |
error.classList.remove('show');
|
| 626 |
}
|
| 627 |
+
|
| 628 |
+
updateButtonText();
|
| 629 |
</script>
|
| 630 |
</body>
|
| 631 |
</html>
|