Skip to content

Commit 3fddffb

Browse files
api changes updates
Video mode fixes Video mode fixes
1 parent 2d9812a commit 3fddffb

File tree

6 files changed

+162
-80
lines changed

6 files changed

+162
-80
lines changed

scripts/depthmap_api.py

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,12 @@
22
# (will only be on with --api starting option)
33
# Currently no API stability guarantees are provided - API may break on any new commit.
44

5-
import numpy as np
6-
from fastapi import FastAPI, Body
7-
from fastapi.exceptions import HTTPException
8-
from PIL import Image
9-
10-
import gradio as gr
11-
12-
from modules.api.models import List, Dict
13-
from modules.api import api
14-
15-
from src.core import core_generation_funnel
16-
from src.misc import SCRIPT_VERSION
175
from src import backbone
18-
from src.common_constants import GenerationOptions as go
19-
20-
21-
def encode_to_base64(image):
22-
if type(image) is str:
23-
return image
24-
elif type(image) is Image.Image:
25-
return api.encode_pil_to_base64(image)
26-
elif type(image) is np.ndarray:
27-
return encode_np_to_base64(image)
28-
else:
29-
return ""
30-
31-
32-
def encode_np_to_base64(image):
33-
pil = Image.fromarray(image)
34-
return api.encode_pil_to_base64(pil)
35-
36-
37-
def to_base64_PIL(encoding: str):
38-
return Image.fromarray(np.array(api.decode_base64_to_image(encoding)).astype('uint8'))
39-
40-
41-
def depth_api(_: gr.Blocks, app: FastAPI):
42-
@app.get("/depth/version")
43-
async def version():
44-
return {"version": SCRIPT_VERSION}
45-
46-
@app.get("/depth/get_options")
47-
async def get_options():
48-
return {"options": sorted([x.name.lower() for x in go])}
49-
50-
# TODO: some potential inputs not supported (like custom depthmaps)
51-
@app.post("/depth/generate")
52-
async def process(
53-
depth_input_images: List[str] = Body([], title='Input Images'),
54-
options: Dict[str, object] = Body("options", title='Generation options'),
55-
):
56-
# TODO: restrict mesh options
57-
58-
if len(depth_input_images) == 0:
59-
raise HTTPException(status_code=422, detail="No images supplied")
60-
print(f"Processing {str(len(depth_input_images))} images trough the API")
61-
62-
pil_images = []
63-
for input_image in depth_input_images:
64-
pil_images.append(to_base64_PIL(input_image))
65-
outpath = backbone.get_outpath()
66-
gen_obj = core_generation_funnel(outpath, pil_images, None, None, options)
67-
68-
results_based = []
69-
for count, type, result in gen_obj:
70-
if not isinstance(result, Image.Image):
71-
continue
72-
results_based += [encode_to_base64(result)]
73-
return {"images": results_based, "info": "Success"}
74-
6+
from src.api import api_extension
757

768
try:
779
import modules.script_callbacks as script_callbacks
7810
if backbone.get_cmd_opt('api', False):
79-
script_callbacks.on_app_started(depth_api)
11+
script_callbacks.on_app_started(api_extension.depth_api)
8012
except:
8113
print('DepthMap API could not start')

src/api/api_constants.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
api_options = {
3+
'outputs': ["depth"], # list of outputs to send in response. examples ["depth", "normalmap", 'heatmap', "normal", 'background_removed'] etc
4+
#'conversions': "", #TODO implement. it's a good idea to give some options serverside for because often that's challenging in js/clientside
5+
'save':"" #TODO implement. To save on local machine. Can be very helpful for debugging.
6+
}
7+
8+
# TODO: These are intended to be temporary
9+
api_defaults={
10+
"BOOST": False,
11+
"NET_SIZE_MATCH": True
12+
}
13+
14+
#These are enforced after user inputs
15+
api_forced={
16+
"GEN_SIMPLE_MESH": False,
17+
"GEN_INPAINTED_MESH": False
18+
}
19+
20+
#model diction TODO find a way to remove without forcing people do know indexes of models
21+
models_to_index = {
22+
'res101':0,
23+
'dpt_beit_large_512 (midas 3.1)':1,
24+
'dpt_beit_large_384 (midas 3.1)':2,
25+
'dpt_large_384 (midas 3.0)':3,
26+
'dpt_hybrid_384 (midas 3.0)':4,
27+
'midas_v21':5,
28+
'midas_v21_small':6,
29+
'zoedepth_n (indoor)':7,
30+
'zoedepth_k (outdoor)':8,
31+
'zoedepth_nk':9
32+
}

