Spaces:
Running on Zero
Running on Zero
Preprocess without torchvision (ZeroGPU base lacks it); show_error=True
Browse files- app.py +1 -1
- pipeline.py +52 -10
app.py
CHANGED
|
@@ -185,4 +185,4 @@ def build_demo():
|
|
| 185 |
|
| 186 |
|
| 187 |
if __name__ == "__main__":
|
| 188 |
-
build_demo().queue().launch()
|
|
|
|
| 185 |
|
| 186 |
|
| 187 |
if __name__ == "__main__":
|
| 188 |
+
build_demo().queue().launch(show_error=True)
|
pipeline.py
CHANGED
|
@@ -25,10 +25,8 @@ import torch
|
|
| 25 |
from lingbot_map.models.gct_stream import GCTStream
|
| 26 |
from lingbot_map.utils.pose_enc import pose_encoding_to_extri_intri
|
| 27 |
# (w2c kept for the GLB path → closed_form_inverse_se3_general intentionally NOT imported)
|
| 28 |
-
# NB:
|
| 29 |
-
#
|
| 30 |
-
# importable on boxes where torchvision is unavailable (e.g. a pyenv build missing
|
| 31 |
-
# _lzma) for CPU-only export testing.
|
| 32 |
|
| 33 |
# NB: import the exporter from the *submodule*, not ``lingbot_map.vis``. The package
|
| 34 |
# __init__ imports the viser-based PointCloudViewer at load time, which would force a
|
|
@@ -137,15 +135,59 @@ def preprocess_paths(
|
|
| 137 |
image_size: int = 518,
|
| 138 |
patch_size: int = 14,
|
| 139 |
) -> torch.Tensor:
|
| 140 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
Returns a tensor of shape [S, 3, H, W] in range [0, 1].
|
| 143 |
"""
|
| 144 |
-
from
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
|
| 151 |
# =============================================================================
|
|
|
|
| 25 |
from lingbot_map.models.gct_stream import GCTStream
|
| 26 |
from lingbot_map.utils.pose_enc import pose_encoding_to_extri_intri
|
| 27 |
# (w2c kept for the GLB path → closed_form_inverse_se3_general intentionally NOT imported)
|
| 28 |
+
# NB: preprocess_paths() reimplements upstream load_and_preprocess_images WITHOUT torchvision
|
| 29 |
+
# (the ZeroGPU base image ships torch but not torchvision), so nothing here imports torchvision.
|
|
|
|
|
|
|
| 30 |
|
| 31 |
# NB: import the exporter from the *submodule*, not ``lingbot_map.vis``. The package
|
| 32 |
# __init__ imports the viser-based PointCloudViewer at load time, which would force a
|
|
|
|
| 135 |
image_size: int = 518,
|
| 136 |
patch_size: int = 14,
|
| 137 |
) -> torch.Tensor:
|
| 138 |
+
"""Preprocess images to the model's canonical input — a **torchvision-free**, faithful
|
| 139 |
+
reimplementation of upstream ``load_and_preprocess_images`` (crop mode).
|
| 140 |
+
|
| 141 |
+
The ZeroGPU base image ships torch but NOT torchvision, and upstream's loader imports
|
| 142 |
+
``torchvision.transforms`` only for ``ToTensor``. We replicate the exact logic (EXIF
|
| 143 |
+
transpose, alpha-on-white, width=518, height→multiple of patch_size, center-crop height,
|
| 144 |
+
pad mismatched shapes with white) and swap ToTensor for a numpy→torch equivalent.
|
| 145 |
|
| 146 |
Returns a tensor of shape [S, 3, H, W] in range [0, 1].
|
| 147 |
"""
|
| 148 |
+
from PIL import Image, ImageOps
|
| 149 |
+
|
| 150 |
+
def to_tensor_rgb(pil_img):
|
| 151 |
+
# torchvision.transforms.ToTensor() equivalent for an 8-bit RGB image:
|
| 152 |
+
# HWC uint8 [0,255] -> CHW float32 [0,1]
|
| 153 |
+
arr = np.array(pil_img, dtype=np.uint8) # [H, W, 3] (copy → writable)
|
| 154 |
+
return torch.from_numpy(arr).permute(2, 0, 1).contiguous().float().div_(255.0)
|
| 155 |
+
|
| 156 |
+
target = image_size
|
| 157 |
+
imgs = []
|
| 158 |
+
for p in paths:
|
| 159 |
+
img = Image.open(p)
|
| 160 |
+
img = ImageOps.exif_transpose(img) # honor phone/camera EXIF
|
| 161 |
+
if img.mode == "RGBA":
|
| 162 |
+
bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
|
| 163 |
+
img = Image.alpha_composite(bg, img)
|
| 164 |
+
img = img.convert("RGB")
|
| 165 |
+
w, h = img.size
|
| 166 |
+
new_w = target
|
| 167 |
+
new_h = round(h * (new_w / w) / patch_size) * patch_size
|
| 168 |
+
img = img.resize((new_w, new_h), Image.Resampling.BICUBIC)
|
| 169 |
+
t = to_tensor_rgb(img) # [3, new_h, new_w]
|
| 170 |
+
if new_h > target: # center-crop height
|
| 171 |
+
sy = (new_h - target) // 2
|
| 172 |
+
t = t[:, sy:sy + target, :]
|
| 173 |
+
imgs.append(t)
|
| 174 |
+
|
| 175 |
+
# If frames differ in shape (mixed aspect ratios), pad to the max with white (1.0).
|
| 176 |
+
shapes = {(t.shape[1], t.shape[2]) for t in imgs}
|
| 177 |
+
if len(shapes) > 1:
|
| 178 |
+
max_h = max(s[0] for s in shapes)
|
| 179 |
+
max_w = max(s[1] for s in shapes)
|
| 180 |
+
out = []
|
| 181 |
+
for t in imgs:
|
| 182 |
+
ph, pw = max_h - t.shape[1], max_w - t.shape[2]
|
| 183 |
+
if ph > 0 or pw > 0:
|
| 184 |
+
pt, pb = ph // 2, ph - ph // 2
|
| 185 |
+
pl, pr = pw // 2, pw - pw // 2
|
| 186 |
+
t = torch.nn.functional.pad(t, (pl, pr, pt, pb), mode="constant", value=1.0)
|
| 187 |
+
out.append(t)
|
| 188 |
+
imgs = out
|
| 189 |
+
|
| 190 |
+
return torch.stack(imgs)
|
| 191 |
|
| 192 |
|
| 193 |
# =============================================================================
|