Spaces:
Sleeping
Sleeping
Commit
·
43c5517
1
Parent(s):
3beeaa7
Git Push
Browse files- .env.example +2 -0
- .gitignore +13 -0
- Dockerfile +26 -0
- README.md +8 -11
- app.py +108 -0
- config.py +8 -0
- convertToOnx.py +115 -0
- example_client.py +29 -0
- generated_mask.png +0 -0
- generated_mask_1.png +0 -0
- inpainted_result.png +0 -0
- model_index.json +7 -0
- requirements.txt +11 -0
- test.py +92 -0
- test2.py +101 -0
- test_app.py +10 -0
.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
HF_TOKEN=your_huggingface_token_here
|
| 2 |
+
MAX_FILE_SIZE=10485760 # 10MB in bytes
|
.gitignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.safetensors
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.env
|
| 5 |
+
onnx_output/*
|
| 6 |
+
OutPutModel/*
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
.coverage
|
| 9 |
+
htmlcov/
|
| 10 |
+
.venv/
|
| 11 |
+
.idea/
|
| 12 |
+
.vscode/
|
| 13 |
+
*.log
|
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04
|
| 2 |
+
|
| 3 |
+
# Add non-root user
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
WORKDIR /home/user/app
|
| 6 |
+
|
| 7 |
+
# Install dependencies
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
python3 \
|
| 10 |
+
python3-pip \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# Copy requirements first for better caching
|
| 14 |
+
COPY --chown=user:user requirements.txt .
|
| 15 |
+
RUN pip3 install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# Copy application code
|
| 18 |
+
COPY --chown=user:user . .
|
| 19 |
+
|
| 20 |
+
# Switch to non-root user
|
| 21 |
+
USER user
|
| 22 |
+
|
| 23 |
+
# Expose port
|
| 24 |
+
EXPOSE 7860
|
| 25 |
+
|
| 26 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: docker
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Stable Diffusion Inpainting API
|
| 3 |
+
emoji: 🎨
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
|
|
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
import torch
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import io
|
| 6 |
+
import base64
|
| 7 |
+
from diffusers import StableDiffusionInpaintPipeline
|
| 8 |
+
import gc
|
| 9 |
+
from fastapi.responses import JSONResponse
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
app = FastAPI()
|
| 13 |
+
|
| 14 |
+
# Add CORS middleware
|
| 15 |
+
app.add_middleware(
|
| 16 |
+
CORSMiddleware,
|
| 17 |
+
allow_origins=["*"],
|
| 18 |
+
allow_credentials=True,
|
| 19 |
+
allow_methods=["*"],
|
| 20 |
+
allow_headers=["*"],
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Global variable for the model
|
| 24 |
+
pipe = None
|
| 25 |
+
|
| 26 |
+
def load_model():
|
| 27 |
+
global pipe
|
| 28 |
+
if pipe is None:
|
| 29 |
+
# Use the pre-uploaded model from Hugging Face
|
| 30 |
+
model_id = "Uminosachi/realisticVisionV51_v51VAE-inpainting"
|
| 31 |
+
pipe = StableDiffusionInpaintPipeline.from_pretrained(
|
| 32 |
+
model_id,
|
| 33 |
+
torch_dtype=torch.float16,
|
| 34 |
+
safety_checker=None
|
| 35 |
+
).to("cuda")
|
| 36 |
+
pipe.enable_attention_slicing(slice_size="max")
|
| 37 |
+
pipe.enable_sequential_cpu_offload()
|
| 38 |
+
return pipe
|
| 39 |
+
|
| 40 |
+
@app.on_event("startup")
|
| 41 |
+
async def startup_event():
|
| 42 |
+
if torch.cuda.is_available():
|
| 43 |
+
load_model()
|
| 44 |
+
|
| 45 |
+
def image_to_base64(image: Image.Image) -> str:
|
| 46 |
+
buffered = io.BytesIO()
|
| 47 |
+
image.save(buffered, format="PNG")
|
| 48 |
+
return base64.b64encode(buffered.getvalue()).decode()
|
| 49 |
+
|
| 50 |
+
@app.post("/inpaint")
|
| 51 |
+
async def inpaint(
|
| 52 |
+
image: UploadFile = File(...),
|
| 53 |
+
mask: UploadFile = File(...),
|
| 54 |
+
prompt: str = "add some flowers and a fountain",
|
| 55 |
+
negative_prompt: str = "blurry, low quality, distorted"
|
| 56 |
+
):
|
| 57 |
+
try:
|
| 58 |
+
# Add file size check (10MB limit)
|
| 59 |
+
max_size = 10 * 1024 * 1024 # 10MB
|
| 60 |
+
if len(await image.read()) > max_size or len(await mask.read()) > max_size:
|
| 61 |
+
return JSONResponse(
|
| 62 |
+
status_code=400,
|
| 63 |
+
content={"error": "File size too large. Maximum size is 10MB"}
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# Reset file positions
|
| 67 |
+
await image.seek(0)
|
| 68 |
+
await mask.seek(0)
|
| 69 |
+
|
| 70 |
+
# Read and process input image
|
| 71 |
+
image_data = await image.read()
|
| 72 |
+
mask_data = await mask.read()
|
| 73 |
+
|
| 74 |
+
original_image = Image.open(io.BytesIO(image_data))
|
| 75 |
+
mask_image = Image.open(io.BytesIO(mask_data))
|
| 76 |
+
|
| 77 |
+
# Resize to multiple of 8
|
| 78 |
+
width, height = (dim - dim % 8 for dim in original_image.size)
|
| 79 |
+
original_image = original_image.resize((width, height))
|
| 80 |
+
mask_image = mask_image.resize((width, height))
|
| 81 |
+
mask_image = mask_image.convert("L")
|
| 82 |
+
|
| 83 |
+
# Perform inpainting
|
| 84 |
+
with torch.cuda.amp.autocast():
|
| 85 |
+
output_image = pipe(
|
| 86 |
+
prompt=prompt,
|
| 87 |
+
negative_prompt=negative_prompt,
|
| 88 |
+
image=original_image,
|
| 89 |
+
mask_image=mask_image,
|
| 90 |
+
num_inference_steps=20,
|
| 91 |
+
guidance_scale=7.5,
|
| 92 |
+
).images[0]
|
| 93 |
+
|
| 94 |
+
# Convert output image to base64
|
| 95 |
+
output_base64 = image_to_base64(output_image)
|
| 96 |
+
|
| 97 |
+
# Clean up
|
| 98 |
+
torch.cuda.empty_cache()
|
| 99 |
+
gc.collect()
|
| 100 |
+
|
| 101 |
+
return {"status": "success", "image": output_base64}
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 105 |
+
|
| 106 |
+
@app.get("/health")
|
| 107 |
+
async def health_check():
|
| 108 |
+
return {"status": "healthy", "cuda_available": torch.cuda.is_available()}
|
config.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
logging.basicConfig(
|
| 4 |
+
level=logging.INFO,
|
| 5 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
convertToOnx.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from diffusers import StableDiffusionInpaintPipeline
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
def convert_to_onnx(model_path, output_dir):
|
| 6 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 7 |
+
|
| 8 |
+
# Load the pipeline
|
| 9 |
+
pipe = StableDiffusionInpaintPipeline.from_single_file(
|
| 10 |
+
model_path
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
# Move to CPU and ensure float32
|
| 14 |
+
pipe = pipe.to("cpu")
|
| 15 |
+
pipe.to(torch.float32)
|
| 16 |
+
|
| 17 |
+
# Set to evaluation mode
|
| 18 |
+
pipe.unet.eval()
|
| 19 |
+
pipe.vae.eval()
|
| 20 |
+
pipe.text_encoder.eval()
|
| 21 |
+
|
| 22 |
+
# First convert the image through VAE to get correct latent dimensions
|
| 23 |
+
with torch.no_grad():
|
| 24 |
+
# Sample random latent in correct shape
|
| 25 |
+
latent_height = 64 # standard height for SD latents
|
| 26 |
+
latent_width = 64 # standard width for SD latents
|
| 27 |
+
|
| 28 |
+
# Create sample inputs for UNet
|
| 29 |
+
# The UNet expects concatenated latent + mask channels
|
| 30 |
+
latents = torch.randn(1, 4, latent_height, latent_width, dtype=torch.float32)
|
| 31 |
+
mask = torch.ones(1, 1, latent_height, latent_width, dtype=torch.float32)
|
| 32 |
+
masked_image_latents = torch.randn(1, 4, latent_height, latent_width, dtype=torch.float32)
|
| 33 |
+
masked_latents = torch.cat([latents, masked_image_latents, mask], dim=1) # 4 + 4 + 1 = 9 channels
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Time embeddings
|
| 37 |
+
timestep = torch.tensor([1], dtype=torch.int64)
|
| 38 |
+
|
| 39 |
+
# Text embeddings (77 is the standard sequence length)
|
| 40 |
+
text_embeddings = torch.randn(1, 77, 768, dtype=torch.float32)
|
| 41 |
+
|
| 42 |
+
# Export UNet
|
| 43 |
+
pipe.text_encoder.text_model.encoder.layers[0].self_attn.scale = torch.tensor(0.125, dtype=torch.float32)
|
| 44 |
+
|
| 45 |
+
torch.onnx.export(
|
| 46 |
+
pipe.unet,
|
| 47 |
+
args=(masked_latents, timestep, text_embeddings),
|
| 48 |
+
f=f"{output_dir}/unet.onnx",
|
| 49 |
+
input_names=["sample", "timestep", "encoder_hidden_states"],
|
| 50 |
+
output_names=["out_sample"],
|
| 51 |
+
dynamic_axes={
|
| 52 |
+
"sample": {0: "batch", 2: "height", 3: "width"},
|
| 53 |
+
"encoder_hidden_states": {0: "batch", 1: "sequence"},
|
| 54 |
+
"out_sample": {0: "batch", 2: "height", 3: "width"}
|
| 55 |
+
},
|
| 56 |
+
opset_version=17,
|
| 57 |
+
export_params=True
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# Export VAE Decoder
|
| 61 |
+
vae_latent = torch.randn(1, 4, latent_height, latent_width, dtype=torch.float32)
|
| 62 |
+
torch.onnx.export(
|
| 63 |
+
pipe.vae.decoder,
|
| 64 |
+
args=(vae_latent,),
|
| 65 |
+
f=f"{output_dir}/vae_decoder.onnx",
|
| 66 |
+
input_names=["latent"],
|
| 67 |
+
output_names=["image"],
|
| 68 |
+
dynamic_axes={
|
| 69 |
+
"latent": {0: "batch", 2: "height", 3: "width"},
|
| 70 |
+
"image": {0: "batch", 2: "height", 3: "width"}
|
| 71 |
+
},
|
| 72 |
+
opset_version=17,
|
| 73 |
+
export_params=True
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# Export Text Encoder
|
| 77 |
+
input_ids = torch.ones(1, 77, dtype=torch.int64)
|
| 78 |
+
torch.onnx.export(
|
| 79 |
+
pipe.text_encoder,
|
| 80 |
+
args=(input_ids,),
|
| 81 |
+
f=f"{output_dir}/text_encoder.onnx",
|
| 82 |
+
input_names=["input_ids"],
|
| 83 |
+
output_names=["last_hidden_state", "pooler_output"],
|
| 84 |
+
dynamic_axes={
|
| 85 |
+
"input_ids": {0: "batch"},
|
| 86 |
+
"last_hidden_state": {0: "batch"},
|
| 87 |
+
"pooler_output": {0: "batch"}
|
| 88 |
+
},
|
| 89 |
+
opset_version=17,
|
| 90 |
+
export_params=True
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
print("Conversion completed successfully!")
|
| 94 |
+
return True
|
| 95 |
+
|
| 96 |
+
def verify_paths(model_path):
|
| 97 |
+
if not os.path.exists(model_path):
|
| 98 |
+
raise FileNotFoundError(f"Model file not found at: {model_path}")
|
| 99 |
+
|
| 100 |
+
print(f"Model file found at: {model_path}")
|
| 101 |
+
return True
|
| 102 |
+
|
| 103 |
+
if __name__ == "__main__":
|
| 104 |
+
# Set your paths here
|
| 105 |
+
model_path = "realisticVisionV60B1_v51VAE-inpainting.safetensors"
|
| 106 |
+
output_dir = "onnx_output"
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
verify_paths(model_path)
|
| 110 |
+
success = convert_to_onnx(model_path, output_dir)
|
| 111 |
+
if success:
|
| 112 |
+
print(f"ONNX models saved to: {output_dir}")
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"Error during conversion: {str(e)}")
|
| 115 |
+
raise # Re-raise the exception to see full traceback
|
example_client.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import base64
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import io
|
| 5 |
+
|
| 6 |
+
def call_inpaint_api(image_path, mask_path, prompt):
|
| 7 |
+
# Update this with your actual space URL after deployment
|
| 8 |
+
url = "https://your-username-your-space-name.hf.space/inpaint"
|
| 9 |
+
|
| 10 |
+
files = {
|
| 11 |
+
'image': open(image_path, 'rb'),
|
| 12 |
+
'mask': open(mask_path, 'rb')
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
data = {
|
| 16 |
+
'prompt': prompt
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
response = requests.post(url, files=files, data=data)
|
| 20 |
+
|
| 21 |
+
if response.status_code == 200:
|
| 22 |
+
# Decode base64 image
|
| 23 |
+
img_data = base64.b64decode(response.json()['image'])
|
| 24 |
+
img = Image.open(io.BytesIO(img_data))
|
| 25 |
+
img.save('result.png')
|
| 26 |
+
return 'result.png'
|
| 27 |
+
else:
|
| 28 |
+
print(f"Error: {response.text}")
|
| 29 |
+
return None
|
generated_mask.png
ADDED
|
generated_mask_1.png
ADDED
|
inpainted_result.png
ADDED
|
model_index.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"modelId": "realisticVisionV60B1",
|
| 3 |
+
"model_type": "stable-diffusion-inpainting",
|
| 4 |
+
"_class_name": "StableDiffusionInpaintPipeline",
|
| 5 |
+
"scheduler": ["DDIMScheduler", "EulerAncestralDiscreteScheduler", "DPMSolverMultistepScheduler"],
|
| 6 |
+
"torch_dtype": "float16"
|
| 7 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
python-multipart
|
| 3 |
+
torch
|
| 4 |
+
diffusers
|
| 5 |
+
transformers
|
| 6 |
+
pillow
|
| 7 |
+
uvicorn
|
| 8 |
+
huggingface_hub
|
| 9 |
+
python-jose[cryptography]
|
| 10 |
+
passlib[bcrypt]
|
| 11 |
+
python-dotenv
|
test.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from diffusers import StableDiffusionInpaintPipeline
|
| 2 |
+
import torch
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
def setup_model(model_path):
|
| 7 |
+
# Load the base pipeline
|
| 8 |
+
pipe = StableDiffusionInpaintPipeline.from_single_file(
|
| 9 |
+
model_path,
|
| 10 |
+
torch_dtype=torch.float16,
|
| 11 |
+
safety_checker=None
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
# Move to GPU if available
|
| 15 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
+
pipe = pipe.to(device)
|
| 17 |
+
|
| 18 |
+
# Enable memory optimizations
|
| 19 |
+
pipe.enable_attention_slicing()
|
| 20 |
+
|
| 21 |
+
return pipe
|
| 22 |
+
|
| 23 |
+
def prepare_images(image_path, mask_path=None):
|
| 24 |
+
# Load and prepare the original image
|
| 25 |
+
original_image = Image.open(image_path)
|
| 26 |
+
# Resize to a multiple of 8 (required by Stable Diffusion)
|
| 27 |
+
width, height = (dim - dim % 8 for dim in original_image.size)
|
| 28 |
+
original_image = original_image.resize((width, height))
|
| 29 |
+
|
| 30 |
+
if mask_path:
|
| 31 |
+
# Load existing mask if provided
|
| 32 |
+
mask_image = Image.open(mask_path)
|
| 33 |
+
mask_image = mask_image.resize((width, height))
|
| 34 |
+
mask_image = mask_image.convert("L")
|
| 35 |
+
else:
|
| 36 |
+
# Create a simple rectangular mask in the center
|
| 37 |
+
mask_image = Image.new("L", (width, height), 0)
|
| 38 |
+
mask_width = width // 3
|
| 39 |
+
mask_height = height // 3
|
| 40 |
+
x1 = (width - mask_width) // 2
|
| 41 |
+
y1 = (height - mask_height) // 2
|
| 42 |
+
x2 = x1 + mask_width
|
| 43 |
+
y2 = y1 + mask_height
|
| 44 |
+
for y in range(y1, y2):
|
| 45 |
+
for x in range(x1, x2):
|
| 46 |
+
mask_image.putpixel((x, y), 255)
|
| 47 |
+
|
| 48 |
+
return original_image, mask_image
|
| 49 |
+
|
| 50 |
+
def main():
|
| 51 |
+
# Setup paths using raw strings
|
| 52 |
+
model_path = "realisticVisionV60B1_v51VAE-inpainting.safetensors"
|
| 53 |
+
image_path = r"C:\Users\M. Y\Downloads\t2.png"
|
| 54 |
+
|
| 55 |
+
# First install accelerate if not already installed
|
| 56 |
+
try:
|
| 57 |
+
import accelerate
|
| 58 |
+
except ImportError:
|
| 59 |
+
print("Installing accelerate...")
|
| 60 |
+
os.system("pip install accelerate")
|
| 61 |
+
|
| 62 |
+
# Initialize model
|
| 63 |
+
print("Loading model...")
|
| 64 |
+
pipe = setup_model(model_path)
|
| 65 |
+
|
| 66 |
+
# Prepare images
|
| 67 |
+
print("Preparing images...")
|
| 68 |
+
original_image, mask_image = prepare_images(image_path)
|
| 69 |
+
|
| 70 |
+
# Save mask for verification
|
| 71 |
+
mask_image.save("generated_mask.png")
|
| 72 |
+
|
| 73 |
+
# Define your prompt
|
| 74 |
+
prompt = "a realistic photo of a beautiful garden"
|
| 75 |
+
negative_prompt = "blurry, low quality, distorted"
|
| 76 |
+
|
| 77 |
+
print("Performing inpainting...")
|
| 78 |
+
output_image = pipe(
|
| 79 |
+
prompt=prompt,
|
| 80 |
+
negative_prompt=negative_prompt,
|
| 81 |
+
image=original_image,
|
| 82 |
+
mask_image=mask_image,
|
| 83 |
+
num_inference_steps=30,
|
| 84 |
+
guidance_scale=7.5,
|
| 85 |
+
).images[0]
|
| 86 |
+
|
| 87 |
+
# Save the result
|
| 88 |
+
output_image.save("inpainted_result.png")
|
| 89 |
+
print("Inpainting completed! Check 'inpainted_result.png' for the result.")
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
main()
|
test2.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from diffusers import StableDiffusionInpaintPipeline
|
| 2 |
+
import torch
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import os
|
| 5 |
+
from torch.multiprocessing import set_start_method
|
| 6 |
+
import gc
|
| 7 |
+
|
| 8 |
+
def setup_model(model_path):
|
| 9 |
+
# Clear CUDA memory
|
| 10 |
+
torch.cuda.empty_cache()
|
| 11 |
+
gc.collect()
|
| 12 |
+
|
| 13 |
+
pipe = StableDiffusionInpaintPipeline.from_single_file(
|
| 14 |
+
model_path,
|
| 15 |
+
torch_dtype=torch.float16,
|
| 16 |
+
safety_checker=None
|
| 17 |
+
).to("cuda")
|
| 18 |
+
|
| 19 |
+
# Enable memory optimizations without xformers
|
| 20 |
+
pipe.enable_attention_slicing(slice_size="max")
|
| 21 |
+
pipe.enable_sequential_cpu_offload()
|
| 22 |
+
|
| 23 |
+
return pipe
|
| 24 |
+
|
| 25 |
+
def prepare_images(image_path, mask_path=None):
|
| 26 |
+
# Load and prepare the original image
|
| 27 |
+
original_image = Image.open(image_path)
|
| 28 |
+
# Resize to a multiple of 8 (required by Stable Diffusion)
|
| 29 |
+
width, height = (dim - dim % 8 for dim in original_image.size)
|
| 30 |
+
original_image = original_image.resize((width, height))
|
| 31 |
+
|
| 32 |
+
if mask_path:
|
| 33 |
+
mask_image = Image.open(mask_path)
|
| 34 |
+
mask_image = mask_image.resize((width, height))
|
| 35 |
+
mask_image = mask_image.convert("L")
|
| 36 |
+
else:
|
| 37 |
+
# Create a simple rectangular mask in the center
|
| 38 |
+
mask_image = Image.new("L", (width, height), 0)
|
| 39 |
+
mask_width = width // 3
|
| 40 |
+
mask_height = height // 3
|
| 41 |
+
x1 = (width - mask_width) // 2
|
| 42 |
+
y1 = (height - mask_height) // 2
|
| 43 |
+
x2 = x1 + mask_width
|
| 44 |
+
y2 = y1 + mask_height
|
| 45 |
+
for y in range(y1, y2):
|
| 46 |
+
for x in range(x1, x2):
|
| 47 |
+
mask_image.putpixel((x, y), 255)
|
| 48 |
+
|
| 49 |
+
return original_image, mask_image
|
| 50 |
+
|
| 51 |
+
def main():
|
| 52 |
+
# Setup paths using raw strings
|
| 53 |
+
model_path = "realisticVisionV60B1_v51VAE-inpainting.safetensors"
|
| 54 |
+
image_path = r"C:\Users\M. Y\Downloads\t2.png"
|
| 55 |
+
|
| 56 |
+
print(f"CUDA available: {torch.cuda.is_available()}")
|
| 57 |
+
if torch.cuda.is_available():
|
| 58 |
+
print(f"GPU: {torch.cuda.get_device_name()}")
|
| 59 |
+
print(f"Memory allocated: {torch.cuda.memory_allocated()/1024**2:.2f}MB")
|
| 60 |
+
|
| 61 |
+
# Initialize model
|
| 62 |
+
print("Loading model...")
|
| 63 |
+
pipe = setup_model(model_path)
|
| 64 |
+
|
| 65 |
+
# Prepare images
|
| 66 |
+
print("Preparing images...")
|
| 67 |
+
original_image, mask_image = prepare_images(image_path)
|
| 68 |
+
|
| 69 |
+
# Save mask for verification
|
| 70 |
+
mask_image.save("generated_mask.png")
|
| 71 |
+
|
| 72 |
+
mask_image_1 = Image.open("generated_mask_1.png")
|
| 73 |
+
# Define your prompt
|
| 74 |
+
prompt = "add some flowers and a fountain"
|
| 75 |
+
negative_prompt = "blurry, low quality, distorted"
|
| 76 |
+
|
| 77 |
+
print("Performing inpainting...")
|
| 78 |
+
with torch.cuda.amp.autocast(): # Use automatic mixed precision
|
| 79 |
+
output_image = pipe(
|
| 80 |
+
prompt=prompt,
|
| 81 |
+
negative_prompt=negative_prompt,
|
| 82 |
+
image=original_image,
|
| 83 |
+
mask_image=mask_image_1,
|
| 84 |
+
num_inference_steps=20, # Reduced steps for faster generation
|
| 85 |
+
guidance_scale=7.5,
|
| 86 |
+
).images[0]
|
| 87 |
+
|
| 88 |
+
# Save the result
|
| 89 |
+
output_image.save("inpainted_result.png")
|
| 90 |
+
print("Inpainting completed! Check 'inpainted_result.png' for the result.")
|
| 91 |
+
|
| 92 |
+
# Clean up
|
| 93 |
+
torch.cuda.empty_cache()
|
| 94 |
+
gc.collect()
|
| 95 |
+
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
try:
|
| 98 |
+
set_start_method('spawn')
|
| 99 |
+
except RuntimeError:
|
| 100 |
+
pass
|
| 101 |
+
main()
|
test_app.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi.testclient import TestClient
|
| 2 |
+
from app import app
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
client = TestClient(app)
|
| 6 |
+
|
| 7 |
+
def test_health_check():
|
| 8 |
+
response = client.get("/health")
|
| 9 |
+
assert response.status_code == 200
|
| 10 |
+
assert "status" in response.json()
|