Spaces:
Paused
Paused
Support API calls
Browse files- app.py +140 -13
- instructions_12345.md +32 -0
app.py
CHANGED
@@ -12,17 +12,13 @@ elev_est_dir = os.path.abspath(os.path.join(code_dir, "one2345_elev_est"))
|
|
12 |
sys.path.append(elev_est_dir)
|
13 |
|
14 |
if not is_local_run:
|
15 |
-
# import pip
|
16 |
-
# pip.main(['install', elev_est_dir])
|
17 |
# export TORCH_CUDA_ARCH_LIST="7.0;7.2;8.0;8.6"
|
18 |
# export IABN_FORCE_CUDA=1
|
19 |
os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
|
20 |
os.environ["IABN_FORCE_CUDA"] = "1"
|
21 |
os.environ["FORCE_CUDA"] = "1"
|
22 |
-
# pip.main(["install", "inplace_abn"])
|
23 |
subprocess.run(['pip', 'install', 'inplace_abn'])
|
24 |
# FORCE_CUDA=1 pip install --no-cache-dir git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0
|
25 |
-
# pip.main(["install", "--no-cache-dir", "git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0"])
|
26 |
subprocess.run(['pip', 'install', '--no-cache-dir', 'git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0'])
|
27 |
|
28 |
import shutil
|
@@ -154,12 +150,9 @@ class CameraVisualizer:
|
|
154 |
|
155 |
self._raw_image = raw_image
|
156 |
self._8bit_image = Image.fromarray(raw_image).convert('P', palette='WEB', dither=None)
|
157 |
-
# self._8bit_image = Image.fromarray(raw_image.clip(0, 254)).convert(
|
158 |
-
# 'P', palette='WEB', dither=None)
|
159 |
self._image_colorscale = [
|
160 |
[i / 255.0, 'rgb({}, {}, {})'.format(*rgb)] for i, rgb in enumerate(idx_to_color)]
|
161 |
self._elev = elev
|
162 |
-
# return self.update_figure()
|
163 |
|
164 |
def update_figure(self):
|
165 |
fig = go.Figure()
|
@@ -243,9 +236,6 @@ class CameraVisualizer:
|
|
243 |
|
244 |
# look at center of scene
|
245 |
fig.update_layout(
|
246 |
-
# width=640,
|
247 |
-
# height=480,
|
248 |
-
# height=400,
|
249 |
height=450,
|
250 |
autosize=True,
|
251 |
hovermode=False,
|
@@ -312,7 +302,7 @@ def stage1_run(models, device, cam_vis, tmp_dir,
|
|
312 |
stage2_steps = 50 # ddim_steps
|
313 |
zero123_infer(model, tmp_dir, indices=[0], device=device, ddim_steps=stage2_steps, scale=scale)
|
314 |
try:
|
315 |
-
elev_output = estimate_elev(tmp_dir)
|
316 |
except:
|
317 |
print("Failed to estimate polar angle")
|
318 |
elev_output = 90
|
@@ -459,6 +449,119 @@ def init_bbox(image):
|
|
459 |
gr.update(value=x_max, maximum=width),
|
460 |
gr.update(value=y_max, maximum=height)]
|
461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
|
463 |
def run_demo(
|
464 |
device_idx=_GPU_INDEX,
|
@@ -473,7 +576,6 @@ def run_demo(
|
|
473 |
with open('instructions_12345.md', 'r') as f:
|
474 |
article = f.read()
|
475 |
|
476 |
-
# NOTE: Examples must match inputs
|
477 |
example_folder = os.path.join(os.path.dirname(__file__), 'demo_examples')
|
478 |
example_fns = os.listdir(example_folder)
|
479 |
example_fns.sort()
|
@@ -494,7 +596,7 @@ def run_demo(
|
|
494 |
image_block = gr.Image(type='pil', image_mode='RGBA', height=290, label='Input image', tool=None)
|
495 |
|
496 |
gr.Examples(
|
497 |
-
examples=examples_full,
|
498 |
inputs=[image_block],
|
499 |
outputs=[image_block],
|
500 |
cache_examples=False,
|
@@ -569,6 +671,31 @@ def run_demo(
|
|
569 |
</div>
|
570 |
""")
|
571 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
572 |
update_guide = lambda GUIDE_TEXT: gr.update(value=GUIDE_TEXT)
|
573 |
|
574 |
views = [view_1, view_2, view_3, view_4, view_5, view_6, view_7, view_8]
|
|
|
12 |
sys.path.append(elev_est_dir)
|
13 |
|
14 |
if not is_local_run:
|
|
|
|
|
15 |
# export TORCH_CUDA_ARCH_LIST="7.0;7.2;8.0;8.6"
|
16 |
# export IABN_FORCE_CUDA=1
|
17 |
os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
|
18 |
os.environ["IABN_FORCE_CUDA"] = "1"
|
19 |
os.environ["FORCE_CUDA"] = "1"
|
|
|
20 |
subprocess.run(['pip', 'install', 'inplace_abn'])
|
21 |
# FORCE_CUDA=1 pip install --no-cache-dir git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0
|
|
|
22 |
subprocess.run(['pip', 'install', '--no-cache-dir', 'git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0'])
|
23 |
|
24 |
import shutil
|
|
|
150 |
|
151 |
self._raw_image = raw_image
|
152 |
self._8bit_image = Image.fromarray(raw_image).convert('P', palette='WEB', dither=None)
|
|
|
|
|
153 |
self._image_colorscale = [
|
154 |
[i / 255.0, 'rgb({}, {}, {})'.format(*rgb)] for i, rgb in enumerate(idx_to_color)]
|
155 |
self._elev = elev
|
|
|
156 |
|
157 |
def update_figure(self):
|
158 |
fig = go.Figure()
|
|
|
236 |
|
237 |
# look at center of scene
|
238 |
fig.update_layout(
|
|
|
|
|
|
|
239 |
height=450,
|
240 |
autosize=True,
|
241 |
hovermode=False,
|
|
|
302 |
stage2_steps = 50 # ddim_steps
|
303 |
zero123_infer(model, tmp_dir, indices=[0], device=device, ddim_steps=stage2_steps, scale=scale)
|
304 |
try:
|
305 |
+
elev_output = int(estimate_elev(tmp_dir))
|
306 |
except:
|
307 |
print("Failed to estimate polar angle")
|
308 |
elev_output = 90
|
|
|
449 |
gr.update(value=x_max, maximum=width),
|
450 |
gr.update(value=y_max, maximum=height)]
|
451 |
|
452 |
+
### API functions
|
453 |
+
def preprocess_api(predictor, raw_im):
|
454 |
+
raw_im.thumbnail([512, 512], Image.Resampling.LANCZOS)
|
455 |
+
image_rem = raw_im.convert('RGBA')
|
456 |
+
image_nobg = remove(image_rem, alpha_matting=True)
|
457 |
+
arr = np.asarray(image_nobg)[:,:,-1]
|
458 |
+
x_nonzero = np.nonzero(arr.sum(axis=0))
|
459 |
+
y_nonzero = np.nonzero(arr.sum(axis=1))
|
460 |
+
x_min = int(x_nonzero[0].min())
|
461 |
+
y_min = int(y_nonzero[0].min())
|
462 |
+
x_max = int(x_nonzero[0].max())
|
463 |
+
y_max = int(y_nonzero[0].max())
|
464 |
+
image_sam = sam_out_nosave(predictor, raw_im.convert("RGB"), x_min, y_min, x_max, y_max)
|
465 |
+
input_256 = image_preprocess_nosave(image_sam, lower_contrast=False, rescale=True)
|
466 |
+
torch.cuda.empty_cache()
|
467 |
+
return input_256
|
468 |
+
|
469 |
+
def estimate_elev_api(models, device, predictor,
|
470 |
+
input_im, preprocess=True, scale=3, ddim_steps=50):
|
471 |
+
model = models['turncam'].half()
|
472 |
+
tmp_dir = tempfile.TemporaryDirectory(dir=os.path.join(os.path.dirname(__file__), 'demo_tmp')).name
|
473 |
+
stage1_dir = os.path.join(tmp_dir, "stage1_8")
|
474 |
+
os.makedirs(stage1_dir, exist_ok=True)
|
475 |
+
if preprocess:
|
476 |
+
input_im = preprocess_api(predictor, input_im)
|
477 |
+
input_image = input_im.convert("RGB")
|
478 |
+
output_ims = predict_stage1_gradio(model, input_image, save_path=stage1_dir, adjust_set=[0], device=device, ddim_steps=ddim_steps, scale=scale)
|
479 |
+
stage2_steps = 50 # ddim_steps
|
480 |
+
zero123_infer(model, tmp_dir, indices=[0], device=device, ddim_steps=stage2_steps, scale=scale)
|
481 |
+
try:
|
482 |
+
polar_angle = int(estimate_elev(tmp_dir))
|
483 |
+
except:
|
484 |
+
print("Failed to estimate polar angle")
|
485 |
+
polar_angle = 90
|
486 |
+
print("Estimated polar angle:", polar_angle)
|
487 |
+
return 90-polar_angle
|
488 |
+
|
489 |
+
def convert_mesh_format(exp_dir, output_format=".obj"):
|
490 |
+
ply_path = os.path.join(exp_dir, f"meshes_val_bg/lod0/mesh_00215000_gradio_lod0.ply")
|
491 |
+
mesh_path = os.path.join(exp_dir, f"mesh{output_format}")
|
492 |
+
mesh = trimesh.load_mesh(ply_path)
|
493 |
+
rotation_matrix = trimesh.transformations.rotation_matrix(np.pi/2, [1, 0, 0]) @ trimesh.transformations.rotation_matrix(np.pi, [0, 0, 1])
|
494 |
+
mesh.apply_transform(rotation_matrix)
|
495 |
+
mesh.vertices[:, 0] = -mesh.vertices[:, 0]
|
496 |
+
mesh.faces = np.fliplr(mesh.faces)
|
497 |
+
if output_format == ".obj":
|
498 |
+
# Export the mesh as .obj file with colors
|
499 |
+
mesh.export(mesh_path, file_type='obj', include_color=True)
|
500 |
+
else:
|
501 |
+
mesh.export(mesh_path, file_type='glb')
|
502 |
+
return mesh_path
|
503 |
+
|
504 |
+
def reconstruct(exp_dir, output_format=".ply", device_idx=0):
|
505 |
+
|
506 |
+
main_dir_path = os.path.dirname(__file__)
|
507 |
+
torch.cuda.empty_cache()
|
508 |
+
os.chdir(os.path.join(code_dir, 'SparseNeuS_demo_v1/'))
|
509 |
+
|
510 |
+
bash_script = f'CUDA_VISIBLE_DEVICES={device_idx} python exp_runner_generic_blender_val.py \
|
511 |
+
--specific_dataset_name {exp_dir} \
|
512 |
+
--mode export_mesh \
|
513 |
+
--conf confs/one2345_lod0_val_demo.conf'
|
514 |
+
print(bash_script)
|
515 |
+
os.system(bash_script)
|
516 |
+
os.chdir(main_dir_path)
|
517 |
+
|
518 |
+
ply_path = os.path.join(exp_dir, f"meshes_val_bg/lod0/mesh_00215000_gradio_lod0.ply")
|
519 |
+
if output_format == ".ply":
|
520 |
+
return ply_path
|
521 |
+
if output_format not in [".obj", ".glb"]:
|
522 |
+
print("Invalid output format, must be one of .ply, .obj, .glb")
|
523 |
+
return ply_path
|
524 |
+
return convert_mesh_format(exp_dir, output_format=output_format)
|
525 |
+
|
526 |
+
def gen_mesh_api(models, predictor, device,
|
527 |
+
input_im, preprocess=True, scale=3, ddim_steps=75, stage2_steps=50):
|
528 |
+
if preprocess:
|
529 |
+
input_im = preprocess_api(predictor, input_im)
|
530 |
+
model = models['turncam'].half()
|
531 |
+
# folder to save the stage 1 images
|
532 |
+
exp_dir = tempfile.TemporaryDirectory(dir=os.path.join(os.path.dirname(__file__), 'demo_tmp')).name
|
533 |
+
stage1_dir = os.path.join(exp_dir, "stage1_8")
|
534 |
+
os.makedirs(stage1_dir, exist_ok=True)
|
535 |
+
|
536 |
+
# stage 1: generate 4 views at the same elevation as the input
|
537 |
+
output_ims = predict_stage1_gradio(model, input_im, save_path=stage1_dir, adjust_set=list(range(4)), device=device, ddim_steps=ddim_steps, scale=scale)
|
538 |
+
|
539 |
+
# stage 2 for the first image
|
540 |
+
# infer 4 nearby views for an image to estimate the polar angle of the input
|
541 |
+
stage2_steps = 50 # ddim_steps
|
542 |
+
zero123_infer(model, exp_dir, indices=[0], device=device, ddim_steps=stage2_steps, scale=scale)
|
543 |
+
# estimate the camera pose (elevation) of the input image.
|
544 |
+
try:
|
545 |
+
polar_angle = int(estimate_elev(exp_dir))
|
546 |
+
except:
|
547 |
+
print("Failed to estimate polar angle")
|
548 |
+
polar_angle = 90
|
549 |
+
print("Estimated polar angle:", polar_angle)
|
550 |
+
gen_poses(exp_dir, polar_angle)
|
551 |
+
|
552 |
+
# stage 1: generate another 4 views at a different elevation
|
553 |
+
if polar_angle <= 75:
|
554 |
+
output_ims_2 = predict_stage1_gradio(model, input_im, save_path=stage1_dir, adjust_set=list(range(4,8)), device=device, ddim_steps=ddim_steps, scale=scale)
|
555 |
+
else:
|
556 |
+
output_ims_2 = predict_stage1_gradio(model, input_im, save_path=stage1_dir, adjust_set=list(range(8,12)), device=device, ddim_steps=ddim_steps, scale=scale)
|
557 |
+
torch.cuda.empty_cache()
|
558 |
+
# stage 2 for the remaining 7 images, generate 7*4=28 views
|
559 |
+
if polar_angle <= 75:
|
560 |
+
zero123_infer(model, exp_dir, indices=list(range(1,8)), device=device, ddim_steps=stage2_steps, scale=scale)
|
561 |
+
else:
|
562 |
+
zero123_infer(model, exp_dir, indices=list(range(1,4))+list(range(8,12)), device=device, ddim_steps=stage2_steps, scale=scale)
|
563 |
+
return reconstruct(exp_dir)
|
564 |
+
|
565 |
|
566 |
def run_demo(
|
567 |
device_idx=_GPU_INDEX,
|
|
|
576 |
with open('instructions_12345.md', 'r') as f:
|
577 |
article = f.read()
|
578 |
|
|
|
579 |
example_folder = os.path.join(os.path.dirname(__file__), 'demo_examples')
|
580 |
example_fns = os.listdir(example_folder)
|
581 |
example_fns.sort()
|
|
|
596 |
image_block = gr.Image(type='pil', image_mode='RGBA', height=290, label='Input image', tool=None)
|
597 |
|
598 |
gr.Examples(
|
599 |
+
examples=examples_full,
|
600 |
inputs=[image_block],
|
601 |
outputs=[image_block],
|
602 |
cache_examples=False,
|
|
|
671 |
</div>
|
672 |
""")
|
673 |
|
674 |
+
# hidden buttons for supporting API calls
|
675 |
+
elev_est_btn = gr.Button('Run API', variant='primary', visible=False)
|
676 |
+
elev_est_out = gr.Number(value=0, visible=False)
|
677 |
+
elev_preprocess_chk = gr.Checkbox(value=True, visible=False)
|
678 |
+
|
679 |
+
elev_est_btn.click(fn=partial(estimate_elev_api, models, device, predictor),
|
680 |
+
inputs=[image_block, elev_preprocess_chk],
|
681 |
+
outputs=[elev_est_out],
|
682 |
+
api_name='estimate_elevation',
|
683 |
+
queue=True)
|
684 |
+
|
685 |
+
preprocess_btn = gr.Button('Run API', variant='primary', visible=False)
|
686 |
+
preprocess_btn.click(fn=partial(preprocess_api, predictor),
|
687 |
+
inputs=[image_block],
|
688 |
+
outputs=[sam_block],
|
689 |
+
api_name='preprocess',
|
690 |
+
queue=True)
|
691 |
+
|
692 |
+
gen_mesh_btn = gr.Button('Run API', variant='primary', visible=False)
|
693 |
+
gen_mesh_btn.click(fn=partial(gen_mesh_api, models, predictor, device),
|
694 |
+
inputs=[image_block, elev_preprocess_chk],
|
695 |
+
outputs=[mesh_output],
|
696 |
+
api_name='generate_mesh',
|
697 |
+
queue=True)
|
698 |
+
|
699 |
update_guide = lambda GUIDE_TEXT: gr.update(value=GUIDE_TEXT)
|
700 |
|
701 |
views = [view_1, view_2, view_3, view_4, view_5, view_6, view_7, view_8]
|
instructions_12345.md
CHANGED
@@ -1,3 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
## Tuning Tips:
|
2 |
|
3 |
1. The multi-view prediction module (Zero123) operates probabilistically. If some of the predicted views are not satisfactory, you may select and regenerate them.
|
|
|
1 |
+
## APIs:
|
2 |
+
<details>
|
3 |
+
<summary>We offer handy APIs for our pipeline and its components.</summary>
|
4 |
+
|
5 |
+
```python
|
6 |
+
from gradio_client import Client
|
7 |
+
client = Client("https://one-2-3-45-one-2-3-45.hf.space/")
|
8 |
+
input_img_path = "https://huggingface.co/spaces/One-2-3-45/One-2-3-45/resolve/main/demo_examples/01_wild_hydrant.png"
|
9 |
+
|
10 |
+
### Single image to 3D mesh
|
11 |
+
generated_mesh_filepath = client.predict(
|
12 |
+
input_img_path,
|
13 |
+
True, # image preprocessing
|
14 |
+
api_name="/generate_mesh"
|
15 |
+
)
|
16 |
+
|
17 |
+
### Elevation estimation
|
18 |
+
# DON'T TO ASK USERS TO ESTIMATE ELEVATION! This OFF-THE-SHELF algorithm is ALL YOU NEED!
|
19 |
+
elevation_angle_deg = client.predict(
|
20 |
+
input_img_path,
|
21 |
+
True, # image preprocessing
|
22 |
+
api_name="/estimate_elevation"
|
23 |
+
)
|
24 |
+
|
25 |
+
### Image preprocessing: segment, rescale, and recenter
|
26 |
+
segmented_img_filepath = client.predict(
|
27 |
+
input_img_path,
|
28 |
+
api_name="/preprocess"
|
29 |
+
)
|
30 |
+
```
|
31 |
+
</details>
|
32 |
+
|
33 |
## Tuning Tips:
|
34 |
|
35 |
1. The multi-view prediction module (Zero123) operates probabilistically. If some of the predicted views are not satisfactory, you may select and regenerate them.
|