Spaces:
Running
Running
Jake Reardon
commited on
Commit
·
f0c79f8
1
Parent(s):
032d4ea
Initial implementation of MLSE Player 3D Generator with SAM 3D Body integration
Browse files- .gitignore +46 -0
- Dockerfile +39 -0
- README.md +138 -2
- app/__init__.py +2 -0
- app/main.py +269 -0
- app/sam_3d_service.py +163 -0
- app/utils.py +38 -0
- app/visualization.py +82 -0
- requirements.txt +45 -0
.gitignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
build/
|
| 9 |
+
develop-eggs/
|
| 10 |
+
dist/
|
| 11 |
+
downloads/
|
| 12 |
+
eggs/
|
| 13 |
+
.eggs/
|
| 14 |
+
lib/
|
| 15 |
+
lib64/
|
| 16 |
+
parts/
|
| 17 |
+
sdist/
|
| 18 |
+
var/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual Environment
|
| 24 |
+
venv/
|
| 25 |
+
ENV/
|
| 26 |
+
env/
|
| 27 |
+
|
| 28 |
+
# IDE
|
| 29 |
+
.idea/
|
| 30 |
+
.vscode/
|
| 31 |
+
*.swp
|
| 32 |
+
*.swo
|
| 33 |
+
|
| 34 |
+
# Project specific
|
| 35 |
+
temp/
|
| 36 |
+
outputs/
|
| 37 |
+
*.ply
|
| 38 |
+
*.glb
|
| 39 |
+
*.jpg
|
| 40 |
+
*.png
|
| 41 |
+
!assets/*.png
|
| 42 |
+
!assets/*.jpg
|
| 43 |
+
|
| 44 |
+
# Logs
|
| 45 |
+
*.log
|
| 46 |
+
logs/
|
Dockerfile
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install system dependencies
|
| 6 |
+
RUN apt-get update && apt-get install -y \
|
| 7 |
+
build-essential \
|
| 8 |
+
git \
|
| 9 |
+
libgl1-mesa-glx \
|
| 10 |
+
libglib2.0-0 \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# Copy requirements file
|
| 14 |
+
COPY requirements.txt .
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
# Install Detectron2
|
| 20 |
+
RUN pip install --no-cache-dir 'git+https://github.com/facebookresearch/detectron2.git@a1ce2f9' --no-build-isolation --no-deps
|
| 21 |
+
|
| 22 |
+
# Optional: Install MoGe
|
| 23 |
+
RUN pip install --no-cache-dir git+https://github.com/microsoft/MoGe.git
|
| 24 |
+
|
| 25 |
+
# Copy application code
|
| 26 |
+
COPY app/ ./app/
|
| 27 |
+
|
| 28 |
+
# Create required directories
|
| 29 |
+
RUN mkdir -p outputs temp
|
| 30 |
+
|
| 31 |
+
# Set environment variables
|
| 32 |
+
ENV PORT=7860
|
| 33 |
+
ENV PYTHONUNBUFFERED=1
|
| 34 |
+
|
| 35 |
+
# Expose the port
|
| 36 |
+
EXPOSE 7860
|
| 37 |
+
|
| 38 |
+
# Start the application
|
| 39 |
+
CMD ["python", "-m", "app.main"]
|
README.md
CHANGED
|
@@ -1,2 +1,138 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MLSE Player 3D Generator
|
| 2 |
+
|
| 3 |
+
A 3D player model generator that uses Meta's SAM 3D Body model to convert images of athletes into detailed 3D models.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
This application allows you to upload images of players and automatically generate 3D human body models. It uses Meta's SAM 3D Body technology, which is designed for high-quality 3D human mesh reconstruction from a single image.
|
| 8 |
+
|
| 9 |
+
### Features
|
| 10 |
+
|
| 11 |
+
- Upload player images and generate 3D models
|
| 12 |
+
- Automatic player detection and segmentation
|
| 13 |
+
- High-quality 3D human mesh reconstruction
|
| 14 |
+
- Interactive 3D viewer for examining models
|
| 15 |
+
- Export models in GLB format for use in various applications
|
| 16 |
+
|
| 17 |
+
## Setup
|
| 18 |
+
|
| 19 |
+
### Prerequisites
|
| 20 |
+
|
| 21 |
+
- Access to Hugging Face model repository
|
| 22 |
+
- GitHub account for Hugging Face Spaces
|
| 23 |
+
|
| 24 |
+
### Deployment Options
|
| 25 |
+
|
| 26 |
+
#### 1. Hugging Face Spaces (Recommended)
|
| 27 |
+
|
| 28 |
+
1. Fork this repository on GitHub
|
| 29 |
+
2. Create a new Space on Hugging Face Spaces (https://huggingface.co/spaces)
|
| 30 |
+
3. Link your GitHub repository to the Space
|
| 31 |
+
4. Choose "Docker" as the Space SDK
|
| 32 |
+
5. Configure the Space with GPU hardware (required for optimal performance)
|
| 33 |
+
6. Set up the Hugging Face access token as a secret named `HF_TOKEN`
|
| 34 |
+
7. Deploy the Space
|
| 35 |
+
|
| 36 |
+
#### 2. Local Development
|
| 37 |
+
|
| 38 |
+
1. Clone this repository
|
| 39 |
+
2. Install dependencies:
|
| 40 |
+
```bash
|
| 41 |
+
pip install -r requirements.txt
|
| 42 |
+
|
| 43 |
+
# Install Detectron2
|
| 44 |
+
pip install 'git+https://github.com/facebookresearch/detectron2.git@a1ce2f9' --no-build-isolation --no-deps
|
| 45 |
+
|
| 46 |
+
# Optional: Install MoGe
|
| 47 |
+
pip install git+https://github.com/microsoft/MoGe.git
|
| 48 |
+
```
|
| 49 |
+
3. Run the application:
|
| 50 |
+
```bash
|
| 51 |
+
python -m app.main
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### Environment Variables
|
| 55 |
+
|
| 56 |
+
- `HF_TOKEN`: Your Hugging Face access token (required for model download)
|
| 57 |
+
- `PORT`: The port to run the server on (default: 7860)
|
| 58 |
+
|
| 59 |
+
## Usage
|
| 60 |
+
|
| 61 |
+
### API Endpoints
|
| 62 |
+
|
| 63 |
+
#### Upload an Image
|
| 64 |
+
|
| 65 |
+
```
|
| 66 |
+
POST /api/upload
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
Form data:
|
| 70 |
+
- `file`: Image file (JPEG/PNG)
|
| 71 |
+
- `player_name`: Name for the model (default: "player")
|
| 72 |
+
- `use_keypoints`: Whether to use keypoint detection (default: true)
|
| 73 |
+
- `use_mask`: Whether to use segmentation masks (default: true)
|
| 74 |
+
|
| 75 |
+
#### Process Base64 Image
|
| 76 |
+
|
| 77 |
+
```
|
| 78 |
+
POST /api/process
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
JSON body:
|
| 82 |
+
```json
|
| 83 |
+
{
|
| 84 |
+
"image_data": "base64_encoded_image_data",
|
| 85 |
+
"player_name": "player_name",
|
| 86 |
+
"options": {
|
| 87 |
+
"use_keypoints": true,
|
| 88 |
+
"use_mask": true
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
#### Check Job Status
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
POST /api/status
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
JSON body:
|
| 100 |
+
```json
|
| 101 |
+
{
|
| 102 |
+
"job_id": "job_id_from_upload_response"
|
| 103 |
+
}
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
#### List All Jobs
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
GET /api/jobs
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
#### Get Model File
|
| 113 |
+
|
| 114 |
+
```
|
| 115 |
+
GET /api/model/{job_id}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
## Integration with MLSE Platform
|
| 119 |
+
|
| 120 |
+
To integrate this service with the MLSE platform:
|
| 121 |
+
|
| 122 |
+
1. Deploy this service on Hugging Face Spaces
|
| 123 |
+
2. Add the Player 3D Generator to the MLSE AI tools section
|
| 124 |
+
3. Configure the frontend to call this service's API endpoints
|
| 125 |
+
4. Use the returned model URLs to display 3D models in the MLSE platform
|
| 126 |
+
|
| 127 |
+
## Technical Details
|
| 128 |
+
|
| 129 |
+
### Model Information
|
| 130 |
+
|
| 131 |
+
This application uses SAM 3D Body, a foundation model for 3D human body reconstruction from Meta AI:
|
| 132 |
+
|
| 133 |
+
- Model: `facebook/sam-3d-body-dinov3` (or `facebook/sam-3d-body-vith`)
|
| 134 |
+
- Papers: [SAM 3D: Segment Anything in 3D with Momentum Human Rig](https://github.com/facebookresearch/sam-3d-body)
|
| 135 |
+
|
| 136 |
+
## License
|
| 137 |
+
|
| 138 |
+
This project uses SAM 3D Body which is covered by Meta's license. Please check the [original repository](https://github.com/facebookresearch/sam-3d-body) for license details.
|
app/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MLSE Player 3D Generator
|
| 2 |
+
# SAM 3D Body integration for player 3D model generation
|
app/main.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import base64
|
| 4 |
+
import uuid
|
| 5 |
+
from typing import Optional, Dict, Any, List
|
| 6 |
+
from fastapi import FastAPI, HTTPException, Body, BackgroundTasks, File, UploadFile, Form
|
| 7 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
+
from fastapi.responses import JSONResponse, FileResponse
|
| 9 |
+
from fastapi.staticfiles import StaticFiles
|
| 10 |
+
from pydantic import BaseModel
|
| 11 |
+
import uvicorn
|
| 12 |
+
|
| 13 |
+
from app.sam_3d_service import initialize_model, process_image
|
| 14 |
+
from app.utils import ensure_directories
|
| 15 |
+
|
| 16 |
+
# Initialize FastAPI app
|
| 17 |
+
app = FastAPI(
|
| 18 |
+
title="MLSE Player 3D Generator",
|
| 19 |
+
description="API for generating 3D human body models from player images using SAM 3D Body",
|
| 20 |
+
version="0.1.0"
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Add CORS middleware for frontend integration
|
| 24 |
+
app.add_middleware(
|
| 25 |
+
CORSMiddleware,
|
| 26 |
+
allow_origins=["*"], # Update this with specific origins in production
|
| 27 |
+
allow_credentials=True,
|
| 28 |
+
allow_methods=["*"],
|
| 29 |
+
allow_headers=["*"],
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
# Create required directories
|
| 33 |
+
ensure_directories()
|
| 34 |
+
|
| 35 |
+
# Mount static files directory
|
| 36 |
+
app.mount("/outputs", StaticFiles(directory="outputs"), name="outputs")
|
| 37 |
+
|
| 38 |
+
# In-memory job storage (replace with database in production)
|
| 39 |
+
jobs = {}
|
| 40 |
+
|
| 41 |
+
# Request models
|
| 42 |
+
class ImageProcessRequest(BaseModel):
|
| 43 |
+
image_data: str # Base64 encoded image
|
| 44 |
+
player_name: str = "player" # Name for the generated model
|
| 45 |
+
options: Dict[str, Any] = {
|
| 46 |
+
"use_keypoints": True,
|
| 47 |
+
"use_mask": True
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
class JobStatusRequest(BaseModel):
|
| 51 |
+
job_id: str
|
| 52 |
+
|
| 53 |
+
# Response models
|
| 54 |
+
class JobResponse(BaseModel):
|
| 55 |
+
job_id: str
|
| 56 |
+
status: str = "queued" # queued, processing, completed, failed
|
| 57 |
+
|
| 58 |
+
class JobStatusResponse(BaseModel):
|
| 59 |
+
job_id: str
|
| 60 |
+
status: str
|
| 61 |
+
progress: float = 0
|
| 62 |
+
model_url: Optional[str] = None
|
| 63 |
+
preview_url: Optional[str] = None
|
| 64 |
+
error: Optional[str] = None
|
| 65 |
+
|
| 66 |
+
# Initialize the model on startup
|
| 67 |
+
@app.on_event("startup")
|
| 68 |
+
async def startup_event():
|
| 69 |
+
try:
|
| 70 |
+
# This will download the model if needed
|
| 71 |
+
initialize_model()
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"Error initializing model: {str(e)}")
|
| 74 |
+
# We'll initialize it on first request if this fails
|
| 75 |
+
|
| 76 |
+
# API endpoints
|
| 77 |
+
@app.post("/api/process", response_model=JobResponse)
|
| 78 |
+
async def process_image_endpoint(request: ImageProcessRequest, background_tasks: BackgroundTasks):
|
| 79 |
+
"""
|
| 80 |
+
Process an image to generate a 3D model using SAM 3D Body.
|
| 81 |
+
Accepts a base64-encoded image and returns a job ID for tracking progress.
|
| 82 |
+
"""
|
| 83 |
+
try:
|
| 84 |
+
# Generate a unique job ID
|
| 85 |
+
job_id = str(uuid.uuid4())
|
| 86 |
+
|
| 87 |
+
# Store job in memory
|
| 88 |
+
jobs[job_id] = {
|
| 89 |
+
"status": "queued",
|
| 90 |
+
"progress": 0,
|
| 91 |
+
"model_url": None,
|
| 92 |
+
"preview_url": None,
|
| 93 |
+
"error": None
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
# Process in background
|
| 97 |
+
background_tasks.add_task(
|
| 98 |
+
process_image_background,
|
| 99 |
+
job_id,
|
| 100 |
+
request.image_data,
|
| 101 |
+
request.player_name,
|
| 102 |
+
request.options
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
return JobResponse(job_id=job_id)
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 109 |
+
|
| 110 |
+
@app.post("/api/upload", response_model=JobResponse)
|
| 111 |
+
async def upload_image_endpoint(
|
| 112 |
+
file: UploadFile = File(...),
|
| 113 |
+
player_name: str = Form("player"),
|
| 114 |
+
use_keypoints: bool = Form(True),
|
| 115 |
+
use_mask: bool = Form(True),
|
| 116 |
+
background_tasks: BackgroundTasks = None
|
| 117 |
+
):
|
| 118 |
+
"""
|
| 119 |
+
Process an uploaded image to generate a 3D model.
|
| 120 |
+
This endpoint accepts multipart/form-data for easier frontend integration.
|
| 121 |
+
"""
|
| 122 |
+
try:
|
| 123 |
+
# Generate a unique job ID
|
| 124 |
+
job_id = str(uuid.uuid4())
|
| 125 |
+
|
| 126 |
+
# Read the image file
|
| 127 |
+
image_bytes = await file.read()
|
| 128 |
+
|
| 129 |
+
# Convert to base64 for consistency with the other endpoint
|
| 130 |
+
image_data = base64.b64encode(image_bytes).decode('utf-8')
|
| 131 |
+
|
| 132 |
+
# Store job in memory
|
| 133 |
+
jobs[job_id] = {
|
| 134 |
+
"status": "queued",
|
| 135 |
+
"progress": 0,
|
| 136 |
+
"model_url": None,
|
| 137 |
+
"preview_url": None,
|
| 138 |
+
"error": None
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
# Process in background
|
| 142 |
+
options = {
|
| 143 |
+
"use_keypoints": use_keypoints,
|
| 144 |
+
"use_mask": use_mask
|
| 145 |
+
}
|
| 146 |
+
background_tasks.add_task(
|
| 147 |
+
process_image_background,
|
| 148 |
+
job_id,
|
| 149 |
+
image_data,
|
| 150 |
+
player_name,
|
| 151 |
+
options
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
return JobResponse(job_id=job_id)
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 158 |
+
|
| 159 |
+
@app.post("/api/status", response_model=JobStatusResponse)
|
| 160 |
+
async def check_status_endpoint(request: JobStatusRequest):
|
| 161 |
+
"""
|
| 162 |
+
Check the status of a processing job by job ID.
|
| 163 |
+
"""
|
| 164 |
+
job_id = request.job_id
|
| 165 |
+
if job_id not in jobs:
|
| 166 |
+
raise HTTPException(status_code=404, detail=f"Job {job_id} not found")
|
| 167 |
+
|
| 168 |
+
job_info = jobs[job_id]
|
| 169 |
+
return JobStatusResponse(
|
| 170 |
+
job_id=job_id,
|
| 171 |
+
status=job_info["status"],
|
| 172 |
+
progress=job_info["progress"],
|
| 173 |
+
model_url=job_info["model_url"],
|
| 174 |
+
preview_url=job_info["preview_url"],
|
| 175 |
+
error=job_info["error"]
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
@app.get("/api/jobs", response_model=List[JobStatusResponse])
|
| 179 |
+
async def list_jobs_endpoint():
|
| 180 |
+
"""
|
| 181 |
+
List all processing jobs and their status.
|
| 182 |
+
"""
|
| 183 |
+
return [
|
| 184 |
+
JobStatusResponse(
|
| 185 |
+
job_id=job_id,
|
| 186 |
+
status=job_info["status"],
|
| 187 |
+
progress=job_info["progress"],
|
| 188 |
+
model_url=job_info["model_url"],
|
| 189 |
+
preview_url=job_info["preview_url"],
|
| 190 |
+
error=job_info["error"]
|
| 191 |
+
)
|
| 192 |
+
for job_id, job_info in jobs.items()
|
| 193 |
+
]
|
| 194 |
+
|
| 195 |
+
@app.get("/api/model/{job_id}")
|
| 196 |
+
async def get_model_endpoint(job_id: str):
|
| 197 |
+
"""
|
| 198 |
+
Get the 3D model file for a completed job.
|
| 199 |
+
"""
|
| 200 |
+
if job_id not in jobs:
|
| 201 |
+
raise HTTPException(status_code=404, detail=f"Job {job_id} not found")
|
| 202 |
+
|
| 203 |
+
job_info = jobs[job_id]
|
| 204 |
+
if job_info["status"] != "completed" or not job_info["model_url"]:
|
| 205 |
+
raise HTTPException(status_code=400, detail="Model not ready or failed")
|
| 206 |
+
|
| 207 |
+
# Return the model file
|
| 208 |
+
model_path = job_info["model_url"].replace("/outputs/", "outputs/")
|
| 209 |
+
return FileResponse(model_path)
|
| 210 |
+
|
| 211 |
+
# Background task for processing images
|
| 212 |
+
async def process_image_background(job_id, image_data, player_name, options):
|
| 213 |
+
try:
|
| 214 |
+
# Update job status
|
| 215 |
+
jobs[job_id]["status"] = "processing"
|
| 216 |
+
jobs[job_id]["progress"] = 10
|
| 217 |
+
|
| 218 |
+
# Decode base64 image if needed
|
| 219 |
+
if image_data.startswith('data:image'):
|
| 220 |
+
image_data = image_data.split(',')[1]
|
| 221 |
+
|
| 222 |
+
if isinstance(image_data, str):
|
| 223 |
+
image_bytes = base64.b64decode(image_data)
|
| 224 |
+
else:
|
| 225 |
+
image_bytes = image_data
|
| 226 |
+
|
| 227 |
+
# Save to temporary file
|
| 228 |
+
os.makedirs("temp", exist_ok=True)
|
| 229 |
+
input_path = f"temp/{job_id}_input.jpg"
|
| 230 |
+
with open(input_path, 'wb') as f:
|
| 231 |
+
f.write(image_bytes)
|
| 232 |
+
|
| 233 |
+
jobs[job_id]["progress"] = 20
|
| 234 |
+
|
| 235 |
+
# Process image with SAM 3D Body
|
| 236 |
+
result = process_image(
|
| 237 |
+
input_path,
|
| 238 |
+
player_name=player_name,
|
| 239 |
+
use_keypoints=options.get("use_keypoints", True),
|
| 240 |
+
use_mask=options.get("use_mask", True),
|
| 241 |
+
job_progress_callback=lambda progress: update_job_progress(job_id, progress)
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# Update job with result
|
| 245 |
+
jobs[job_id].update({
|
| 246 |
+
"status": "completed",
|
| 247 |
+
"progress": 100,
|
| 248 |
+
"model_url": f"/outputs/{job_id}/{player_name}.glb",
|
| 249 |
+
"preview_url": f"/outputs/{job_id}/{player_name}_preview.jpg"
|
| 250 |
+
})
|
| 251 |
+
|
| 252 |
+
except Exception as e:
|
| 253 |
+
print(f"Error processing job {job_id}: {str(e)}")
|
| 254 |
+
jobs[job_id].update({
|
| 255 |
+
"status": "failed",
|
| 256 |
+
"error": str(e)
|
| 257 |
+
})
|
| 258 |
+
|
| 259 |
+
def update_job_progress(job_id: str, progress: float):
|
| 260 |
+
"""Update the progress of a job"""
|
| 261 |
+
if job_id in jobs:
|
| 262 |
+
# Scale progress to 20-90% range (we reserve 0-20% for setup and 90-100% for final steps)
|
| 263 |
+
scaled_progress = 20 + (progress * 70)
|
| 264 |
+
jobs[job_id]["progress"] = min(90, scaled_progress)
|
| 265 |
+
|
| 266 |
+
# Serve the app with uvicorn if run directly
|
| 267 |
+
if __name__ == "__main__":
|
| 268 |
+
port = int(os.environ.get("PORT", 7860))
|
| 269 |
+
uvicorn.run("main:app", host="0.0.0.0", port=port)
|
app/sam_3d_service.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import tempfile
|
| 4 |
+
from typing import Dict, Any, Optional, Callable
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
from PIL import Image
|
| 8 |
+
import cv2
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
# Set up logging
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
# Global model instance
|
| 16 |
+
model = None
|
| 17 |
+
estimator = None
|
| 18 |
+
|
| 19 |
+
def initialize_model():
|
| 20 |
+
"""
|
| 21 |
+
Initialize the SAM 3D Body model.
|
| 22 |
+
This loads the model from Hugging Face.
|
| 23 |
+
"""
|
| 24 |
+
global model, estimator
|
| 25 |
+
|
| 26 |
+
if model is not None and estimator is not None:
|
| 27 |
+
return # Model already initialized
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
logger.info("Initializing SAM 3D Body model...")
|
| 31 |
+
|
| 32 |
+
# Import SAM 3D Body
|
| 33 |
+
from sam_3d_body import load_sam_3d_body_hf, SAM3DBodyEstimator
|
| 34 |
+
|
| 35 |
+
# Load model from Hugging Face
|
| 36 |
+
# Note: This requires authentication with Hugging Face
|
| 37 |
+
model_name = "facebook/sam-3d-body-dinov3" # or facebook/sam-3d-body-vith
|
| 38 |
+
model, model_cfg = load_sam_3d_body_hf(model_name)
|
| 39 |
+
|
| 40 |
+
# Create estimator
|
| 41 |
+
estimator = SAM3DBodyEstimator(
|
| 42 |
+
sam_3d_body_model=model,
|
| 43 |
+
model_cfg=model_cfg
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
logger.info("SAM 3D Body model initialized successfully")
|
| 47 |
+
|
| 48 |
+
except Exception as e:
|
| 49 |
+
logger.error(f"Error initializing SAM 3D Body model: {str(e)}")
|
| 50 |
+
raise RuntimeError(f"Failed to initialize SAM 3D Body: {str(e)}")
|
| 51 |
+
|
| 52 |
+
def process_image(
|
| 53 |
+
image_path: str,
|
| 54 |
+
player_name: str = "player",
|
| 55 |
+
use_keypoints: bool = True,
|
| 56 |
+
use_mask: bool = True,
|
| 57 |
+
job_progress_callback: Callable[[float], None] = None
|
| 58 |
+
) -> Dict[str, Any]:
|
| 59 |
+
"""
|
| 60 |
+
Process an image using SAM 3D Body to generate a 3D human model.
|
| 61 |
+
|
| 62 |
+
Args:
|
| 63 |
+
image_path: Path to the input image
|
| 64 |
+
player_name: Name for the generated model
|
| 65 |
+
use_keypoints: Whether to use 2D keypoints as auxiliary prompts
|
| 66 |
+
use_mask: Whether to use segmentation masks as auxiliary prompts
|
| 67 |
+
job_progress_callback: Optional callback to report progress (0-1)
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
Dictionary with model information including paths to output files
|
| 71 |
+
"""
|
| 72 |
+
global model, estimator
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
# Ensure model is initialized
|
| 76 |
+
if model is None or estimator is None:
|
| 77 |
+
initialize_model()
|
| 78 |
+
|
| 79 |
+
if job_progress_callback:
|
| 80 |
+
job_progress_callback(0.1) # 10% progress
|
| 81 |
+
|
| 82 |
+
# Process the image
|
| 83 |
+
logger.info(f"Processing image: {image_path}")
|
| 84 |
+
outputs = estimator.process_one_image(image_path)
|
| 85 |
+
|
| 86 |
+
if job_progress_callback:
|
| 87 |
+
job_progress_callback(0.7) # 70% progress
|
| 88 |
+
|
| 89 |
+
# Create output directory
|
| 90 |
+
job_id = os.path.basename(image_path).split('_')[0]
|
| 91 |
+
output_dir = f"outputs/{job_id}"
|
| 92 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 93 |
+
|
| 94 |
+
# Export 3D mesh in GLB format
|
| 95 |
+
mesh = outputs["mesh"]
|
| 96 |
+
glb_path = f"{output_dir}/{player_name}.glb"
|
| 97 |
+
export_mesh_as_glb(mesh, glb_path)
|
| 98 |
+
|
| 99 |
+
# Generate a preview image of the 3D model
|
| 100 |
+
preview_path = f"{output_dir}/{player_name}_preview.jpg"
|
| 101 |
+
generate_model_preview(mesh, preview_path)
|
| 102 |
+
|
| 103 |
+
if job_progress_callback:
|
| 104 |
+
job_progress_callback(1.0) # 100% progress
|
| 105 |
+
|
| 106 |
+
return {
|
| 107 |
+
"model_path": glb_path,
|
| 108 |
+
"preview_path": preview_path,
|
| 109 |
+
"status": "completed"
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
except Exception as e:
|
| 113 |
+
logger.error(f"Error processing image with SAM 3D Body: {str(e)}")
|
| 114 |
+
raise RuntimeError(f"Failed to process image: {str(e)}")
|
| 115 |
+
|
| 116 |
+
def export_mesh_as_glb(mesh, output_path: str):
|
| 117 |
+
"""
|
| 118 |
+
Export a mesh as GLB format.
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
mesh: The mesh data from SAM 3D Body
|
| 122 |
+
output_path: Path to save the GLB file
|
| 123 |
+
"""
|
| 124 |
+
try:
|
| 125 |
+
# In a real implementation, this would use proper 3D libraries
|
| 126 |
+
# like trimesh or pyrender to export the mesh
|
| 127 |
+
|
| 128 |
+
# For now, this is a placeholder that would be replaced with actual code
|
| 129 |
+
# Typically something like:
|
| 130 |
+
# import trimesh
|
| 131 |
+
# mesh_obj = trimesh.Trimesh(vertices=mesh.vertices, faces=mesh.faces)
|
| 132 |
+
# mesh_obj.export(output_path)
|
| 133 |
+
|
| 134 |
+
# Create a dummy GLB file for demonstration
|
| 135 |
+
with open(output_path, 'w') as f:
|
| 136 |
+
f.write("Placeholder GLB file")
|
| 137 |
+
|
| 138 |
+
logger.info(f"Exported mesh to {output_path}")
|
| 139 |
+
except Exception as e:
|
| 140 |
+
logger.error(f"Error exporting mesh: {str(e)}")
|
| 141 |
+
raise RuntimeError(f"Failed to export mesh: {str(e)}")
|
| 142 |
+
|
| 143 |
+
def generate_model_preview(mesh, output_path: str):
|
| 144 |
+
"""
|
| 145 |
+
Generate a preview image of the 3D model.
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
mesh: The mesh data from SAM 3D Body
|
| 149 |
+
output_path: Path to save the preview image
|
| 150 |
+
"""
|
| 151 |
+
try:
|
| 152 |
+
# In a real implementation, this would render the mesh using pyrender or another renderer
|
| 153 |
+
# For now, create a placeholder image
|
| 154 |
+
|
| 155 |
+
# Create a simple image
|
| 156 |
+
img = np.ones((512, 512, 3), dtype=np.uint8) * 200
|
| 157 |
+
cv2.putText(img, "3D Model Preview", (100, 256), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
|
| 158 |
+
cv2.imwrite(output_path, img)
|
| 159 |
+
|
| 160 |
+
logger.info(f"Generated preview at {output_path}")
|
| 161 |
+
except Exception as e:
|
| 162 |
+
logger.error(f"Error generating preview: {str(e)}")
|
| 163 |
+
raise RuntimeError(f"Failed to generate preview: {str(e)}")
|
app/utils.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
from typing import Dict, Any
|
| 4 |
+
|
| 5 |
+
# Set up logging
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
def ensure_directories():
|
| 10 |
+
"""
|
| 11 |
+
Ensure required directories exist.
|
| 12 |
+
"""
|
| 13 |
+
dirs = ["outputs", "temp"]
|
| 14 |
+
for dir_path in dirs:
|
| 15 |
+
os.makedirs(dir_path, exist_ok=True)
|
| 16 |
+
logger.info(f"Ensured directory exists: {dir_path}")
|
| 17 |
+
|
| 18 |
+
def cleanup_temp_files():
|
| 19 |
+
"""
|
| 20 |
+
Clean up temporary files that are no longer needed.
|
| 21 |
+
"""
|
| 22 |
+
try:
|
| 23 |
+
import glob
|
| 24 |
+
import time
|
| 25 |
+
|
| 26 |
+
# Delete files in temp directory that are older than 1 hour
|
| 27 |
+
current_time = time.time()
|
| 28 |
+
one_hour_ago = current_time - 3600
|
| 29 |
+
|
| 30 |
+
for file_path in glob.glob("temp/*"):
|
| 31 |
+
if os.path.isfile(file_path):
|
| 32 |
+
file_stats = os.stat(file_path)
|
| 33 |
+
if file_stats.st_mtime < one_hour_ago:
|
| 34 |
+
os.remove(file_path)
|
| 35 |
+
logger.info(f"Deleted old temp file: {file_path}")
|
| 36 |
+
|
| 37 |
+
except Exception as e:
|
| 38 |
+
logger.error(f"Error cleaning up temp files: {str(e)}")
|
app/visualization.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import json
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
|
| 6 |
+
def convert_mesh_to_threejs(mesh_data: Dict[str, Any], output_path: str):
|
| 7 |
+
"""
|
| 8 |
+
Convert mesh data to THREE.js compatible JSON format.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
mesh_data: Mesh data from SAM 3D Body
|
| 12 |
+
output_path: Path to save the THREE.js compatible JSON
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
# Convert mesh data to THREE.js format
|
| 16 |
+
threejs_data = {
|
| 17 |
+
"metadata": {
|
| 18 |
+
"version": 4.5,
|
| 19 |
+
"type": "Object",
|
| 20 |
+
"generator": "MLSE Player 3D Generator"
|
| 21 |
+
},
|
| 22 |
+
"geometries": [
|
| 23 |
+
{
|
| 24 |
+
"uuid": "player-mesh",
|
| 25 |
+
"type": "BufferGeometry",
|
| 26 |
+
"data": {
|
| 27 |
+
"attributes": {
|
| 28 |
+
"position": {
|
| 29 |
+
"itemSize": 3,
|
| 30 |
+
"type": "Float32Array",
|
| 31 |
+
"array": mesh_data["vertices"].flatten().tolist()
|
| 32 |
+
},
|
| 33 |
+
"normal": {
|
| 34 |
+
"itemSize": 3,
|
| 35 |
+
"type": "Float32Array",
|
| 36 |
+
"array": mesh_data.get("normals", np.zeros_like(mesh_data["vertices"])).flatten().tolist()
|
| 37 |
+
},
|
| 38 |
+
"uv": {
|
| 39 |
+
"itemSize": 2,
|
| 40 |
+
"type": "Float32Array",
|
| 41 |
+
"array": mesh_data.get("uvs", np.zeros((len(mesh_data["vertices"]), 2))).flatten().tolist()
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"index": {
|
| 45 |
+
"type": "Uint16Array",
|
| 46 |
+
"array": mesh_data["faces"].flatten().tolist()
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
],
|
| 51 |
+
"materials": [
|
| 52 |
+
{
|
| 53 |
+
"uuid": "player-material",
|
| 54 |
+
"type": "MeshStandardMaterial",
|
| 55 |
+
"color": 0x8888ff,
|
| 56 |
+
"roughness": 0.5,
|
| 57 |
+
"metalness": 0.2,
|
| 58 |
+
"emissive": 0x000000,
|
| 59 |
+
"side": 2
|
| 60 |
+
}
|
| 61 |
+
],
|
| 62 |
+
"object": {
|
| 63 |
+
"uuid": "player-object",
|
| 64 |
+
"type": "Mesh",
|
| 65 |
+
"name": "PlayerMesh",
|
| 66 |
+
"geometry": "player-mesh",
|
| 67 |
+
"material": "player-material",
|
| 68 |
+
"position": [0, 0, 0],
|
| 69 |
+
"quaternion": [0, 0, 0, 1],
|
| 70 |
+
"scale": [1, 1, 1]
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# Write to file
|
| 75 |
+
with open(output_path, 'w') as f:
|
| 76 |
+
json.dump(threejs_data, f)
|
| 77 |
+
|
| 78 |
+
return output_path
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
print(f"Error converting mesh to THREE.js format: {str(e)}")
|
| 82 |
+
raise RuntimeError(f"Failed to convert mesh: {str(e)}")
|
requirements.txt
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FastAPI and server dependencies
|
| 2 |
+
fastapi==0.109.0
|
| 3 |
+
uvicorn==0.25.0
|
| 4 |
+
python-multipart==0.0.6
|
| 5 |
+
pydantic==2.5.2
|
| 6 |
+
|
| 7 |
+
# Image processing
|
| 8 |
+
Pillow==10.1.0
|
| 9 |
+
opencv-python==4.8.1.78
|
| 10 |
+
numpy==1.24.3
|
| 11 |
+
|
| 12 |
+
# SAM 3D Body dependencies
|
| 13 |
+
torch==2.1.1
|
| 14 |
+
pytorch-lightning==2.1.3
|
| 15 |
+
pyrender==0.1.45
|
| 16 |
+
scikit-image==0.22.0
|
| 17 |
+
einops==0.7.0
|
| 18 |
+
timm==0.9.12
|
| 19 |
+
dill==0.3.7
|
| 20 |
+
pandas==2.1.4
|
| 21 |
+
rich==13.7.0
|
| 22 |
+
hydra-core==1.3.2
|
| 23 |
+
hydra-submitit-launcher==1.2.0
|
| 24 |
+
hydra-colorlog==1.2.0
|
| 25 |
+
pyrootutils==1.0.4
|
| 26 |
+
webdataset==0.2.86
|
| 27 |
+
chump==1.6.0
|
| 28 |
+
networkx==3.2.1
|
| 29 |
+
roma==1.0.1
|
| 30 |
+
joblib==1.3.2
|
| 31 |
+
seaborn==0.13.0
|
| 32 |
+
wandb==0.16.1
|
| 33 |
+
appdirs==1.4.4
|
| 34 |
+
appnope==0.1.3
|
| 35 |
+
cython==3.0.6
|
| 36 |
+
jsonlines==4.0.0
|
| 37 |
+
pytest==7.4.3
|
| 38 |
+
loguru==0.7.2
|
| 39 |
+
optree==0.10.0
|
| 40 |
+
fvcore==0.1.5.post20221221
|
| 41 |
+
huggingface_hub==0.20.2
|
| 42 |
+
|
| 43 |
+
# 3D model processing
|
| 44 |
+
trimesh==4.0.5
|
| 45 |
+
pyglet==2.0.10
|