Blair Johnson commited on
Commit
f374884
·
1 Parent(s): b745045

Move max resolution to launch config

Browse files
Files changed (2) hide show
  1. README.md +9 -3
  2. app.py +73 -19
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. Local users can raise this in the UI with **Max processing pixels**.
 
 
 
 
 
 
35
 
36
  Environment variables are also available:
37
 
38
- - `DEFAULT_MAX_RESOLUTION` — default value shown in the UI.
39
- - `MAX_RESOLUTION_LIMIT` — upper clamp for the UI value.
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
- MIN_RESOLUTION_LIMIT = 10_000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, max_processing_pixels: float):
626
- input_image, load_status = load_and_resize_image(input_filepath, sanitize_max_pixels(max_processing_pixels))
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
- z_span = max(float(max_z) - float(min_z), 1e-6)
796
- return float(texture_strength) * (REFERENCE_TEXTURE_Z_SPAN / z_span)
 
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(y_length, 0, height, dtype=np.float32)
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, v2, v3], [v1, v3, v4]])
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, b_br, b_bl], [b_tl, b_tr, b_br],
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
- max_processing_pixels = gr.Number(
946
- value=DEFAULT_MAX_RESOLUTION,
947
- label="Max processing pixels",
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, max_processing_pixels],
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="0.0.0.0",
1117
- server_port=7860,
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,