Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions _gsdocs-proposals/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__pycache__/
*.pyc
*.pyo
*.pyd
.env
venv/
.git/
6 changes: 6 additions & 0 deletions _gsdocs-proposals/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HF_TOKEN="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"

SEGMENTATION_MODEL="facebook/mask2former-swin-tiny-ade-semantic"
INPAINTING_MODEL="runwayml/stable-diffusion-inpainting"

DEVICE=cuda
22 changes: 22 additions & 0 deletions _gsdocs-proposals/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.12-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \
git \
ffmpeg \
libgl1 \
libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .

RUN pip install --upgrade pip

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 7860

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
71 changes: 71 additions & 0 deletions _gsdocs-proposals/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os
import io
import torch
import huggingface_hub
import asyncio
import traceback
from fastapi import FastAPI, UploadFile, File, HTTPException
from starlette.responses import StreamingResponse
from PIL import Image
import uvicorn

if not hasattr(torch, 'xpu'):
class XPU:
@staticmethod
def is_available(): return False
@staticmethod
def empty_cache(): pass
torch.xpu = XPU

if not hasattr(huggingface_hub, "cached_download"):
huggingface_hub.cached_download = huggingface_hub.hf_hub_download

os.environ["DIFFUSERS_VERIFY_COMPATIBILITY"] = "0"
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"

from pipeline import reconstruct_artifact

app = FastAPI(title="AI Artifact Reconstruction")

@app.get("/")
async def home():
return {
"status": "online",
"device": "CUDA" if torch.cuda.is_available() else "CPU",
"torch_version": torch.__version__
}

@app.post("/generate")
async def generate(file: UploadFile = File(...)):
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="File must be an image")

content = await file.read()

if len(content) > 5 * 1024 * 1024:
raise HTTPException(status_code=400, detail="File too large")

try:
Image.open(io.BytesIO(content)).verify()
except:
raise HTTPException(status_code=400, detail="Invalid image")

try:
processed_img = await asyncio.wait_for(
asyncio.to_thread(reconstruct_artifact, content),
timeout=60
)

img_byte_arr = io.BytesIO()
processed_img.save(img_byte_arr, format="PNG")
img_byte_arr.seek(0)

return StreamingResponse(img_byte_arr, media_type="image/png")

except Exception as e:
traceback.print_exc()
return {"error": str(e)}

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)
2 changes: 2 additions & 0 deletions _gsdocs-proposals/login_me.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from huggingface_hub import login
login()
101 changes: 101 additions & 0 deletions _gsdocs-proposals/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import os
import torch
import io
import numpy as np
import cv2
from PIL import Image
from dotenv import load_dotenv
from transformers import AutoImageProcessor, Mask2FormerForUniversalSegmentation
from diffusers import StableDiffusionInpaintPipeline

load_dotenv()
HF_TOKEN = os.getenv("HF_TOKEN")

device = "cuda" if torch.cuda.is_available() else "cpu"

seg_id = "facebook/mask2former-swin-tiny-ade-semantic"
processor = AutoImageProcessor.from_pretrained(seg_id, token=HF_TOKEN)
seg_model = Mask2FormerForUniversalSegmentation.from_pretrained(seg_id, token=HF_TOKEN).to(device)

pipe = StableDiffusionInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-inpainting",
token=HF_TOKEN,
torch_dtype=torch.float16 if device == "cuda" else torch.float32
)

if device == "cpu":
pipe.enable_attention_slicing()
else:
pipe.to(device)


def refine_mask(mask):
kernel = np.ones((5, 5), np.uint8)
mask = cv2.GaussianBlur(mask, (5, 5), 0)
mask = cv2.dilate(mask, kernel, iterations=2)
mask = cv2.erode(mask, kernel, iterations=1)
_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
return mask


def detect_missing_regions(init_image):
gray = cv2.cvtColor(np.array(init_image), cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 80, 160)

inputs = processor(images=init_image, return_tensors="pt").to(device)
with torch.no_grad():
outputs = seg_model(**inputs)

prediction = processor.post_process_semantic_segmentation(
outputs, target_sizes=[init_image.size[::-1]]
)[0].cpu().numpy()

seg_mask = np.where(
(prediction == 0) | (prediction == 1) | (prediction == 255),
255,
0
).astype(np.uint8)

combined = cv2.bitwise_or(edges, seg_mask)
combined = refine_mask(combined)

if np.sum(combined) < 3000:
combined = np.ones_like(gray) * 255

return combined


def reconstruct_artifact(image_bytes):
init_image = Image.open(io.BytesIO(image_bytes)).convert("RGB").resize((512, 512))

mask_array = detect_missing_regions(init_image)

coverage = np.sum(mask_array) / (512 * 512 * 255)
if coverage < 0.02:
return init_image

mask_image = Image.fromarray(mask_array)

prompt = (
"infrared underpainting, hidden sketch beneath painting, "
"classical artwork, faded pigments, original composition, "
"historically accurate restoration, museum quality"
)

negative_prompt = (
"modern objects, distorted shapes, unrealistic textures, "
"blurry, oversmoothed, low quality"
)

with torch.inference_mode():
result = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
image=init_image,
mask_image=mask_image,
num_inference_steps=28,
guidance_scale=8.0,
strength=0.65
).images[0]

return result
Loading