Alizmoh98 commited on
Commit
e8e33af
·
1 Parent(s): 2070be5

deploy-app

Browse files
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ # Set working directory
4
+ WORKDIR /code
5
+
6
+ # Copy requirements and install
7
+ COPY ./requirements.txt /code/requirements.txt
8
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
9
+
10
+ # Install system dependencies for OpenCV (YOLO usually needs this)
11
+ RUN apt-get update && apt-get install -y libgl1-mesa-glx
12
+
13
+ # Copy your source code
14
+ COPY ./src /code/src
15
+
16
+ # Create a directory for boxes if it doesn't exist (permissions fix)
17
+ RUN mkdir -p /code/src/boxes && chmod 777 /code/src/boxes
18
+
19
+ # Hugging Face Spaces expects the app to run on port 7860
20
+ CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "7860"]
requirements.txt ADDED
Binary file (4.57 kB). View file
 
src/__pycache__/app.cpython-311.pyc ADDED
Binary file (1.78 kB). View file
 
src/app.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import asynccontextmanager
2
+ from fastapi import FastAPI
3
+ import gradio as gr # <--- 1. Import Gradio
4
+
5
+ from .storage.database import engine
6
+ from .storage.models import Base
7
+ from .routers.parse_router import router as parse_router
8
+ from .ui.gradio_ui import gradio_app # <--- 2. Import your UI object
9
+
10
+ async def create_all_tables():
11
+ async with engine.begin() as conn:
12
+ await conn.run_sync(Base.metadata.create_all)
13
+
14
+ @asynccontextmanager
15
+ async def lifespan(app: FastAPI):
16
+ await create_all_tables()
17
+ yield
18
+
19
+ app = FastAPI(lifespan=lifespan)
20
+
21
+ # Include your API router
22
+ app.include_router(parse_router, prefix="/parse", tags=["parse-image"])
23
+
24
+ # 3. Mount Gradio UI
25
+ # Now your supervisor can visit http://localhost:8000/ui
26
+ #app = gr.mount_gradio_app(app, gradio_app, path="/ui")
27
+ app = gr.mount_gradio_app(app, gradio_app, path="/ui", auth=("irandoc", "12345678"))
src/processing/__init__.py ADDED
File without changes
src/processing/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (170 Bytes). View file
 
src/processing/__pycache__/parse_img.cpython-311.pyc ADDED
Binary file (3.24 kB). View file
 
src/processing/parse_img.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ from huggingface_hub import hf_hub_download
3
+ from doclayout_yolo import YOLOv10
4
+ from ..storage.schemas import BaseBox
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ filepath = hf_hub_download(
9
+ repo_id="juliozhao/DocLayout-YOLO-DocStructBench",
10
+ filename="doclayout_yolo_docstructbench_imgsz1024.pt"
11
+ )
12
+ model = YOLOv10(filepath)
13
+
14
+
15
+ def parse_img(
16
+ img: Image.Image,
17
+ device: str = "cpu",
18
+ box_directory: str = "src/boxes",
19
+ ):
20
+ """
21
+ Processes an image, runs detection, crops boxes, saves their images,
22
+ and returns a list of BaseBox objects with box metadata.
23
+ """
24
+ # Create box directory if it doesn't exist
25
+ Path(box_directory).mkdir(parents=True, exist_ok=True)
26
+
27
+ # Create temp file with delete=False so it stays on disk
28
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
29
+ img.save(temp_file.name, format="PNG")
30
+ img_path = temp_file.name
31
+
32
+ # Now model.predict can access the file
33
+ det_res = model.predict(
34
+ img_path,
35
+ imgsz=1024,
36
+ conf=0.2,
37
+ device=device
38
+ )
39
+
40
+ boxes_data = det_res[0].boxes.data
41
+ boxes_result = []
42
+ crop_image_list = []
43
+ for i, box_data in enumerate(boxes_data):
44
+ box_data = box_data.tolist()
45
+ crop = img.crop(tuple(box_data[:4]))
46
+ box_path = str(Path(box_directory) / f"box_{i}.png")
47
+ crop.save(box_path)
48
+ crop_image_list.append(crop)
49
+
50
+ box_info = BaseBox(
51
+ class_name=int(box_data[-1]),
52
+ x_min=float(box_data[0]),
53
+ y_min=float(box_data[1]),
54
+ x_max=float(box_data[2]),
55
+ y_max=float(box_data[3]),
56
+ confidence=float(box_data[-2]),
57
+ saved_img_path=box_path
58
+ )
59
+ boxes_result.append(box_info)
60
+
61
+ # Clean up temp file
62
+ Path(img_path).unlink(missing_ok=True)
63
+
64
+ return boxes_result, crop_image_list
src/routers/__init__.py ADDED
File without changes
src/routers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (167 Bytes). View file
 
