Tremick commited on
Commit
6bd049a
·
1 Parent(s): 3edc3f3

Add application files

Browse files
Files changed (3) hide show
  1. Dockerfile +20 -0
  2. app.py +140 -0
  3. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Create a non-root user matching Hugging Face internal permissions
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV HOME=/home/user \
7
+ PATH=/home/user/.local/bin:$PATH
8
+
9
+ # Set the working directory inside the container's user space
10
+ WORKDIR $HOME/app
11
+
12
+ # Copy requirements and install dependencies into user space
13
+ COPY --chown=user requirements.txt $HOME/app/requirements.txt
14
+ RUN pip install --no-cache-dir --user --upgrade -r $HOME/app/requirements.txt
15
+
16
+ # Copy all your local source code folders into the container
17
+ COPY --chown=user . $HOME/app
18
+
19
+ # Run uvicorn pointing to your app file inside the code directory
20
+ CMD ["uvicorn", "code.app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import numpy as np
4
+ from PIL import Image, UnidentifiedImageError
5
+ import torch
6
+ import gradio as gr
7
+ from fastapi import FastAPI, File, UploadFile, HTTPException
8
+ from fastapi.responses import Response
9
+ from transformers import DepthProImageProcessorFast, DepthProForDepthEstimation
10
+
11
+ # 1. Initialize FastAPI & Load Apple Depth Pro
12
+ fastapi_app = FastAPI(title="True Metric ToF Server")
13
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
14
+
15
+ print("Loading Apple Depth Pro components...")
16
+ image_processor = DepthProImageProcessorFast.from_pretrained("apple/DepthPro-hf")
17
+ model = DepthProForDepthEstimation.from_pretrained("apple/DepthPro-hf").to(device)
18
+ model.eval()
19
+ print(f"Model successfully loaded on target device: {device}")
20
+
21
+
22
+ @fastapi_app.get("/")
23
+ def home():
24
+ return {"status": "running"}
25
+
26
+
27
+ @fastapi_app.post("/get_metric_depth")
28
+ def get_metric_depth(file: UploadFile = File(...)):
29
+ if not (file.content_type or "").startswith("image/"):
30
+ raise HTTPException(status_code=400, detail="Uploaded file must be an image.")
31
+
32
+ try:
33
+ image_bytes = file.file.read()
34
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
35
+ except (UnidentifiedImageError, OSError):
36
+ raise HTTPException(status_code=400, detail="Could not decode image.")
37
+
38
+ try:
39
+ inputs = image_processor(images=image, return_tensors="pt").to(device)
40
+ with torch.no_grad():
41
+ outputs = model(**inputs)
42
+
43
+ post_processed = image_processor.post_process_depth_estimation(
44
+ outputs, target_sizes=[(image.height, image.width)],
45
+ )
46
+
47
+ metric_depth_map = (
48
+ post_processed["predicted_depth"]
49
+ .detach().cpu().numpy().astype(np.float32)
50
+ )
51
+
52
+ buffer = io.BytesIO()
53
+ np.save(buffer, metric_depth_map)
54
+ return Response(content=buffer.getvalue(), media_type="application/octet-stream")
55
+
56
+ except Exception as e:
57
+ raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")
58
+
59
+
60
+ # =====================================================================
61
+ # 2. GRADIO INTERACTIVE INTERFACE (Does the 3D Projection in the Cloud)
62
+ # =====================================================================
63
+
64
+ def create_3d_point_cloud(input_pil_image):
65
+ """Processes the image, calculates 3D vertices, and writes a .ply file."""
66
+ if input_pil_image is None:
67
+ return None
68
+
69
+ # Step A: Run inference locally inside the space
70
+ image = input_pil_image.convert("RGB")
71
+ inputs = image_processor(images=image, return_tensors="pt").to(device)
72
+
73
+ with torch.no_grad():
74
+ outputs = model(**inputs)
75
+
76
+ post_processed = image_processor.post_process_depth_estimation(
77
+ outputs, target_sizes=[(image.height, image.width)],
78
+ )
79
+ depth_matrix = post_processed["predicted_depth"].detach().cpu().numpy().astype(np.float32)
80
+
81
+ # Step B: Pinhole camera math projection (u, v, depth) -> (X, Y, Z)
82
+ height, width = depth_matrix.shape
83
+ color_np = np.array(image) / 255.0
84
+
85
+ focal_length = max(width, height)
86
+ cx, cy = width / 2.0, height / 2.0
87
+ u, v = np.meshgrid(np.arange(width), np.arange(height))
88
+
89
+ z = depth_matrix.flatten()
90
+ x = ((u.flatten() - cx) * z) / focal_length
91
+ y = ((v.flatten() - cy) * z) / focal_length
92
+
93
+ # Clean up infinite backgrounds (keep objects between 10cm and 30 meters)
94
+ valid_mask = (z > 0.1) & (z < 30.0)
95
+ x, y, z = x[valid_mask], y[valid_mask], z[valid_mask]
96
+
97
+ rgb = color_np.reshape(-1, 3)[valid_mask]
98
+ rgb_ints = (rgb * 255).astype(np.uint8)
99
+
100
+ # Step C: Write raw points directly to a standard .ply file format
101
+ ply_filename = "output_scene.ply"
102
+ num_points = len(z)
103
+
104
+ with open(ply_filename, "w") as f:
105
+ f.write("ply\n")
106
+ f.write("format ascii 1.0\n")
107
+ f.write(f"element vertex {num_points}\n")
108
+ f.write("property float x\n")
109
+ f.write("property float y\n")
110
+ f.write("property float z\n")
111
+ f.write("property uchar red\n")
112
+ f.write("property uchar green\n")
113
+ f.write("property uchar blue\n")
114
+ f.write("end_header\n")
115
+
116
+ for i in range(num_points):
117
+ # Invert Y axis slightly so the 3D model loads right-side up
118
+ f.write(f"{x[i]:.4f} {-y[i]:.4f} {z[i]:.4f} {rgb_ints[i][0]} {rgb_ints[i][1]} {rgb_ints[i][2]}\n")
119
+
120
+ return ply_filename
121
+
122
+
123
+ # Define web dashboard interface layout
124
+ with gr.Blocks(title="Software-ToF 3D Cloud Visualizer") as demo:
125
+ gr.Markdown("# 📷 Software-ToF Camera Simulation")
126
+ gr.Markdown("Upload any image to calculate absolute metric depths and generate an interactive 3D Point Cloud visualization.")
127
+
128
+ with gr.Row():
129
+ with gr.Column():
130
+ input_img = gr.Image(type="pil", label="Input Scene Photo")
131
+ submit_btn = gr.Button("Generate ToF 3D Structure", variant="primary")
132
+
133
+ with gr.Column():
134
+ # Model3D renders an interactive canvas in the web browser
135
+ output_3d = gr.Model3D(label="Interactive 3D Point Cloud Canvas", display_mode="point_cloud")
136
+
137
+ submit_btn.click(fn=create_3d_point_cloud, inputs=[input_img], outputs=[output_3d])
138
+
139
+ # Mount your unified architecture onto Hugging Face port 7860
140
+ app = gr.mount_gradio_app(fastapi_app, demo, path="/")
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
4
+ torch --index-url https://pytorch.org
5
+ transformers
6
+ accelerate
7
+ numpy
8
+ pillow
9
+ gradio