SebRincon commited on
Commit
15e4e8c
Β·
verified Β·
1 Parent(s): b599b20

Fix: graceful GPU/CPU fallback, remove spaces dependency requirement

Browse files
Files changed (3) hide show
  1. README.md +4 -6
  2. app.py +27 -29
  3. requirements.txt +0 -1
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: AnyCalib GPU
3
  emoji: "\U0001F4F7"
4
  colorFrom: indigo
5
  colorTo: blue
@@ -14,15 +14,14 @@ tags:
14
  - computer-vision
15
  - lens-correction
16
  - dinov2
17
- - gpu
18
- - zerogpu
19
  ---
20
 
21
- # AnyCalib β€” Full-Resolution GPU Camera Calibration
22
 
23
- Single-image camera calibration and lens distortion correction running on **ZeroGPU**.
24
 
25
  No quantization, no resolution limits β€” full FP32 inference with the complete AnyCalib pipeline.
 
26
 
27
  ## What it does
28
 
@@ -45,5 +44,4 @@ No quantization, no resolution limits β€” full FP32 inference with the complete
45
  - **Total**: ~320M parameters, full FP32
46
  - **Weights**: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib)
47
  - **ONNX**: [SebRincon/anycalib-onnx](https://huggingface.co/SebRincon/anycalib-onnx)
48
- - **WASM demo**: [SebRincon/anycalib-wasm](https://huggingface.co/spaces/SebRincon/anycalib-wasm)
49
  - **Source**: [github.com/javrtg/AnyCalib](https://github.com/javrtg/AnyCalib)
 
1
  ---
2
+ title: AnyCalib
3
  emoji: "\U0001F4F7"
4
  colorFrom: indigo
5
  colorTo: blue
 
14
  - computer-vision
15
  - lens-correction
16
  - dinov2
 
 
17
  ---
18
 
19
+ # AnyCalib β€” Full-Resolution Camera Calibration
20
 
21
+ Single-image camera calibration and lens distortion correction.
22
 
23
  No quantization, no resolution limits β€” full FP32 inference with the complete AnyCalib pipeline.
24
+ Automatically uses GPU when available (ZeroGPU / dedicated), falls back to CPU.
25
 
26
  ## What it does
27
 
 
44
  - **Total**: ~320M parameters, full FP32
45
  - **Weights**: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib)
46
  - **ONNX**: [SebRincon/anycalib-onnx](https://huggingface.co/SebRincon/anycalib-onnx)
 
47
  - **Source**: [github.com/javrtg/AnyCalib](https://github.com/javrtg/AnyCalib)
app.py CHANGED
@@ -1,12 +1,13 @@
1
  """
2
- AnyCalib β€” Full-Resolution GPU Camera Calibration & Lens Correction
3
 
4
- Gradio Space running the full AnyCalib pipeline on ZeroGPU:
5
  1. DINOv2 ViT-L/14 backbone β†’ LightDPT decoder β†’ ConvexTangentDecoder head
6
  2. RANSAC + Gauss-Newton calibrator β†’ camera intrinsics [f, cx, cy, k1, ...]
7
  3. Full-resolution undistortion via grid_sample
8
 
9
- No resolution limits. No quantization. Full FP32 on a real GPU.
 
10
  """
11
  from __future__ import annotations
12
 
@@ -15,11 +16,18 @@ import time
15
 
16
  import gradio as gr
17
  import numpy as np
18
- import spaces
19
  import torch
20
 
21
- # ── Load model at startup (on CPU β€” ZeroGPU moves it to GPU per-call) ──
 
 
 
 
 
 
 
22
 
 
23
  from anycalib.model.anycalib_pretrained import AnyCalib
24
  from anycalib.cameras.factory import CameraFactory
25
 
@@ -27,12 +35,10 @@ print("[anycalib] Loading model...")
27
  t0 = time.time()
28
  MODEL = AnyCalib(model_id="anycalib_gen")
29
  MODEL.eval()
30
- print(f"[anycalib] Model loaded in {time.time() - t0:.1f}s "
31
- f"({sum(p.numel() for p in MODEL.parameters()):,} params)")
32
 
33
 
34
- # ── Undistortion grid builder ──
35
-
36
  def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspective"):
37
  """Build undistortion sampling grid (mirrors AnyCalibRunner._undistort_grid)."""
38
  params_b = params[None, ...] if params.ndim == 1 else params
@@ -61,9 +67,7 @@ def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspect
61
  return grid, valid
62
 
63
 
64
- # ── Main inference function (runs on GPU via ZeroGPU) ──
65
-
66
- @spaces.GPU(duration=60)
67
  @torch.no_grad()
68
  def run_calibration(
69
  input_image: np.ndarray,
@@ -74,7 +78,7 @@ def run_calibration(
74
  interp_mode: str,
75
  k1_threshold: float,
76
  ):
77
- """Full pipeline: predict β†’ fit β†’ undistort at original resolution."""
78
 
79
  if input_image is None:
80
  raise gr.Error("Please upload an image.")
@@ -85,19 +89,19 @@ def run_calibration(
85
  h, w = input_image.shape[:2]
86
  t_total = time.time()
87
 
88
- # ── Preprocess ──
89
  x = input_image.astype("float32") / 255.0
90
- x = np.transpose(x, (2, 0, 1)) # HWC β†’ CHW
91
  x_t = torch.from_numpy(x).to(device)
92
 
93
- # ── Neural network inference ──
94
  t0 = time.time()
95
  out = MODEL.predict(x_t, cam_id=cam_id)
96
  intrinsics = out["intrinsics"]
97
  pred_size = out.get("pred_size")
98
  t_infer = time.time() - t0
99
 
100
- # ── Parse intrinsics ──
101
  camera = CameraFactory.create_from_id(cam_id)
102
  num_f = int(camera.NUM_F)
103
  intr_list = intrinsics.detach().cpu().numpy().astype(np.float64).tolist()
@@ -106,12 +110,10 @@ def run_calibration(
106
  cx_val, cy_val = intr_list[num_f], intr_list[num_f + 1]
107
  k1_val = intr_list[num_f + 2] if len(intr_list) > num_f + 2 else 0.0
108
 
109
- # FOV
110
  f_px = focal[0]
111
  fov_h = float(2 * np.degrees(np.arctan(w / (2 * f_px)))) if f_px > 0 else 0
112
  fov_v = float(2 * np.degrees(np.arctan(h / (2 * f_px)))) if f_px > 0 else 0
113
 
114
- # Distortion type
115
  if k1_val < -0.001:
116
  dist_type = "Barrel (k1 < 0)"
117
  elif k1_val > 0.001:
@@ -119,7 +121,6 @@ def run_calibration(
119
  else:
120
  dist_type = "Negligible"
121
 
122
- # ── k1 gating ──
123
  skip_undistort = k1_threshold > 0 and abs(k1_val) < k1_threshold
124
 
125
  if skip_undistort:
@@ -127,7 +128,6 @@ def run_calibration(
127
  valid_frac = 1.0
128
  t_undistort = 0.0
129
  else:
130
- # ── Undistortion at full resolution ──
131
  t0 = time.time()
132
  grid, valid = _build_undistort_grid(
133
  camera, intrinsics, h, w,
@@ -149,7 +149,8 @@ def run_calibration(
149
 
150
  t_total_elapsed = time.time() - t_total
151
 
152
- # ── Build params table ──
 
153
  params_md = f"""
154
  ### Camera Intrinsics
155
 
@@ -174,17 +175,16 @@ def run_calibration(
174
  | **Scale** | `{scale}` |
175
  | **Target projection** | `{target_proj}` |
176
 
177
- ### Timing
178
 
179
  | Stage | Time |
180
  |-------|------|
181
  | Neural net inference | `{t_infer*1000:.0f}` ms |
182
  | Undistortion (grid_sample) | `{t_undistort*1000:.0f}` ms |
183
  | **Total** | **`{t_total_elapsed*1000:.0f}` ms** |
184
- | Device | `{device}` |
185
  """
186
 
187
- # ── Raw JSON ──
188
  raw_json = json.dumps({
189
  "intrinsics": {
190
  "focal_length_px": focal,
@@ -225,12 +225,12 @@ def run_calibration(
225
  with gr.Blocks() as demo:
226
 
227
  gr.Markdown("""
228
- # AnyCalib β€” Full-Resolution GPU Camera Calibration
229
 
230
  Single-image lens calibration & distortion correction powered by
231
  [AnyCalib](https://github.com/javrtg/AnyCalib) (DINOv2 ViT-L/14 + LightDPT + ConvexTangentDecoder, ~320M params).
232
 
233
- Running on **GPU via ZeroGPU** β€” no quantization, no resolution limits, full FP32 inference.
234
 
235
  Upload any image and get the **corrected (undistorted) image** at original resolution,
236
  plus camera intrinsics, FOV, distortion parameters, and timing.
@@ -302,8 +302,6 @@ plus camera intrinsics, FOV, distortion parameters, and timing.
302
  4. Image is **undistorted at full resolution** via differentiable grid_sample
303
  5. All parameters and raw JSON output are displayed
304
 
305
- Runs in ~100-500ms on GPU depending on image size.
306
-
307
  ### Links
308
 
309
  - Raw weights: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib) (safetensors)
 
1
  """
2
+ AnyCalib β€” Full-Resolution Camera Calibration & Lens Correction
3
 
4
+ Gradio Space running the full AnyCalib pipeline:
5
  1. DINOv2 ViT-L/14 backbone β†’ LightDPT decoder β†’ ConvexTangentDecoder head
6
  2. RANSAC + Gauss-Newton calibrator β†’ camera intrinsics [f, cx, cy, k1, ...]
7
  3. Full-resolution undistortion via grid_sample
8
 
9
+ No resolution limits. No quantization. Full FP32 inference.
10
+ Runs on GPU if available (ZeroGPU / dedicated), falls back to CPU.
11
  """
12
  from __future__ import annotations
13
 
 
16
 
17
  import gradio as gr
18
  import numpy as np
 
19
  import torch
20
 
21
+ # ── GPU decorator (works on ZeroGPU Spaces, no-op elsewhere) ──
22
+ try:
23
+ import spaces
24
+ gpu_decorator = spaces.GPU(duration=120)
25
+ except (ImportError, Exception):
26
+ # Not on a ZeroGPU Space β€” use identity decorator
27
+ def gpu_decorator(fn):
28
+ return fn
29
 
30
+ # ── Load model at startup ──
31
  from anycalib.model.anycalib_pretrained import AnyCalib
32
  from anycalib.cameras.factory import CameraFactory
33
 
 
35
  t0 = time.time()
36
  MODEL = AnyCalib(model_id="anycalib_gen")
37
  MODEL.eval()
38
+ TOTAL_PARAMS = sum(p.numel() for p in MODEL.parameters())
39
+ print(f"[anycalib] Model loaded in {time.time() - t0:.1f}s ({TOTAL_PARAMS:,} params)")
40
 
41
 
 
 
42
  def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspective"):
43
  """Build undistortion sampling grid (mirrors AnyCalibRunner._undistort_grid)."""
44
  params_b = params[None, ...] if params.ndim == 1 else params
 
67
  return grid, valid
68
 
69
 
70
+ @gpu_decorator
 
 
71
  @torch.no_grad()
72
  def run_calibration(
73
  input_image: np.ndarray,
 
78
  interp_mode: str,
79
  k1_threshold: float,
80
  ):
81
+ """Full pipeline: predict -> fit -> undistort at original resolution."""
82
 
83
  if input_image is None:
84
  raise gr.Error("Please upload an image.")
 
89
  h, w = input_image.shape[:2]
90
  t_total = time.time()
91
 
92
+ # Preprocess
93
  x = input_image.astype("float32") / 255.0
94
+ x = np.transpose(x, (2, 0, 1))
95
  x_t = torch.from_numpy(x).to(device)
96
 
97
+ # Neural network inference
98
  t0 = time.time()
99
  out = MODEL.predict(x_t, cam_id=cam_id)
100
  intrinsics = out["intrinsics"]
101
  pred_size = out.get("pred_size")
102
  t_infer = time.time() - t0
103
 
104
+ # Parse intrinsics
105
  camera = CameraFactory.create_from_id(cam_id)
106
  num_f = int(camera.NUM_F)
107
  intr_list = intrinsics.detach().cpu().numpy().astype(np.float64).tolist()
 
110
  cx_val, cy_val = intr_list[num_f], intr_list[num_f + 1]
111
  k1_val = intr_list[num_f + 2] if len(intr_list) > num_f + 2 else 0.0
112
 
 
113
  f_px = focal[0]
114
  fov_h = float(2 * np.degrees(np.arctan(w / (2 * f_px)))) if f_px > 0 else 0
115
  fov_v = float(2 * np.degrees(np.arctan(h / (2 * f_px)))) if f_px > 0 else 0
116
 
 
117
  if k1_val < -0.001:
118
  dist_type = "Barrel (k1 < 0)"
119
  elif k1_val > 0.001:
 
121
  else:
122
  dist_type = "Negligible"
123
 
 
124
  skip_undistort = k1_threshold > 0 and abs(k1_val) < k1_threshold
125
 
126
  if skip_undistort:
 
128
  valid_frac = 1.0
129
  t_undistort = 0.0
130
  else:
 
131
  t0 = time.time()
132
  grid, valid = _build_undistort_grid(
133
  camera, intrinsics, h, w,
 
149
 
150
  t_total_elapsed = time.time() - t_total
151
 
152
+ hw_label = "GPU" if device.type == "cuda" else "CPU"
153
+
154
  params_md = f"""
155
  ### Camera Intrinsics
156
 
 
175
  | **Scale** | `{scale}` |
176
  | **Target projection** | `{target_proj}` |
177
 
178
+ ### Timing ({hw_label})
179
 
180
  | Stage | Time |
181
  |-------|------|
182
  | Neural net inference | `{t_infer*1000:.0f}` ms |
183
  | Undistortion (grid_sample) | `{t_undistort*1000:.0f}` ms |
184
  | **Total** | **`{t_total_elapsed*1000:.0f}` ms** |
185
+ | Hardware | `{device}` ({hw_label}) |
186
  """
187
 
 
188
  raw_json = json.dumps({
189
  "intrinsics": {
190
  "focal_length_px": focal,
 
225
  with gr.Blocks() as demo:
226
 
227
  gr.Markdown("""
228
+ # AnyCalib β€” Full-Resolution Camera Calibration
229
 
230
  Single-image lens calibration & distortion correction powered by
231
  [AnyCalib](https://github.com/javrtg/AnyCalib) (DINOv2 ViT-L/14 + LightDPT + ConvexTangentDecoder, ~320M params).
232
 
233
+ Full FP32 inference, no quantization, no resolution limits. Automatically uses GPU when available.
234
 
235
  Upload any image and get the **corrected (undistorted) image** at original resolution,
236
  plus camera intrinsics, FOV, distortion parameters, and timing.
 
302
  4. Image is **undistorted at full resolution** via differentiable grid_sample
303
  5. All parameters and raw JSON output are displayed
304
 
 
 
305
  ### Links
306
 
307
  - Raw weights: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib) (safetensors)
requirements.txt CHANGED
@@ -4,4 +4,3 @@ numpy>=1.26.0
4
  opencv-python-headless>=4.9.0
5
  anycalib @ git+https://github.com/javrtg/AnyCalib.git@3cf2e5dda92faf80f3548adaa0a8515f807848aa
6
  safetensors>=0.4.0
7
- gradio>=4.0.0
 
4
  opencv-python-headless>=4.9.0
5
  anycalib @ git+https://github.com/javrtg/AnyCalib.git@3cf2e5dda92faf80f3548adaa0a8515f807848aa
6
  safetensors>=0.4.0