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()
|