src/api/api_extension.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Non-public API. Don't host publicly - SECURITY RISKS!
2+
# (will only be on with --api starting option)
3+
# Currently no API stability guarantees are provided - API may break on any new commit.
4+
5+
import numpy as np
6+
from fastapi import FastAPI, Body
7+
from fastapi.exceptions import HTTPException
8+
from PIL import Image
9+
from itertools import tee
10+
import json
11+
12+
import gradio as gr
13+
14+
from modules.api.models import List, Dict
15+
from modules.api import api
16+
17+
from src.common_constants import GenerationOptions as go
18+
from src.core import core_generation_funnel, CoreGenerationFunnelInp
19+
from src import backbone
20+
from src.misc import SCRIPT_VERSION
21+
from src.api.api_constants import api_defaults, api_forced, api_options, models_to_index
22+
23+
def encode_to_base64(image):
24+
if type(image) is str:
25+
return image
26+
elif type(image) is Image.Image:
27+
return api.encode_pil_to_base64(image)
28+
elif type(image) is np.ndarray:
29+
return encode_np_to_base64(image)
30+
else:
31+
return ""
32+
33+
def encode_np_to_base64(image):
34+
pil = Image.fromarray(image)
35+
return api.encode_pil_to_base64(pil)
36+
37+
def to_base64_PIL(encoding: str):
38+
return Image.fromarray(np.array(api.decode_base64_to_image(encoding)).astype('uint8'))
39+
40+
41+
def api_gen(input_images, client_options):
42+
43+
default_options = CoreGenerationFunnelInp(api_defaults).values
44+
45+
#TODO try-catch type errors here
46+
for key, value in client_options.items():
47+
if key == "model_type":
48+
default_options[key] = models_to_index(value)
49+
continue
50+
default_options[key] = value
51+
52+
for key, value in api_forced.items():
53+
default_options[key.lower()] = value
54+
55+
print(f"Processing {str(len(input_images))} images through the API")
56+
57+
print(default_options)
58+
59+
pil_images = []
60+
for input_image in input_images:
61+
pil_images.append(to_base64_PIL(input_image))
62+
outpath = backbone.get_outpath()
63+
gen_obj = core_generation_funnel(outpath, pil_images, None, None, default_options)
64+
return gen_obj
65+
66+
def depth_api(_: gr.Blocks, app: FastAPI):
67+
@app.get("/depth/version")
68+
async def version():
69+
return {"version": SCRIPT_VERSION}
70+
71+
@app.get("/depth/get_options")
72+
async def get_options():
73+
return {
74+
"gen_options": [x.name.lower() for x in go],
75+
"api_options": api_options
76+
}
77+
78+
@app.post("/depth/generate")
79+
async def process(
80+
input_images: List[str] = Body([], title='Input Images'),
81+
generate_options: Dict[str, object] = Body({}, title='Generation options', options= [x.name.lower() for x in go]),
82+
api_options: Dict[str, object] = Body({'outputs': ["depth"]}, title='Api options', options= api_options)
83+
):
84+
85+
if len(input_images)==0:
86+
raise HTTPException(status_code=422, detail="No images supplied")
87+
88+
gen_obj = api_gen(input_images, generate_options)
89+
90+
#NOTE Work around yield. (Might not be necessary, not sure if yield caches)
91+
_, gen_obj = tee (gen_obj)
92+
93+
# If no outputs are specified assume depthmap is expected
94+
if len(api_options["outputs"])==0:
95+
api_options["outputs"] = ["depth"]
96+
97+
results_based = {}
98+
for output_type in api_options["outputs"]:
99+
results_per_type = []
100+
101+
for count, img_type, result in gen_obj:
102+
if img_type == output_type:
103+
results_per_type.append( encode_to_base64(result) )
104+
105+
# simpler output for simpler request.
106+
if api_options["outputs"] == ["depth"]:
107+
return {"images": results_per_type, "info": "Success"}
108+
109+
if len(results_per_type)==0:
110+
results_based[output_type] = "Check options. no img-type of " + str(type) + " where generated"
111+
else:
112+
results_based[output_type] = results_per_type
113+
return {"images": results_based, "info": "Success"}
114+

src/api/api_standalone.py

Whitespace-only changes.

