Spaces:
Sleeping
Sleeping
Blair Johnson commited on
Commit ·
f374884
1
Parent(s): b745045
Move max resolution to launch config
Browse files
README.md
CHANGED
|
@@ -31,12 +31,18 @@ HEIC/HEIF uploads are decoded server-side with `pillow-heif`. If a browser canno
|
|
| 31 |
|
| 32 |
## Resolution controls
|
| 33 |
|
| 34 |
-
The app defaults to a conservative `1,500,000` processing-pixel limit for Hugging Face CPU runners.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
Environment variables are also available:
|
| 37 |
|
| 38 |
-
- `DEFAULT_MAX_RESOLUTION` — default value
|
| 39 |
-
- `MAX_RESOLUTION_LIMIT` — upper clamp for the
|
| 40 |
|
| 41 |
Example:
|
| 42 |
|
|
|
|
| 31 |
|
| 32 |
## Resolution controls
|
| 33 |
|
| 34 |
+
The app defaults to a conservative `1,500,000` processing-pixel limit for Hugging Face CPU runners. This is intentionally not user-editable in the web UI, so hosted users cannot accidentally overload the Space.
|
| 35 |
+
|
| 36 |
+
Local runners can raise the limit at launch:
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
python app.py --max-resolution-pixels 4000000
|
| 40 |
+
```
|
| 41 |
|
| 42 |
Environment variables are also available:
|
| 43 |
|
| 44 |
+
- `DEFAULT_MAX_RESOLUTION` — default launch value when no CLI flag is provided.
|
| 45 |
+
- `MAX_RESOLUTION_LIMIT` — upper clamp for the CLI/env value.
|
| 46 |
|
| 47 |
Example:
|
| 48 |
|
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import json
|
| 3 |
import os
|
|
@@ -24,9 +25,43 @@ except Exception as heif_error: # pragma: no cover - surfaced in UI if HEIC ope
|
|
| 24 |
else:
|
| 25 |
HEIF_IMPORT_ERROR = None
|
| 26 |
|
|
|
|
| 27 |
DEFAULT_MAX_RESOLUTION = int(os.getenv("DEFAULT_MAX_RESOLUTION", "1500000"))
|
| 28 |
MAX_RESOLUTION_LIMIT = int(os.getenv("MAX_RESOLUTION_LIMIT", "20000000"))
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
MODEL_ID = "depth-anything/Depth-Anything-V2-Large-hf"
|
| 31 |
|
| 32 |
DEFAULT_TEXTURE_STRENGTH = 0.1
|
|
@@ -475,6 +510,20 @@ def sanitize_max_pixels(max_pixels: Any) -> int:
|
|
| 475 |
requested = DEFAULT_MAX_RESOLUTION
|
| 476 |
return int(clamp(requested, MIN_RESOLUTION_LIMIT, MAX_RESOLUTION_LIMIT))
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
def get_lanczos_resample_filter() -> int:
|
| 479 |
return getattr(getattr(Image, "Resampling", Image), "LANCZOS")
|
| 480 |
|
|
@@ -622,8 +671,8 @@ def histogram_json(depth_map: np.ndarray, bins: int = 96) -> str:
|
|
| 622 |
return json.dumps(counts.astype(int).tolist())
|
| 623 |
|
| 624 |
|
| 625 |
-
def prepare_depth(input_filepath: str
|
| 626 |
-
input_image, load_status = load_and_resize_image(input_filepath,
|
| 627 |
depth_normalized = estimate_depth_map(input_image)
|
| 628 |
curve_json = curve_points_to_json(DEFAULT_CURVE_POINTS)
|
| 629 |
adjusted_depth = apply_depth_curve(depth_normalized, curve_json, invert_depth=False)
|
|
@@ -680,6 +729,9 @@ def update_depth_preview(
|
|
| 680 |
min_z: float,
|
| 681 |
max_z: float,
|
| 682 |
):
|
|
|
|
|
|
|
|
|
|
| 683 |
if max_z <= min_z:
|
| 684 |
raise gr.Error("Max Z-height must be greater than Min Z-height.")
|
| 685 |
smoothed_depth = get_smoothed_depth_from_state(depth_state, depth_map_smoothing)
|
|
@@ -754,6 +806,9 @@ def get_height_map_for_settings(
|
|
| 754 |
min_z: float,
|
| 755 |
max_z: float,
|
| 756 |
) -> np.ndarray:
|
|
|
|
|
|
|
|
|
|
| 757 |
normalized_curve_json = curve_points_to_json(sanitize_curve_points(curve_points_json))
|
| 758 |
cached_height_map = depth_state.get("height_map")
|
| 759 |
cache_matches = (
|
|
@@ -792,8 +847,9 @@ def get_height_map_for_settings(
|
|
| 792 |
|
| 793 |
|
| 794 |
def normalized_texture_strength(texture_strength: float, min_z: float, max_z: float) -> float:
|
| 795 |
-
|
| 796 |
-
|
|
|
|
| 797 |
|
| 798 |
|
| 799 |
def combine_depth_and_texture(
|
|
@@ -804,6 +860,7 @@ def combine_depth_and_texture(
|
|
| 804 |
min_z: float,
|
| 805 |
max_z: float,
|
| 806 |
) -> np.ndarray:
|
|
|
|
| 807 |
gray_image = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
|
| 808 |
brightness_normalized = gray_image.astype(np.float32) / 255.0
|
| 809 |
brightness_normalized = apply_smoothing(brightness_normalized, texture_smoothing)
|
|
@@ -847,7 +904,7 @@ def build_stl_mesh(z_data: np.ndarray, x_length: float, close_body: bool) -> str
|
|
| 847 |
y_length = x_length * (height / width)
|
| 848 |
vertices = np.zeros((height, width, 3), dtype=np.float32)
|
| 849 |
x_coords = np.linspace(0, x_length, width, dtype=np.float32)
|
| 850 |
-
y_coords = np.linspace(
|
| 851 |
vertices[:, :, 0] = x_coords[np.newaxis, :]
|
| 852 |
vertices[:, :, 1] = y_coords[:, np.newaxis]
|
| 853 |
vertices[:, :, 2] = z_data.astype(np.float32)
|
|
@@ -856,7 +913,7 @@ def build_stl_mesh(z_data: np.ndarray, x_length: float, close_body: bool) -> str
|
|
| 856 |
for i in range(height - 1):
|
| 857 |
for j in range(width - 1):
|
| 858 |
v1, v2, v3, v4 = vertices[i, j], vertices[i + 1, j], vertices[i + 1, j + 1], vertices[i, j + 1]
|
| 859 |
-
faces.extend([[v1,
|
| 860 |
|
| 861 |
if close_body:
|
| 862 |
v_tl = vertices[0, 0]
|
|
@@ -873,7 +930,7 @@ def build_stl_mesh(z_data: np.ndarray, x_length: float, close_body: bool) -> str
|
|
| 873 |
[v_br, b_br, b_bl], [v_br, b_bl, v_bl],
|
| 874 |
[v_bl, b_bl, b_tl], [v_bl, b_tl, v_tl],
|
| 875 |
[v_tr, b_tr, b_br], [v_tr, b_br, v_br],
|
| 876 |
-
[b_tl,
|
| 877 |
])
|
| 878 |
|
| 879 |
surface = mesh.Mesh(np.zeros(len(faces), dtype=mesh.Mesh.dtype))
|
|
@@ -897,6 +954,8 @@ def generate_3d_model_from_adjusted_depth(
|
|
| 897 |
do_pca_correction: bool,
|
| 898 |
close_body: bool,
|
| 899 |
):
|
|
|
|
|
|
|
| 900 |
if max_z <= min_z:
|
| 901 |
raise gr.Error("Max Z-height must be greater than Min Z-height.")
|
| 902 |
if x_length <= 0:
|
|
@@ -942,14 +1001,9 @@ with gr.Blocks(title="Image to 3D Relief Generator") as demo:
|
|
| 942 |
label="Upload image (PNG, JPG, WebP, TIFF, HEIC/HEIF)",
|
| 943 |
file_types=ACCEPTED_IMAGE_TYPES,
|
| 944 |
)
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
precision=0,
|
| 949 |
-
info=(
|
| 950 |
-
"Default is conservative for Hugging Face CPU runners. "
|
| 951 |
-
"Raise it locally for more detail; very high values can be slow or memory-intensive."
|
| 952 |
-
),
|
| 953 |
)
|
| 954 |
estimate_btn = gr.Button("Estimate Depth Map", variant="primary")
|
| 955 |
analysis_status = gr.Textbox(label="Depth estimation status", lines=4, interactive=False)
|
|
@@ -1022,7 +1076,7 @@ with gr.Blocks(title="Image to 3D Relief Generator") as demo:
|
|
| 1022 |
|
| 1023 |
estimate_btn.click(
|
| 1024 |
fn=prepare_depth,
|
| 1025 |
-
inputs=[input_file
|
| 1026 |
outputs=[
|
| 1027 |
depth_state,
|
| 1028 |
original_preview,
|
|
@@ -1113,8 +1167,8 @@ with gr.Blocks(title="Image to 3D Relief Generator") as demo:
|
|
| 1113 |
|
| 1114 |
if __name__ == "__main__":
|
| 1115 |
demo.queue().launch(
|
| 1116 |
-
server_name=
|
| 1117 |
-
server_port=
|
| 1118 |
theme="base",
|
| 1119 |
css=APP_CSS,
|
| 1120 |
head=CURVE_EDITOR_HEAD,
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
import gradio as gr
|
| 3 |
import json
|
| 4 |
import os
|
|
|
|
| 25 |
else:
|
| 26 |
HEIF_IMPORT_ERROR = None
|
| 27 |
|
| 28 |
+
MIN_RESOLUTION_LIMIT = 10_000
|
| 29 |
DEFAULT_MAX_RESOLUTION = int(os.getenv("DEFAULT_MAX_RESOLUTION", "1500000"))
|
| 30 |
MAX_RESOLUTION_LIMIT = int(os.getenv("MAX_RESOLUTION_LIMIT", "20000000"))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def parse_launch_args() -> argparse.Namespace:
|
| 34 |
+
parser = argparse.ArgumentParser(description="Image to 3D Relief Gradio app")
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
"--max-resolution-pixels",
|
| 37 |
+
type=int,
|
| 38 |
+
default=None,
|
| 39 |
+
help=(
|
| 40 |
+
"Maximum number of pixels processed for depth estimation. "
|
| 41 |
+
"Default comes from DEFAULT_MAX_RESOLUTION or 1,500,000."
|
| 42 |
+
),
|
| 43 |
+
)
|
| 44 |
+
parser.add_argument(
|
| 45 |
+
"--server-name",
|
| 46 |
+
default=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
|
| 47 |
+
help="Host/interface for Gradio to bind.",
|
| 48 |
+
)
|
| 49 |
+
parser.add_argument(
|
| 50 |
+
"--server-port",
|
| 51 |
+
type=int,
|
| 52 |
+
default=int(os.getenv("PORT", os.getenv("GRADIO_SERVER_PORT", "7860"))),
|
| 53 |
+
help="Port for Gradio to bind.",
|
| 54 |
+
)
|
| 55 |
+
args, _ = parser.parse_known_args()
|
| 56 |
+
return args
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
LAUNCH_ARGS = parse_launch_args()
|
| 60 |
+
REQUESTED_MAX_RESOLUTION = LAUNCH_ARGS.max_resolution_pixels or DEFAULT_MAX_RESOLUTION
|
| 61 |
+
CONFIGURED_MAX_RESOLUTION = int(max(
|
| 62 |
+
MIN_RESOLUTION_LIMIT,
|
| 63 |
+
min(MAX_RESOLUTION_LIMIT, REQUESTED_MAX_RESOLUTION),
|
| 64 |
+
))
|
| 65 |
MODEL_ID = "depth-anything/Depth-Anything-V2-Large-hf"
|
| 66 |
|
| 67 |
DEFAULT_TEXTURE_STRENGTH = 0.1
|
|
|
|
| 510 |
requested = DEFAULT_MAX_RESOLUTION
|
| 511 |
return int(clamp(requested, MIN_RESOLUTION_LIMIT, MAX_RESOLUTION_LIMIT))
|
| 512 |
|
| 513 |
+
def coerce_float(value: Any, default: float) -> float:
|
| 514 |
+
try:
|
| 515 |
+
if value is None:
|
| 516 |
+
return float(default)
|
| 517 |
+
return float(value)
|
| 518 |
+
except (TypeError, ValueError):
|
| 519 |
+
return float(default)
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def coerce_z_bounds(min_z: Any, max_z: Any) -> tuple[float, float]:
|
| 523 |
+
min_z_value = coerce_float(min_z, DEFAULT_MIN_Z)
|
| 524 |
+
max_z_value = coerce_float(max_z, DEFAULT_MAX_Z)
|
| 525 |
+
return min_z_value, max_z_value
|
| 526 |
+
|
| 527 |
def get_lanczos_resample_filter() -> int:
|
| 528 |
return getattr(getattr(Image, "Resampling", Image), "LANCZOS")
|
| 529 |
|
|
|
|
| 671 |
return json.dumps(counts.astype(int).tolist())
|
| 672 |
|
| 673 |
|
| 674 |
+
def prepare_depth(input_filepath: str):
|
| 675 |
+
input_image, load_status = load_and_resize_image(input_filepath, CONFIGURED_MAX_RESOLUTION)
|
| 676 |
depth_normalized = estimate_depth_map(input_image)
|
| 677 |
curve_json = curve_points_to_json(DEFAULT_CURVE_POINTS)
|
| 678 |
adjusted_depth = apply_depth_curve(depth_normalized, curve_json, invert_depth=False)
|
|
|
|
| 729 |
min_z: float,
|
| 730 |
max_z: float,
|
| 731 |
):
|
| 732 |
+
min_z, max_z = coerce_z_bounds(min_z, max_z)
|
| 733 |
+
texture_strength = coerce_float(texture_strength, DEFAULT_TEXTURE_STRENGTH)
|
| 734 |
+
texture_smoothing = int(coerce_float(texture_smoothing, DEFAULT_TEXTURE_SMOOTHING))
|
| 735 |
if max_z <= min_z:
|
| 736 |
raise gr.Error("Max Z-height must be greater than Min Z-height.")
|
| 737 |
smoothed_depth = get_smoothed_depth_from_state(depth_state, depth_map_smoothing)
|
|
|
|
| 806 |
min_z: float,
|
| 807 |
max_z: float,
|
| 808 |
) -> np.ndarray:
|
| 809 |
+
min_z, max_z = coerce_z_bounds(min_z, max_z)
|
| 810 |
+
texture_strength = coerce_float(texture_strength, DEFAULT_TEXTURE_STRENGTH)
|
| 811 |
+
texture_smoothing = int(coerce_float(texture_smoothing, DEFAULT_TEXTURE_SMOOTHING))
|
| 812 |
normalized_curve_json = curve_points_to_json(sanitize_curve_points(curve_points_json))
|
| 813 |
cached_height_map = depth_state.get("height_map")
|
| 814 |
cache_matches = (
|
|
|
|
| 847 |
|
| 848 |
|
| 849 |
def normalized_texture_strength(texture_strength: float, min_z: float, max_z: float) -> float:
|
| 850 |
+
min_z, max_z = coerce_z_bounds(min_z, max_z)
|
| 851 |
+
z_span = max(max_z - min_z, 1e-6)
|
| 852 |
+
return coerce_float(texture_strength, DEFAULT_TEXTURE_STRENGTH) * (REFERENCE_TEXTURE_Z_SPAN / z_span)
|
| 853 |
|
| 854 |
|
| 855 |
def combine_depth_and_texture(
|
|
|
|
| 860 |
min_z: float,
|
| 861 |
max_z: float,
|
| 862 |
) -> np.ndarray:
|
| 863 |
+
texture_smoothing = int(coerce_float(texture_smoothing, DEFAULT_TEXTURE_SMOOTHING))
|
| 864 |
gray_image = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
|
| 865 |
brightness_normalized = gray_image.astype(np.float32) / 255.0
|
| 866 |
brightness_normalized = apply_smoothing(brightness_normalized, texture_smoothing)
|
|
|
|
| 904 |
y_length = x_length * (height / width)
|
| 905 |
vertices = np.zeros((height, width, 3), dtype=np.float32)
|
| 906 |
x_coords = np.linspace(0, x_length, width, dtype=np.float32)
|
| 907 |
+
y_coords = np.linspace(0, y_length, height, dtype=np.float32)
|
| 908 |
vertices[:, :, 0] = x_coords[np.newaxis, :]
|
| 909 |
vertices[:, :, 1] = y_coords[:, np.newaxis]
|
| 910 |
vertices[:, :, 2] = z_data.astype(np.float32)
|
|
|
|
| 913 |
for i in range(height - 1):
|
| 914 |
for j in range(width - 1):
|
| 915 |
v1, v2, v3, v4 = vertices[i, j], vertices[i + 1, j], vertices[i + 1, j + 1], vertices[i, j + 1]
|
| 916 |
+
faces.extend([[v1, v3, v2], [v1, v4, v3]])
|
| 917 |
|
| 918 |
if close_body:
|
| 919 |
v_tl = vertices[0, 0]
|
|
|
|
| 930 |
[v_br, b_br, b_bl], [v_br, b_bl, v_bl],
|
| 931 |
[v_bl, b_bl, b_tl], [v_bl, b_tl, v_tl],
|
| 932 |
[v_tr, b_tr, b_br], [v_tr, b_br, v_br],
|
| 933 |
+
[b_tl, b_bl, b_br], [b_tl, b_br, b_tr],
|
| 934 |
])
|
| 935 |
|
| 936 |
surface = mesh.Mesh(np.zeros(len(faces), dtype=mesh.Mesh.dtype))
|
|
|
|
| 954 |
do_pca_correction: bool,
|
| 955 |
close_body: bool,
|
| 956 |
):
|
| 957 |
+
min_z, max_z = coerce_z_bounds(min_z, max_z)
|
| 958 |
+
x_length = coerce_float(x_length, 100.0)
|
| 959 |
if max_z <= min_z:
|
| 960 |
raise gr.Error("Max Z-height must be greater than Min Z-height.")
|
| 961 |
if x_length <= 0:
|
|
|
|
| 1001 |
label="Upload image (PNG, JPG, WebP, TIFF, HEIC/HEIF)",
|
| 1002 |
file_types=ACCEPTED_IMAGE_TYPES,
|
| 1003 |
)
|
| 1004 |
+
gr.Markdown(
|
| 1005 |
+
f"Processing limit: **{CONFIGURED_MAX_RESOLUTION:,} pixels**. "
|
| 1006 |
+
"Local runners can change this with `--max-resolution-pixels`; hosted users cannot change it from the UI."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
)
|
| 1008 |
estimate_btn = gr.Button("Estimate Depth Map", variant="primary")
|
| 1009 |
analysis_status = gr.Textbox(label="Depth estimation status", lines=4, interactive=False)
|
|
|
|
| 1076 |
|
| 1077 |
estimate_btn.click(
|
| 1078 |
fn=prepare_depth,
|
| 1079 |
+
inputs=[input_file],
|
| 1080 |
outputs=[
|
| 1081 |
depth_state,
|
| 1082 |
original_preview,
|
|
|
|
| 1167 |
|
| 1168 |
if __name__ == "__main__":
|
| 1169 |
demo.queue().launch(
|
| 1170 |
+
server_name=LAUNCH_ARGS.server_name,
|
| 1171 |
+
server_port=LAUNCH_ARGS.server_port,
|
| 1172 |
theme="base",
|
| 1173 |
css=APP_CSS,
|
| 1174 |
head=CURVE_EDITOR_HEAD,
|