Spaces:
Running
Running
# Copyright 2023-2025 Marigold Team, ETH Zürich. All rights reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# -------------------------------------------------------------------------- | |
# More information about Marigold: | |
# https://marigoldmonodepth.github.io | |
# https://marigoldcomputervision.github.io | |
# Efficient inference pipelines are now part of diffusers: | |
# https://huggingface.co/docs/diffusers/using-diffusers/marigold_usage | |
# https://huggingface.co/docs/diffusers/api/pipelines/marigold | |
# Examples of trained models and live demos: | |
# https://huggingface.co/prs-eth | |
# Related projects: | |
# https://rollingdepth.github.io/ | |
# https://marigolddepthcompletion.github.io/ | |
# Citation (BibTeX): | |
# https://github.com/prs-eth/Marigold#-citation | |
# If you find Marigold useful, we kindly ask you to cite our papers. | |
# -------------------------------------------------------------------------- | |
import os | |
import tempfile | |
import gradio as gr | |
from PIL import Image | |
from extrude import extrude_depth_3d | |
from gradio_patches.examples import Examples | |
default_seed = 2024 | |
default_batch_size = 4 | |
default_bas_plane_near = 0.0 | |
default_bas_plane_far = 1.0 | |
default_bas_embossing = 20 | |
default_bas_size_longest_px = 512 | |
default_bas_size_longest_cm = 10 | |
default_bas_filter_size = 3 | |
default_bas_frame_thickness = 5 | |
default_bas_frame_near = 1 | |
default_bas_frame_far = 1 | |
def process_bas( | |
path_input_depth, | |
path_input_rgb=None, | |
plane_near=default_bas_plane_near, | |
plane_far=default_bas_plane_far, | |
embossing=default_bas_embossing, | |
size_longest_px=default_bas_size_longest_px, | |
size_longest_cm=default_bas_size_longest_cm, | |
filter_size=default_bas_filter_size, | |
frame_thickness=default_bas_frame_thickness, | |
frame_near=default_bas_frame_near, | |
frame_far=default_bas_frame_far, | |
): | |
if path_input_depth is None: | |
raise gr.Error( | |
"Missing image in the first pane: upload a file or use one from the gallery below." | |
) | |
input_depth = Image.open(path_input_depth) | |
if input_depth.mode not in ("I", "I;16"): | |
raise gr.Error( | |
f"Input depth must be a 16-bit PNG image of a depth map, found {input_depth.mode}" | |
) | |
depth_longest_px = max(input_depth.size) | |
input_rgb = None | |
if path_input_rgb is not None: | |
input_rgb = Image.open(path_input_rgb).convert("RGB") | |
if ( | |
input_depth.size[0] * input_rgb.size[1] | |
!= input_depth.size[0] * input_rgb.size[1] | |
): | |
raise gr.Error( | |
f"Inputs have incompatible dimensions: {input_depth.size} and {input_rgb.size}" | |
) | |
if plane_near >= plane_far: | |
raise gr.Error("NEAR plane must have a value smaller than the FAR plane") | |
name_base, name_ext = os.path.splitext(os.path.basename(path_input_depth)) | |
print(f"Processing bas-relief {name_base}{name_ext}") | |
path_output_dir = tempfile.mkdtemp() | |
def _process_3d( | |
size_longest_px, | |
filter_size, | |
vertex_colors, | |
scene_lights, | |
output_model_scale=None, | |
prepare_for_3d_printing=False, | |
zip_outputs=False, | |
): | |
image_new_w = size_longest_px * input_depth.width // depth_longest_px | |
image_new_h = size_longest_px * input_depth.height // depth_longest_px | |
image_new_sz = (image_new_w, image_new_h) | |
path_depth_new = os.path.join( | |
path_output_dir, f"{name_base}_depth_{size_longest_px}.png" | |
) | |
( | |
input_depth.convert(mode="F") | |
.resize(image_new_sz, Image.BILINEAR) | |
.convert("I") | |
.save(path_depth_new) | |
) | |
path_rgb_new = None | |
if input_rgb is not None: | |
path_rgb_new = os.path.join( | |
path_output_dir, f"{name_base}_rgb_{size_longest_px}{name_ext}" | |
) | |
input_rgb.resize(image_new_sz, Image.LANCZOS).save(path_rgb_new) | |
path_glb, path_stl, path_obj = extrude_depth_3d( | |
path_depth_new, | |
path_rgb_new, | |
output_model_scale=( | |
size_longest_cm * 10 | |
if output_model_scale is None | |
else output_model_scale | |
), | |
filter_size=filter_size, | |
coef_near=plane_near, | |
coef_far=plane_far, | |
emboss=embossing / 100, | |
f_thic=frame_thickness / 100, | |
f_near=frame_near / 100, | |
f_back=frame_far / 100, | |
vertex_colors=vertex_colors, | |
scene_lights=scene_lights, | |
prepare_for_3d_printing=prepare_for_3d_printing, | |
zip_outputs=zip_outputs, | |
) | |
return path_glb, path_stl, path_obj | |
path_viewer_glb, _, _ = _process_3d( | |
256, filter_size, vertex_colors=False, scene_lights=True, output_model_scale=1 | |
) | |
path_files_glb, path_files_stl, path_files_obj = _process_3d( | |
size_longest_px, | |
filter_size, | |
vertex_colors=True, | |
scene_lights=False, | |
prepare_for_3d_printing=True, | |
zip_outputs=True, | |
) | |
return path_viewer_glb, [path_files_glb, path_files_stl, path_files_obj] | |
with gr.Blocks( | |
title="Depth To 3D Print", | |
css=""" | |
#download { | |
height: 118px; | |
} | |
.viewport { | |
aspect-ratio: 4/3; | |
} | |
h1 { | |
text-align: center; | |
display: block; | |
} | |
h2 { | |
text-align: center; | |
display: block; | |
} | |
h3 { | |
text-align: center; | |
display: block; | |
} | |
a { | |
display: inline-block; | |
} | |
.md_feedback li { | |
margin-bottom: 0px !important; | |
} | |
ol { | |
margin: 0 auto; | |
width: fit-content; | |
text-align: left; | |
} | |
ol li { | |
margin-bottom: 0px; | |
} | |
""", | |
head=""" | |
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1FWSVCGZTG"></script> | |
<script> | |
window.dataLayer = window.dataLayer || []; | |
function gtag() {dataLayer.push(arguments);} | |
gtag('js', new Date()); | |
gtag('config', 'G-1FWSVCGZTG'); | |
</script> | |
""", | |
) as demo: | |
gr.Markdown( | |
""" | |
# Depth To 3D Print | |
<p align="center"> | |
<a title="Get Depth" href="https://huggingface.co/spaces/prs-eth/marigold" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> | |
<img src="https://img.shields.io/badge/🤗%20Create%20Your%20-Depth%20from%20Image-blue" alt="Get Depth"> | |
</a> | |
<a title="Website" href="https://marigoldcomputervision.github.io/" target="_blank" rel="noopener noreferrer" | |
style="display: inline-block;"> | |
<img src="https://img.shields.io/badge/%F0%9F%A4%8D%20Project%20-Website-af2928" alt="Website Badge"> | |
</a> | |
<a title="Social" href="https://twitter.com/antonobukhov1" target="_blank" rel="noopener noreferrer" | |
style="display: inline-block;"> | |
<img src="https://www.obukhov.ai/img/badges/badge-social.svg" alt="social"> | |
</a><br> | |
Start exploring the interactive bas-relief examples at the bottom of the page! | |
To create your own watertight 3D-printable bas-relief depth map: | |
</p> | |
<ol> | |
<li>Click the "Create Your Depth from Image" badge above</li> | |
<li>Upload the input there and download the 16-bit PNG (right click save)</li> | |
<li>Return to this demo</li> | |
<li>Load your 16-bit depth PNG file in the top-left pane</li> | |
<li>Click "Create 3D"</li> | |
</ol> | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(): | |
bas_depth = gr.Image( | |
label="Depth", | |
type="filepath", | |
format="png", | |
image_mode=None, | |
sources=["upload", "clipboard"], | |
show_fullscreen_button=False, | |
) | |
bas_rgb = gr.Image( | |
label="Image (optional)", | |
type="filepath", | |
sources=["upload", "clipboard"], | |
show_fullscreen_button=False, | |
) | |
with gr.Row(): | |
bas_submit_btn = gr.Button(value="Create 3D", variant="primary") | |
bas_reset_btn = gr.Button(value="Reset") | |
with gr.Accordion("3D printing demo: Main options", open=True): | |
bas_plane_near = gr.Slider( | |
label="Near plane", | |
minimum=0.0, | |
maximum=1.0, | |
step=0.001, | |
value=default_bas_plane_near, | |
) | |
bas_plane_far = gr.Slider( | |
label="Far plane", | |
minimum=0.0, | |
maximum=1.0, | |
step=0.001, | |
value=default_bas_plane_far, | |
) | |
bas_embossing = gr.Slider( | |
label="Embossing level", | |
minimum=0, | |
maximum=100, | |
step=1, | |
value=default_bas_embossing, | |
) | |
with gr.Accordion("3D printing demo: Advanced options", open=False): | |
bas_size_longest_px = gr.Slider( | |
label="Longest side (px)", | |
minimum=256, | |
maximum=1024, | |
step=256, | |
value=default_bas_size_longest_px, | |
) | |
bas_size_longest_cm = gr.Slider( | |
label="Longest side (cm)", | |
minimum=1, | |
maximum=100, | |
step=1, | |
value=default_bas_size_longest_cm, | |
) | |
bas_filter_size = gr.Slider( | |
label="Smooth radius", | |
minimum=1, | |
maximum=5, | |
step=2, | |
value=default_bas_filter_size, | |
) | |
bas_frame_thickness = gr.Slider( | |
label="Frame thickness", | |
minimum=0, | |
maximum=100, | |
step=1, | |
value=default_bas_frame_thickness, | |
) | |
bas_frame_near = gr.Slider( | |
label="Near offset", | |
minimum=-100, | |
maximum=100, | |
step=1, | |
value=default_bas_frame_near, | |
) | |
bas_frame_far = gr.Slider( | |
label="Far offset", | |
minimum=1, | |
maximum=10, | |
step=1, | |
value=default_bas_frame_far, | |
) | |
with gr.Column(): | |
bas_output_viewer = gr.Model3D( | |
camera_position=(75.0, 90.0, 1.25), | |
elem_classes="viewport", | |
label="3D preview", | |
interactive=False, | |
) | |
bas_output_files = gr.Files( | |
label="3D models", | |
elem_id="download", | |
interactive=False, | |
) | |
Examples( | |
fn=process_bas, | |
examples=[ | |
[ | |
"files/einstein_depth_16bit.png", # input_depth | |
"files/einstein_rgb.jpg", # input_rgb | |
0.0, # plane_near | |
0.5, # plane_far | |
50, # embossing | |
512, # size_longest_px | |
10, # size_longest_cm | |
3, # filter_size | |
5, # frame_thickness | |
-25, # frame_near | |
1, # frame_far | |
], | |
], | |
inputs=[ | |
bas_depth, | |
bas_rgb, | |
bas_plane_near, | |
bas_plane_far, | |
bas_embossing, | |
bas_size_longest_px, | |
bas_size_longest_cm, | |
bas_filter_size, | |
bas_frame_thickness, | |
bas_frame_near, | |
bas_frame_far, | |
], | |
outputs=[bas_output_viewer, bas_output_files], | |
cache_examples=True, | |
directory_name="outputs", | |
) | |
bas_submit_btn.click( | |
fn=process_bas, | |
inputs=[ | |
bas_depth, | |
bas_rgb, | |
bas_plane_near, | |
bas_plane_far, | |
bas_embossing, | |
bas_size_longest_px, | |
bas_size_longest_cm, | |
bas_filter_size, | |
bas_frame_thickness, | |
bas_frame_near, | |
bas_frame_far, | |
], | |
outputs=[bas_output_viewer, bas_output_files], | |
) | |
bas_reset_btn.click( | |
fn=lambda: ( | |
gr.Button(interactive=True), | |
None, | |
None, | |
None, | |
None, | |
default_bas_plane_near, | |
default_bas_plane_far, | |
default_bas_embossing, | |
default_bas_size_longest_px, | |
default_bas_size_longest_cm, | |
default_bas_filter_size, | |
default_bas_frame_thickness, | |
default_bas_frame_near, | |
default_bas_frame_far, | |
), | |
inputs=[], | |
outputs=[ | |
bas_submit_btn, | |
bas_depth, | |
bas_rgb, | |
bas_output_viewer, | |
bas_output_files, | |
bas_plane_near, | |
bas_plane_far, | |
bas_embossing, | |
bas_size_longest_px, | |
bas_size_longest_cm, | |
bas_filter_size, | |
bas_frame_thickness, | |
bas_frame_near, | |
bas_frame_far, | |
], | |
) | |
if __name__ == "__main__": | |
demo.queue( | |
api_open=False, | |
).launch( | |
share=True, | |
server_port=7860, | |
) | |