src/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def convert_to_i16(arr):
4444
# uint16 conversion uses round-down, therefore values should be [0; 2**16)
4545
numbytes = 2
4646
max_val = (2 ** (8 * numbytes))
47-
out = np.clip(arr * max_val, 0, max_val - 0.1) # -0.1 from above is needed to avoid overflowing
47+
out = np.clip(arr * max_val + 0.0001, 0, max_val - 0.1) # -0.1 from above is needed to avoid overflowing
4848
return out.astype("uint16")
4949

5050
def convert_i16_to_rgb(image, like):
@@ -252,7 +252,7 @@ def core_generation_funnel(outpath, inputimages, inputdepthmaps, inputnames, inp
252252
yield count, 'depth', Image.fromarray(img_output)
253253

254254
if inp[go.GEN_STEREO]:
255-
print("Generating stereoscopic images..")
255+
# print("Generating stereoscopic image(s)..")
256256
stereoimages = create_stereoimages(
257257
inputimages[count], img_output,
258258
inp[go.STEREO_DIVERGENCE], inp[go.STEREO_SEPARATION],

src/video_mode.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,25 @@ def open_path_as_images(path, maybe_depthvideo=False):
2121
frames.append(img.convert('RGB'))
2222
return 1000 / img.info['duration'], frames
2323
if suffix in ['.avi'] and maybe_depthvideo:
24-
import imageio_ffmpeg
25-
gen = imageio_ffmpeg.read_frames(path)
2624
try:
25+
import imageio_ffmpeg
26+
# Suppose there are in fact 16 bits per pixel
27+
# If this is not the case, this is not a 16-bit depthvideo, so no need to process it this way
28+
gen = imageio_ffmpeg.read_frames(path, pix_fmt='gray16le', bits_per_pixel=16)
2729
video_info = next(gen)
2830
if video_info['pix_fmt'] == 'gray16le':
2931
width, height = video_info['size']
3032
frames = []
3133
for frame in gen:
3234
# Not sure if this is implemented somewhere else
3335
result = np.frombuffer(frame, dtype='uint16')
34-
result.shape = (height, width * 3 // 2) # Why does it work? I don't remotely have any idea.
36+
result.shape = (height, width) # Why does it work? I don't remotely have any idea.
3537
frames += [Image.fromarray(result)]
3638
# TODO: Wrapping frames into Pillow objects is wasteful
3739
return video_info['fps'], frames
3840
finally:
39-
gen.close()
41+
if 'gen' in locals():
42+
gen.close()
4043
if suffix in ['.webm', '.mp4', '.avi']:
4144
from moviepy.video.io.VideoFileClip import VideoFileClip
4245
clip = VideoFileClip(path)
@@ -45,7 +48,7 @@ def open_path_as_images(path, maybe_depthvideo=False):
4548
return clip.fps, frames
4649
else:
4750
try:
48-
return 1000, [Image.open(path)]
51+
return 1, [Image.open(path)]
4952
except Exception as e:
5053
raise Exception(f"Probably an unsupported file format: {suffix}") from e
5154

@@ -128,8 +131,8 @@ def gen_video(video, outpath, inp, custom_depthmap=None, colorvids_bitrate=None,
128131
first_pass_inp[go.DO_OUTPUT_DEPTH.name] = False
129132

130133
gen_obj = core.core_generation_funnel(None, input_images, None, None, first_pass_inp)
131-
predictions = [x[2] for x in list(gen_obj)]
132-
input_depths = process_predicitons(predictions, smoothening)
134+
input_depths = [x[2] for x in list(gen_obj)]
135+
input_depths = process_predicitons(input_depths, smoothening)
133136
else:
134137
print('Using custom depthmap video')
135138
cdm_fps, input_depths = open_path_as_images(os.path.abspath(custom_depthmap.name), maybe_depthvideo=True)
@@ -153,4 +156,5 @@ def gen_video(video, outpath, inp, custom_depthmap=None, colorvids_bitrate=None,
153156
frames_to_video(fps, imgs, outpath, f"depthmap-{backbone.get_next_sequence_number()}-{basename}",
154157
colorvids_bitrate)
155158
print('All done. Video(s) saved!')
156-
return 'Video generated!' if len(gens) == 1 else 'Videos generated!'
159+
return '<h3>Videos generated</h3>' if len(gens) > 1 else '<h3>Video generated</h3>' if len(gens) == 1 \
160+
else '<h3>Nothing generated - please check the settings and try again</h3>'

0 commit comments

Comments
 (0)