yaseengoldfinchpc commited on
Commit
43c5517
·
1 Parent(s): 3beeaa7
Files changed (16) hide show
  1. .env.example +2 -0
  2. .gitignore +13 -0
  3. Dockerfile +26 -0
  4. README.md +8 -11
  5. app.py +108 -0
  6. config.py +8 -0
  7. convertToOnx.py +115 -0
  8. example_client.py +29 -0
  9. generated_mask.png +0 -0
  10. generated_mask_1.png +0 -0
  11. inpainted_result.png +0 -0
  12. model_index.json +7 -0
  13. requirements.txt +11 -0
  14. test.py +92 -0
  15. test2.py +101 -0
  16. 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: Modeltest
3
- emoji: 🌍
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- short_description: ModelTes
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()