ReliefGenerater / app.py
TKOSEI's picture
Update app.py
8daa403 verified
import cv2
import torch
from PIL import Image, ImageOps
import numpy as np
import gradio as gr
import math
import os
import zipfile
import trimesh
import pygltflib
from scipy.ndimage import median_filter
import requests # Import requests for downloading
# Depth-Anything V2 model setup
from depth_anything_v2.dpt import DepthAnythingV2
DEVICE = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
model_configs = {
'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},
'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]},
'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}
}
encoder = 'vitl' # or 'vits', 'vitb', 'vitg'
model = DepthAnythingV2(**model_configs[encoder])
# Define model directory and path
MODEL_DIR = "models"
os.makedirs(MODEL_DIR, exist_ok=True)
model_filename = f'depth_anything_v2_{encoder}.pth'
model_path = os.path.join(MODEL_DIR, model_filename)
# Add code to download model weights if not exists
if not os.path.exists(model_path):
print(f"Downloading {model_path}...")
url = f"https://huggingface.co/depth-anything/Depth-Anything-V2-Large/resolve/main/{model_filename}"
response = requests.get(url, stream=True)
with open(model_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print("Download complete.")
model.load_state_dict(torch.load(model_path, map_location='cpu'))
model = model.to(DEVICE).eval()
# Helper functions (from your notebook)
def quaternion_multiply(q1, q2):
x1, y1, z1, w1 = q1
x2, y2, z2, w2 = q2
return [
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2,
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
]
def glb_add_lights(path_input, path_output):
"""
Adds directional lights in the horizontal plane to the glb file.
:param path_input: path to input glb
:param path_output: path to output glb
:return: None
"""
glb = pygltflib.GLTF2().load(path_input)
N = 3 # default max num lights in Babylon.js is 4
angle_step = 2 * math.pi / N
elevation_angle = math.radians(75)
light_colors = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]
lights_extension = {
"lights": [
{"type": "directional", "color": light_colors[i], "intensity": 2.0}
for i in range(N)
]
}
if "KHR_lights_punctual" not in glb.extensionsUsed:
glb.extensionsUsed.append("KHR_lights_punctual")
glb.extensions["KHR_lights_punctual"] = lights_extension
light_nodes = []
for i in range(N):
angle = i * angle_step
pos_rot = [0.0, 0.0, math.sin(angle / 2), math.cos(angle / 2)]
elev_rot = [
math.sin(elevation_angle / 2),
0.0,
0.0,
math.cos(elevation_angle / 2),
]
rotation = quaternion_multiply(pos_rot, elev_rot)
node = {
"rotation": rotation,
"extensions": {"KHR_lights_punctual": {"light": i}},
}
light_nodes.append(node)
light_node_indices = list(range(len(glb.nodes), len(glb.nodes) + N))
glb.nodes.extend(light_nodes)
root_node_index = glb.scenes[glb.scene].nodes[0]
root_node = glb.nodes[root_node_index]
if hasattr(root_node, "children"):
root_node.children.extend(light_node_indices)
else:
root_node.children = light_node_indices
glb.save(path_output)
def extrude_depth_3d(
path_rgb,
path_depth,
path_out_base=None,
alpha=1.0,
invert=0,
output_model_scale=100,
filter_size=3,
coef_near=0.0,
coef_far=1.0,
emboss=0.3,
f_thic=0.05,
f_near=-0.15,
f_back=0.01,
vertex_colors=True,
scene_lights=True,
prepare_for_3d_printing=False,
zip_outputs=False,
lift_height=0.0
):
f_far_inner = -emboss
f_far_outer = f_far_inner - f_back
f_near = max(f_near, f_far_inner)
depth_image = Image.open(path_depth)
mono_image = Image.open(path_rgb).convert("L")
if invert==1:
mono_image = ImageOps.invert(mono_image)
w, h = depth_image.size
d_max = max(w, h)
depth_image = np.array(depth_image).astype(np.double)
mono_image = np.array(mono_image).astype(np.double)
z_min, z_max = np.min(depth_image), np.max(depth_image)
m_min, m_max = np.min(mono_image), np.max(mono_image)
depth_image = (depth_image.astype(np.double) - z_min) / (z_max - z_min)
depth_image[depth_image < coef_near] = coef_near
depth_image[depth_image > coef_far] = coef_far
z_min, z_max = np.min(depth_image), np.max(depth_image)
depth_image = (depth_image - z_min) / (z_max - z_min)
mono_image = median_filter(mono_image, size=5)
mono_image = (mono_image.astype(np.double) - m_min) / (m_max - m_min)
mono_image_new = np.where(depth_image == coef_far, 1, mono_image)
m_min=np.min(mono_image_new)
mono_image_new = np.where(depth_image == coef_far, 0, mono_image)
m_max=np.max(mono_image_new)
mono_image = np.where(depth_image == coef_far, m_min, mono_image)
mono_image = (mono_image - m_min) / (m_max - m_min)
depth_image = np.where(depth_image != 1.0, (1-alpha) * depth_image + alpha * mono_image, depth_image)
#depth_image_new[depth_image < coef_near] = 0
#depth_image_new[depth_image > coef_far] = 1
#depth_image_new[depth_image_new < 0] = 0
depth_image = median_filter(depth_image, size=filter_size)
depth_image = emboss*(depth_image - np.min(depth_image)) / (np.max(depth_image) - np.min(depth_image))
depth_image = np.where(depth_image != emboss, depth_image + lift_height, depth_image)
Image.fromarray((depth_image * 255).astype(np.uint8)).convert("L").save(path_out_base+".png")
rgb_image = np.array(
Image.open(path_rgb).convert("RGB").resize((w, h), Image.Resampling.LANCZOS)
)
w_norm = w / float(d_max - 1)
h_norm = h / float(d_max - 1)
w_half = w_norm / 2
h_half = h_norm / 2
x, y = np.meshgrid(np.arange(w), np.arange(h))
x = x / float(d_max - 1) - w_half # [-w_half, w_half]
y = -y / float(d_max - 1) + h_half # [-h_half, h_half]
z = -depth_image # -depth_emboss (far) - 0 (near)
vertices_2d = np.stack((x, y, z), axis=-1)
vertices = vertices_2d.reshape(-1, 3)
colors = rgb_image[:, :, :3].reshape(-1, 3) / 255.0
faces = []
for y in range(h - 1):
for x in range(w - 1):
idx = y * w + x
faces.append([idx, idx + w, idx + 1])
faces.append([idx + 1, idx + w, idx + 1 + w])
# OUTER frame
nv = len(vertices)
vertices = np.append(
vertices,
[
[-w_half - f_thic, -h_half - f_thic, f_near], # 00
[-w_half - f_thic, -h_half - f_thic, f_far_outer], # 01
[w_half + f_thic, -h_half - f_thic, f_near], # 02
[w_half + f_thic, -h_half - f_thic, f_far_outer], # 03
[w_half + f_thic, h_half + f_thic, f_near], # 04
[w_half + f_thic, h_half + f_thic, f_far_outer], # 05
[-w_half - f_thic, h_half + f_thic, f_near], # 06
[-w_half - f_thic, h_half + f_thic, f_far_outer], # 07
],
axis=0,
)
faces.extend(
[
[nv + 0, nv + 1, nv + 2],
[nv + 2, nv + 1, nv + 3],
[nv + 2, nv + 3, nv + 4],
[nv + 4, nv + 3, nv + 5],
[nv + 4, nv + 5, nv + 6],
[nv + 6, nv + 5, nv + 7],
[nv + 6, nv + 7, nv + 0],
[nv + 0, nv + 7, nv + 1],
]
)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * 8, axis=0)
# INNER frame
nv = len(vertices)
vertices_left_data = vertices_2d[:, 0] # H x 3
vertices_left_frame = vertices_2d[:, 0].copy() # H x 3
vertices_left_frame[:, 2] = f_near
vertices = np.append(vertices, vertices_left_data, axis=0)
vertices = np.append(vertices, vertices_left_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * h), axis=0)
for i in range(h - 1):
nvi_d = nv + i
nvi_f = nvi_d + h
faces.append([nvi_d, nvi_f, nvi_d + 1])
faces.append([nvi_d + 1, nvi_f, nvi_f + 1])
nv = len(vertices)
vertices_right_data = vertices_2d[:, -1] # H x 3
vertices_right_frame = vertices_2d[:, -1].copy() # H x 3
vertices_right_frame[:, 2] = f_near
vertices = np.append(vertices, vertices_right_data, axis=0)
vertices = np.append(vertices, vertices_right_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * h), axis=0)
for i in range(h - 1):
nvi_d = nv + i
nvi_f = nvi_d + h
faces.append([nvi_d, nvi_d + 1, nvi_f])
faces.append([nvi_d + 1, nvi_f + 1, nvi_f])
nv = len(vertices)
vertices_top_data = vertices_2d[0, :] # H x 3
vertices_top_frame = vertices_2d[0, :].copy() # H x 3
vertices_top_frame[:, 2] = f_near
vertices = np.append(vertices, vertices_top_data, axis=0)
vertices = np.append(vertices, vertices_top_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * w), axis=0)
for i in range(w - 1):
nvi_d = nv + i
nvi_f = nvi_d + w
faces.append([nvi_d, nvi_d + 1, nvi_f])
faces.append([nvi_d + 1, nvi_f + 1, nvi_f])
nv = len(vertices)
vertices_bottom_data = vertices_2d[-1, :] # H x 3
vertices_bottom_frame = vertices_2d[-1, :].copy() # H x 3
vertices_bottom_frame[:, 2] = f_near
vertices = np.append(vertices, vertices_bottom_data, axis=0)
vertices = np.append(vertices, vertices_bottom_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * w), axis=0)
for i in range(w - 1):
nvi_d = nv + i
nvi_f = nvi_d + w
faces.append([nvi_d, nvi_f, nvi_d + 1])
faces.append([nvi_d + 1, nvi_f, nvi_f + 1])
# FRONT frame
nv = len(vertices)
vertices = np.append(
vertices,
[
[-w_half - f_thic, -h_half - f_thic, f_near],
[-w_half - f_thic, h_half + f_thic, f_near],
],
axis=0,
)
vertices = np.append(vertices, vertices_left_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + h), axis=0)
for i in range(h - 1):
faces.append([nv, nv + 2 + i + 1, nv + 2 + i])
faces.append([nv, nv + 2, nv + 1])
nv = len(vertices)
vertices = np.append(
vertices,
[
[w_half + f_thic, h_half + f_thic, f_near],
[w_half + f_thic, -h_half - f_thic, f_near],
],
axis=0,
)
vertices = np.append(vertices, vertices_right_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + h), axis=0)
for i in range(h - 1):
faces.append([nv, nv + 2 + i, nv + 2 + i + 1])
faces.append([nv, nv + h + 1, nv + 1])
nv = len(vertices)
vertices = np.append(
vertices,
[
[w_half + f_thic, h_half + f_thic, f_near],
[-w_half - f_thic, h_half + f_thic, f_near],
],
axis=0,
)
vertices = np.append(vertices, vertices_top_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + w), axis=0)
for i in range(w - 1):
faces.append([nv, nv + 2 + i, nv + 2 + i + 1])
faces.append([nv, nv + 1, nv + 2])
nv = len(vertices)
vertices = np.append(
vertices,
[
[-w_half - f_thic, -h_half - f_thic, f_near],
[w_half + f_thic, -h_half - f_thic, f_near],
],
axis=0,
)
vertices = np.append(vertices, vertices_bottom_frame, axis=0)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + w), axis=0)
for i in range(w - 1):
faces.append([nv, nv + 2 + i + 1, nv + 2 + i])
faces.append([nv, nv + 1, nv + w + 1])
# BACK frame
nv = len(vertices)
vertices = np.append(
vertices,
[
[-w_half - f_thic, -h_half - f_thic, f_far_outer], # 00
[w_half + f_thic, -h_half - f_thic, f_far_outer], # 01
[w_half + f_thic, h_half + f_thic, f_far_outer], # 02
[-w_half - f_thic, h_half + f_thic, f_far_outer], # 03
],
axis=0,
)
faces.extend(
[
[nv + 0, nv + 2, nv + 1],
[nv + 2, nv + 0, nv + 3],
]
)
colors = np.append(colors, [[0.5, 0.5, 0.5]] * 4, axis=0)
trimesh_kwargs = {}
if vertex_colors:
trimesh_kwargs["vertex_colors"] = colors
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, **trimesh_kwargs)
mesh.merge_vertices()
current_max_dimension = max(mesh.extents)
scaling_factor = output_model_scale / current_max_dimension
mesh.apply_scale(scaling_factor)
if prepare_for_3d_printing:
rotation_mat = trimesh.transformations.rotation_matrix(
np.radians(0), [0.5, 0, 0]
)
mesh.apply_transform(rotation_mat)
if path_out_base is None:
path_out_base = os.path.splitext(path_depth)[0].replace("_16bit", "")
path_out_glb = path_out_base + ".glb"
path_out_stl = path_out_base + ".stl"
path_out_obj = path_out_base + ".obj"
mesh.export(path_out_stl, file_type="stl")
"""
mesh.export(path_out_glb, file_type="glb")
if scene_lights:
glb_add_lights(path_out_glb, path_out_glb)
mesh.export(path_out_obj, file_type="obj")
if zip_outputs:
with zipfile.ZipFile(path_out_glb + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf:
arcname = os.path.basename(os.path.splitext(path_out_glb)[0]) + ".glb"
zipf.write(path_out_glb, arcname=arcname)
path_out_glb = path_out_glb + ".zip"
with zipfile.ZipFile(path_out_stl + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf:
arcname = os.path.basename(os.path.splitext(path_out_stl)[0]) + ".stl"
zipf.write(path_out_stl, arcname=arcname)
path_out_stl = path_out_stl + ".zip"
with zipfile.ZipFile(path_out_obj + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf:
arcname = os.path.basename(os.path.splitext(path_out_obj)[0]) + ".obj"
zipf.write(path_out_obj, arcname=arcname)
path_out_obj = path_out_obj + ".zip"
"""
return path_out_glb, path_out_stl, path_out_obj
def scale_to_width(img, length):
if img.width < img.height:
width = length
height = round(img.height * length / img.width)
else:
width = round(img.width * length / img.height)
height = length
return (width,height)
# Gradio Interface function
def process_image_and_generate_stl(image_input, depth_near, depth_far, thickness, alpha, backsheet, lift):
# Depth Estimation
raw_img = cv2.imread(image_input)
depth = model.infer_image(raw_img) # HxW raw depth map in numpy
# Save depth map temporarily
depth_output_path = "output_depth.png"
cv2.imwrite(depth_output_path, depth)
# Prepare images for 3D model generation
img_rgb = image_input
img_depth = depth_output_path
inv = 0 # Assuming no inversion for now, based on previous code
# Image.open(img_rgb).convert("L").save("example_1_black.png") # This line might not be necessary for the final output
size = scale_to_width(Image.open(img_rgb), 512)
Image.open(img_rgb).resize(size, Image.Resampling.LANCZOS).save("one.png") # Use Resampling.LANCZOS
if inv == 1:
Image.open(img_depth).convert(mode="F").resize(size, Image.Resampling.BILINEAR).convert("I").save("two.png") # Use Resampling.BILINEAR
else:
img=Image.open(img_depth).convert(mode="F").resize(size, Image.Resampling.BILINEAR).convert("I") # Use Resampling.BILINEAR
img = np.array(img).astype(np.double)
im_max=np.max(img)
im_min=np.min(img)
img=(1-(img-im_min)/(im_max-im_min))*im_max
img=Image.fromarray(img)
img.convert("I").save("two.png")
# 3D Model Generation
output_path_base = "generated_relief"
glb_path, stl_path, obj_path = extrude_depth_3d(
"one.png",
"two.png",
alpha=alpha,
invert=inv,
path_out_base=output_path_base,
output_model_scale=100,
filter_size=5, # Using 5 based on previous code
coef_near=depth_near,
coef_far=depth_far,
emboss=thickness,
f_thic=0.0, # Using 0.0 based on previous code
f_near=-thickness, # Using -thickness based on previous code
f_back=backsheet, # Using 0.01 based on previous code
vertex_colors=True,
scene_lights=True,
prepare_for_3d_printing=True,
lift_height=-lift
)
return stl_path # Return the path to the generated STL file
# Gradio Interface definition
iface = gr.Interface(
fn=process_image_and_generate_stl,
inputs=[
gr.Image(type="filepath", label="Upload Image"),
gr.Slider(minimum=0, maximum=1.0, value=0, label="Depth Near"),
gr.Slider(minimum=0, maximum=1.0, value=1.0, label="Depth Far"),
gr.Slider(minimum=0.1, maximum=1.0, value=0.3, label="Thickness"),
gr.Slider(minimum=0, maximum=1.0, value=0.05, label="Alpha"),
gr.Slider(minimum=0.01, maximum=0.1, value=0.01, label="BackSheet Thickness"),
gr.Slider(minimum=0, maximum=0.1, value=0.0, label="lift"),
],
outputs=gr.File(label="Download STL File"), # Use gr.File() for file downloads
title="Image to 2.5D Relief Model Generator",
description="Upload an image, set parameters, and generate a 2.5D relief model (.stl file)."
)
# Launch the interface (for local testing)
if __name__ == "__main__":
iface.launch(debug=True)