src/routers/__pycache__/parse_router.cpython-311.pyc ADDED
Binary file (1.87 kB). View file
 
src/routers/parse_router.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, status, UploadFile, File
2
+ from ..storage.schemas import BaseBox
3
+ from ..storage.models import BoxesData
4
+ from ..storage.database import get_session, engine
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+ from PIL import Image
7
+ import io
8
+ from ..processing.parse_img import parse_img
9
+
10
+ router = APIRouter()
11
+
12
+ @router.post("/", response_model=list[BaseBox], status_code=status.HTTP_201_CREATED)
13
+ async def parse_image(image_file: UploadFile = File(...), session: AsyncSession = Depends(get_session)):
14
+ contents = await image_file.read()
15
+ img = Image.open(io.BytesIO(contents))
16
+
17
+ boxes_data, _ = parse_img(img)
18
+
19
+ for box_data in boxes_data:
20
+ db_box = BoxesData(**box_data.model_dump())
21
+ session.add(db_box)
22
+ await session.commit()
23
+
24
+ return boxes_data
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
src/storage/__init__.py ADDED
File without changes
src/storage/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (167 Bytes). View file
 
src/storage/__pycache__/database.cpython-311.pyc ADDED
Binary file (1.43 kB). View file
 
src/storage/__pycache__/models.cpython-311.pyc ADDED
Binary file (1.66 kB). View file
 
src/storage/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (1.45 kB). View file
 
src/storage/crud.py ADDED
File without changes
src/storage/database.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
2
+ from pathlib import Path
3
+ from collections.abc import AsyncGenerator
4
+ # Create data directory if it doesn't exist
5
+ DATA_DIR = Path("./data/database")
6
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
7
+
8
+ DATABASE_URL = "sqlite+aiosqlite:///./data/database/ocr_results.db"
9
+
10
+ engine = create_async_engine(
11
+ DATABASE_URL,
12
+ echo=False
13
+ )
14
+
15
+ async_session_maker = async_sessionmaker(
16
+ bind=engine,
17
+ class_=AsyncSession,
18
+ expire_on_commit=False
19
+ )
20
+
21
+
22
+ async def get_session() -> AsyncGenerator[AsyncSession]:
23
+ """Dependency to get async database session."""
24
+ async with async_session_maker() as session:
25
+ yield session
src/storage/models.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Integer, Float, Text
2
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
3
+
4
+ class Base(DeclarativeBase):
5
+ pass
6
+
7
+ class BoxesData(Base):
8
+ __tablename__= "croped_boxes_metadata"
9
+
10
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
11
+ class_name: Mapped[int] = mapped_column(Integer)
12
+
13
+ x_min: Mapped[float] = mapped_column(Float)
14
+ y_min: Mapped[float] = mapped_column(Float)
15
+ x_max: Mapped[float] = mapped_column(Float)
16
+ y_max: Mapped[float] = mapped_column(Float)
17
+
18
+ confidence: Mapped[float] = mapped_column(Float)
19
+ saved_img_path: Mapped[str] = mapped_column(Text)
20
+
21
+
22
+
23
+
src/storage/schemas.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+ class BaseBox(BaseModel):
4
+ class_name: int = Field(..., description="Integers that each show type of box")
5
+ x_min: float = Field(..., description="X-coordinate of the top-left corner.")
6
+ y_min: float = Field(..., description="Y-coordinate of the top-left corner.")
7
+ x_max: float = Field(..., description="X-coordinate of the bottom-right corner.")
8
+ y_max: float = Field(..., description="Y-coordinate of the bottom-right corner.")
9
+ confidence: float
10
+ saved_img_path: str
11
+
12
+ class Config:
13
+ from_attributes = True
src/ui/__pycache__/gradio_ui.cpython-311.pyc ADDED
Binary file (1.48 kB). View file
 
src/ui/gradio_ui.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from ..processing.parse_img import parse_img
4
+
5
+ def ui_predict_fn(image):
6
+ # Wrapper to format data specifically for Gradio Gallery
7
+ _, gallery_items = parse_img(image)
8
+ return gallery_items
9
+
10
+ # Define the Interface/Blocks
11
+ with gr.Blocks(title="DocLayout Parser") as gradio_app:
12
+ gr.Markdown("## Supervisor Dashboard")
13
+ with gr.Row():
14
+ input_img = gr.Image(type="pil", label="Upload Document")
15
+ output_gal = gr.Gallery(label="Parsed Regions")
16
+
17
+ btn = gr.Button("Run Analysis", variant="primary")
18
+ btn.click(fn=ui_predict_fn, inputs=input_img, outputs=output_gal)