Blair Johnson commited on
Commit
a622ce1
·
1 Parent(s): a1ec3e9

initial implementation

Browse files
Files changed (3) hide show
  1. Dockerfile +25 -0
  2. app.py +153 -0
  3. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Choose a specific Python version. Using a slim image is good for size.
2
+ # You can change the version to match your needs, e.g., python:3.10-slim
3
+ FROM python:3.12-slim
4
+
5
+ # Set the working directory inside the container
6
+ WORKDIR /code
7
+
8
+ # Copy the file with your Python package dependencies
9
+ COPY requirements.txt .
10
+
11
+ # Install system-level dependencies if you need them.
12
+ # For example, if you need ffmpeg for audio processing:
13
+ # RUN apt-get update && apt-get install -y ffmpeg
14
+
15
+ # Install the Python dependencies from your requirements.txt
16
+ # --no-cache-dir makes the image smaller
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy your application code into the container
20
+ COPY app.py .
21
+
22
+ # Command to run your Gradio app.
23
+ # The --server-name 0.0.0.0 makes it accessible from outside the container.
24
+ # The --server-port 7860 is the default Gradio port that Hugging Face expects.
25
+ CMD ["python", "app.py", "--server-name", "0.0.0.0", "--server-port", "7860"]
app.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import numpy as np
4
+ import cv2
5
+ from PIL import Image
6
+ from transformers import AutoProcessor, AutoModelForDepthEstimation
7
+ import tempfile
8
+ from stl import mesh
9
+ from sklearn.decomposition import PCA
10
+
11
+ MAX_RESOLUTION = 1.5e6
12
+ MODEL_ID = "depth-anything/Depth-Anything-V2-Large-hf"
13
+ device = "cuda" if torch.cuda.is_available() else "cpu"
14
+ processor = AutoProcessor.from_pretrained(MODEL_ID)
15
+ model = AutoModelForDepthEstimation.from_pretrained(MODEL_ID).to(device)
16
+ print("Model loaded successfully.")
17
+
18
+ def create_3d_model(
19
+ input_image: np.ndarray,
20
+ texture_strength: float,
21
+ max_z: float,
22
+ min_z: float,
23
+ x_length: float,
24
+ do_pca_correction: bool,
25
+ depth_map_smoothing: int,
26
+ texture_smoothing: int
27
+ ):
28
+ if input_image is None: raise gr.Error("Please upload an image.")
29
+ if max_z <= min_z: raise gr.Error("Max Z-height must be greater than Min Z-height.")
30
+
31
+ # resize large images
32
+ h, w, _ = input_image.shape
33
+ if h * w > MAX_RESOLUTION:
34
+ gr.Info("Image is large, downsampling to improve performance...")
35
+ ratio = (MAX_RESOLUTION / (h * w)) ** 0.5
36
+ new_w, new_h = int(w * ratio), int(h * ratio)
37
+ input_image = cv2.resize(input_image, (new_w, new_h), interpolation=cv2.INTER_AREA)
38
+
39
+ image = Image.fromarray(input_image).convert("RGB")
40
+ with torch.no_grad():
41
+ inputs = processor(images=image, return_tensors="pt").to(device)
42
+ outputs = model(**inputs)
43
+ predicted_depth = outputs.predicted_depth
44
+ depth = torch.nn.functional.interpolate(
45
+ predicted_depth.unsqueeze(1), size=(image.height, image.width),
46
+ mode="bicubic", align_corners=False,
47
+ ).squeeze().cpu().numpy()
48
+ depth_normalized = (depth - depth.min()) / (depth.max() - depth.min())
49
+
50
+ # smoothing base depthmap
51
+ if depth_map_smoothing > 1:
52
+ ksize = int(depth_map_smoothing)
53
+ if ksize % 2 == 0: ksize += 1
54
+ depth_normalized = cv2.GaussianBlur(depth_normalized, (ksize, ksize), 0)
55
+
56
+ gray_image = cv2.cvtColor(input_image, cv2.COLOR_RGB2GRAY)
57
+ brightness_normalized = gray_image.astype(float) / 255.0
58
+
59
+ # smoothing brightness
60
+ if texture_smoothing > 1:
61
+ ksize = int(texture_smoothing)
62
+ if ksize % 2 == 0: ksize += 1
63
+ brightness_normalized = cv2.GaussianBlur(brightness_normalized, (ksize, ksize), 0)
64
+
65
+ if brightness_normalized.shape != depth_normalized.shape:
66
+ brightness_normalized = cv2.resize(
67
+ brightness_normalized, (depth_normalized.shape[1], depth_normalized.shape[0]),
68
+ interpolation=cv2.INTER_LINEAR
69
+ )
70
+
71
+ combined_map = depth_normalized + (brightness_normalized * texture_strength)
72
+ c_min, c_max = combined_map.min(), combined_map.max()
73
+ if c_max > c_min:
74
+ combined_map_rescaled = (combined_map - c_min) / (c_max - c_min)
75
+ else:
76
+ combined_map_rescaled = np.zeros_like(combined_map)
77
+
78
+ z_data = min_z + combined_map_rescaled * (max_z - min_z)
79
+
80
+ # Planar correction with PCA
81
+ if do_pca_correction:
82
+ height, width = z_data.shape
83
+ y_length = x_length * (height / width)
84
+ x_coords_1d, y_coords_1d = np.linspace(0, x_length, width), np.linspace(y_length, 0, height)
85
+ x_grid, y_grid = np.meshgrid(x_coords_1d, y_coords_1d)
86
+ points = np.stack([x_grid.flatten(), y_grid.flatten(), z_data.flatten()], axis=1)
87
+ n_points, n_samples = points.shape[0], min(points.shape[0], 50000)
88
+ sample_indices = np.random.choice(n_points, n_samples, replace=False)
89
+ pca = PCA(n_components=3)
90
+ pca.fit(points[sample_indices])
91
+ normal = pca.components_[2]
92
+ if normal[2] < 0:
93
+ normal *= -1
94
+ p0 = pca.mean_
95
+ z_plane = p0[2] - (normal[0] * (x_grid - p0[0]) + normal[1] * (y_grid - p0[1])) / normal[2]
96
+ corrected_z = z_data - z_plane
97
+ cz_min, cz_max = corrected_z.min(), corrected_z.max()
98
+ if cz_max > cz_min:
99
+ z_data = min_z + (corrected_z - cz_min) / (cz_max - cz_min) * (max_z - min_z)
100
+
101
+ # STL mesh
102
+ height, width = z_data.shape
103
+ y_length = x_length * (height / width)
104
+ vertices = np.zeros((height, width, 3))
105
+ x_coords, y_coords = np.linspace(0, x_length, width), np.linspace(y_length, 0, height)
106
+ vertices[:, :, 0], vertices[:, :, 1], vertices[:, :, 2] = x_coords[np.newaxis, :], y_coords[:, np.newaxis], z_data
107
+ faces = []
108
+ for i in range(height-1):
109
+ for j in range(width-1):
110
+ v1,v2,v3,v4 = vertices[i,j], vertices[i+1,j], vertices[i+1,j+1], vertices[i,j+1]
111
+ faces.extend([[v1, v2, v3], [v1, v3, v4]])
112
+ v_tl, v_tr, v_bl, v_br = vertices[0,0], vertices[0,width-1], vertices[height-1,0], vertices[height-1,width-1]
113
+ b_tl,b_tr,b_bl,b_br = np.array([v_tl[0],v_tl[1],0]), np.array([v_tr[0],v_tr[1],0]), np.array([v_bl[0],v_bl[1],0]), np.array([v_br[0],v_br[1],0])
114
+ faces.extend([[v_tl,b_tl,b_tr],[v_tl,b_tr,v_tr], [v_br,b_br,b_bl],[v_br,b_bl,v_bl], [v_bl,b_bl,b_tl],[v_bl,b_tl,v_tl], [v_tr,b_tr,b_br],[v_tr,b_br,v_br], [b_tl,b_br,b_bl],[b_tl,b_tr,b_br]])
115
+ surface = mesh.Mesh(np.zeros(len(faces), dtype=mesh.Mesh.dtype))
116
+ surface.vectors = np.array(faces)
117
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".stl") as tmpfile:
118
+ surface.save(tmpfile.name)
119
+ return tmpfile.name, tmpfile.name
120
+
121
+ with gr.Blocks(theme='base') as demo:
122
+ gr.Markdown("# Image to 3D Relief Generator")
123
+ with gr.Row():
124
+ with gr.Column(scale=1):
125
+ input_image = gr.Image(type="numpy", label="Upload Image")
126
+ gr.Markdown("### Model Parameters")
127
+ texture_strength = gr.Slider(minimum=0.0, maximum=1.0, value=0.1, step=0.001, label="Brightness Texture Strength")
128
+ depth_map_smoothing = gr.Slider(minimum=0, maximum=51, value=0, step=1, label="Depth Map Smoothing", info="Smooths the base geometry. 0 = none.")
129
+ texture_smoothing = gr.Slider(minimum=0, maximum=51, value=0, step=1, label="Texture Smoothing", info="Smooths the brightness texture. 0 = none.")
130
+
131
+ x_length = gr.Number(value=100, label="X Length (units)")
132
+ min_z = gr.Number(value=0.5, label="Min Z-Height (units)")
133
+ max_z = gr.Number(value=5.0, label="Max Z-Height (units)")
134
+ do_pca = gr.Checkbox(value=True, label="Enable PCA Planar Correction")
135
+
136
+ generate_btn = gr.Button("Generate STL", variant="primary")
137
+
138
+ with gr.Column(scale=1):
139
+ gr.Markdown("### 3D Model Output")
140
+ output_model = gr.Model3D(label="Generated 3D Model")
141
+ output_file = gr.File(label="Download STL File")
142
+
143
+ generate_btn.click(
144
+ fn=create_3d_model,
145
+ inputs=[
146
+ input_image, texture_strength, max_z, min_z, x_length, do_pca,
147
+ depth_map_smoothing, texture_smoothing
148
+ ],
149
+ outputs=[output_model, output_file]
150
+ )
151
+
152
+ if __name__ == "__main__":
153
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ transformers
3
+ torch
4
+ torchvision
5
+ accelerate
6
+ opencv-python-headless
7
+ numpy-stl
8
+ Pillow
9
+ scikit-learn