Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a8232ab
gdino-samv2: gate trajectory-overlay PNGs behind --save_traj_overlay …
jishnujayakumar May 7, 2026
30fe08a
hamer: relax scipy minimize tolerance + gate per-frame debug renders
jishnujayakumar May 7, 2026
763bab9
rfp-grasp-transfer: bump submodule to jishnu/fasten-vie@5f0fc08
jishnujayakumar May 7, 2026
f2ddb62
bundlesdf: persistent docker launch + --n_step CLI knob
jishnujayakumar May 7, 2026
e558a66
gdino-samv2: single-pass multi-bbox propagation with timing instrumen…
jishnujayakumar May 7, 2026
258ae13
hamer: warm-start Nelder-Mead from prior frame + tighter aggressive d…
jishnujayakumar May 7, 2026
25c1fa9
rfp-grasp-transfer: bump submodule for Phase 2/3 (warm-start + aggres…
jishnujayakumar May 7, 2026
a7322d8
bundlesdf: pre-cache masks, drop n_step default to 5, add per-frame t…
jishnujayakumar May 7, 2026
e32a3fa
scripts/bench_vie.sh: A/B benchmark wrapper for the four vie modules
jishnujayakumar May 7, 2026
cb205a9
vie/README: add status + upstream-project shields.io badges
jishnujayakumar May 7, 2026
238ac85
vie/README: add Benchmark section with measured + expected speedups p…
jishnujayakumar May 7, 2026
b74ba3e
rfp-grasp-transfer: bump submodule for reset() reload revert (perf fix)
jishnujayakumar May 7, 2026
064d7ba
vie/README: replace HaMeR + rfp-grasp-transfer "expected" sections wi…
jishnujayakumar May 7, 2026
9e02382
hamer + rfp Phase 4: fp16 autocast on hamer fwd + bump rfp submodule
jishnujayakumar May 7, 2026
ca516b0
vie/README: Phase 4 status — fp16 + rfp cache + jittered init + cKDTr…
jishnujayakumar May 7, 2026
427ac37
vie: bake install workarounds into setup_vie.sh + requirements + README
jishnujayakumar May 8, 2026
349a8ff
setup_vie.sh: patch chumpy __init__ for numpy 1.20+ removed type aliases
jishnujayakumar May 8, 2026
cbc84be
hamer: expose --body_detector CLI flag (vitdet vs regnety) for VRAM-t…
jishnujayakumar May 8, 2026
bcb5649
rfp-grasp-transfer: bump submodule for Phase 5 (skip source plotly wh…
jishnujayakumar May 8, 2026
bb675df
rfp-grasp-transfer: bump submodule for FASTEN_PROFILE per-stage timing
jishnujayakumar May 8, 2026
0b604b1
rfp-grasp-transfer: bump submodule for Phase 5 deepcopy snapshot (~2.…
jishnujayakumar May 8, 2026
8d28d7b
rfp-grasp-transfer: bump submodule for Phase 6 (BatchedAdamGraspTrans…
jishnujayakumar May 8, 2026
401622e
vie/README: document Phase 5 (deepcopy snapshot) + Phase 6 (BatchedAd…
jishnujayakumar May 8, 2026
7bbdf96
gdino-samv2 phase 7: --sam2_size flag for smaller-faster model variants
jishnujayakumar May 8, 2026
9637299
gdino-samv2 phase 8: DALI fast-path for init_state JPEG decode
jishnujayakumar May 8, 2026
61cf0ad
gdino-samv2: rich-based industry-grade logging + silence third-party …
jishnujayakumar May 8, 2026
5016e3d
log: add working() spinner so long ops show live progress, not silent…
jishnujayakumar May 8, 2026
37053e5
gdino-samv2: spinner-during-imports kills the ~5s startup-silence gap
jishnujayakumar May 8, 2026
ff1325e
perception: lazy-import heavy unused libs — cuts startup ~35s -> ~8s
jishnujayakumar May 8, 2026
62466bc
rfp-grasp-transfer: bump submodule for industry-grade rich logging
jishnujayakumar May 8, 2026
ff5aef5
vie: unified --save_viz flag across gdino-samv2 / hamer / rfp-grasp-t…
jishnujayakumar May 8, 2026
ebc463d
vie/README: document --save_viz, sam2_size/frame_batch_size knobs, ri…
jishnujayakumar May 8, 2026
1881ec2
vie/README: drop personal-branch attribution; this is team work
jishnujayakumar May 8, 2026
d1a3ccb
hamer: lazy-import open3d / pyrender / KMeans — startup reduction
jishnujayakumar May 8, 2026
d39693b
vie: 10.9× hamer pipeline speedup, processing/viz mode split, model c…
jishnujayakumar May 10, 2026
59d9430
vie/README: document hamer 10.9× + new flags + model cache + processi…
jishnujayakumar May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion vie/BundleSDF
276 changes: 265 additions & 11 deletions vie/README.md

Large diffs are not rendered by default.

1,258 changes: 1,097 additions & 161 deletions vie/hamer/extract_hand_bboxes_and_meshes.py

Large diffs are not rendered by default.

89 changes: 62 additions & 27 deletions vie/hamer/mesh_to_sdf/rgbd2pc.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import numpy as np
from sklearn.neighbors import KDTree
from sklearn.cluster import KMeans
import math
import pyrender
import open3d as o3d
import time
from sklearn.neighbors import KDTree
# pyrender, open3d, KMeans are lazy-imported at their use sites:
# pyrender -> compute_sdf_cost(vis=True), opt-in only
# open3d -> save_point_cloud(...), per-call
# KMeans -> __init__(use_kmeans=True), opt-in only
# Top-level imports were costing several seconds of startup on a fresh process.
# import _init_paths


class RGBD2PC:
def __init__(self, depth, intrinsic_matrix, camera_pose, target_mask=None, threshold=1.5, rgb=None, use_kmeans=False):
def __init__(self, depth, intrinsic_matrix, camera_pose, target_mask=None, threshold=1.5, rgb=None, use_kmeans=False, cluster_method="otsu_z"):
self.depth = depth
self.intrinsic_matrix = intrinsic_matrix
self.camera_pose = camera_pose
Expand All @@ -22,16 +24,44 @@ def __init__(self, depth, intrinsic_matrix, camera_pose, target_mask=None, thres
# backproject to camera
pc = self.backproject_camera(depth, intrinsic_matrix)

# kmean to keep the big cluster
# Keep the bigger of the two depth clusters. The intent is to drop
# background surfaces (wall, far floor) so the translation optimizer
# only fits against the local foreground (table + object near the hand).
#
# The original implementation ran sklearn KMeans(n_clusters=2,
# n_init="auto") in 3D — ~125 ms/frame on ~225k points. For tabletop
# scenes the foreground/background split is dominated by the z axis,
# so a 1D Otsu threshold on z gives the same partition for ~25x less
# work (~5 ms). 'kmeans' is kept as a fallback for atypical geometries.
if use_kmeans:
kmeans = KMeans(n_clusters=2, random_state=0, n_init="auto").fit(pc.T)
labels = kmeans.labels_
n0 = np.sum(labels == 0)
n1 = np.sum(labels == 1)
if n0 > n1:
pc = pc[:, labels == 0]
else:
pc = pc[:, labels == 1]
method = "kmeans" if cluster_method == "kmeans_legacy" else cluster_method
if method == "otsu_z" and pc.shape[1] > 0:
z = pc[2]
hist, edges = np.histogram(z, bins=256)
total = hist.sum()
if total > 0:
cumsum = np.cumsum(hist).astype(np.float64)
cumsum_w = np.cumsum(hist * edges[:-1]).astype(np.float64)
total_w = cumsum_w[-1]
w0 = cumsum
w1 = total - cumsum
valid = (w0 > 0) & (w1 > 0)
mu0 = np.where(valid, cumsum_w / np.maximum(w0, 1), 0)
mu1 = np.where(valid, (total_w - cumsum_w) / np.maximum(w1, 1), 0)
bcv = w0 * w1 * (mu0 - mu1) ** 2
bcv[~valid] = -1
thr = edges[np.argmax(bcv)]
near = z < thr
keep = near if near.sum() > (~near).sum() else ~near
pc = pc[:, keep]
elif method == "kmeans":
from sklearn.cluster import KMeans # lazy
kmeans = KMeans(n_clusters=2, random_state=0, n_init="auto").fit(pc.T)
labels = kmeans.labels_
n0 = np.sum(labels == 0)
n1 = np.sum(labels == 1)
pc = pc[:, labels == (0 if n0 > n1 else 1)]
# method == "none" → leave pc as-is.

# transform points to world
pc_base = camera_pose[:3, :3] @ pc + camera_pose[:3, 3].reshape((3, 1))
Expand All @@ -54,23 +84,25 @@ def get_rgbd_point_cloud(self):
colors = self.map_rgb_to_points(self.rgb)

# Create Open3D point cloud with color data
import open3d as o3d # lazy: only used by save_point_cloud / get_rgbd_point_cloud paths
pc = o3d.geometry.PointCloud()
pc.points = o3d.utility.Vector3dVector(self.points) # 3D points
pc.colors = o3d.utility.Vector3dVector(colors) # RGB colors

return pc

def map_rgb_to_points(self, rgb_image):
# Create a color map for the points based on the depth points
colors = []
for point in self.points:
# Map each 3D point to its corresponding 2D pixel in the RGB image
u, v = self.project_to_image_plane(point)
u = int(np.clip(u, 0, rgb_image.shape[1] - 1))
v = int(np.clip(v, 0, rgb_image.shape[0] - 1))
colors.append(rgb_image[v, u] / 255.0) # Normalize to [0, 1]

return np.array(colors)
# Vectorized projection: previously a Python loop over ~300k points dominated
# save_point_cloud (~3 s/frame). The math is just K @ point with z-divide.
pts = self.points
fx, fy = self.intrinsic_matrix[0, 0], self.intrinsic_matrix[1, 1]
cx, cy = self.intrinsic_matrix[0, 2], self.intrinsic_matrix[1, 2]
z = pts[:, 2]
u = np.clip(np.rint(fx * pts[:, 0] / z + cx).astype(np.int32),
0, rgb_image.shape[1] - 1)
v = np.clip(np.rint(fy * pts[:, 1] / z + cy).astype(np.int32),
0, rgb_image.shape[0] - 1)
return rgb_image[v, u] / 255.0

def project_to_image_plane(self, point):
# Project 3D point onto the image plane using the intrinsic matrix
Expand All @@ -83,9 +115,11 @@ def save_point_cloud(self, file_path):
# Get the point cloud with RGB information
pc = self.get_rgbd_point_cloud()

# Save the point cloud as a PLY file
o3d.io.write_point_cloud(file_path, pc)
# print(f"Point cloud saved to {file_path}")
# Save the point cloud as a PLY file. Binary PLY is ~10x faster to write
# for ~300k-point scene clouds and is transparent to all downstream readers
# (open3d, trimesh, plyfile). Pass write_ascii=False explicitly.
import open3d as o3d # lazy
o3d.io.write_point_cloud(file_path, pc, write_ascii=False)


def backproject_camera(self, im_depth, K):
Expand Down Expand Up @@ -132,6 +166,7 @@ def get_sdf_cost(self, query_points, epsilon=0.02, w_inside=1, vis=False):

# visualization
if vis:
import pyrender # lazy: opt-in viewer only
index = np.absolute(distances) < 0.03
points_show = query_points[index]
colors = np.zeros(points_show.shape)
Expand Down
5 changes: 4 additions & 1 deletion vie/hamer/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
'yacs',
'detectron2 @ git+https://github.com/facebookresearch/detectron2',
'chumpy @ git+https://github.com/mattloper/chumpy',
'mmcv==1.3.9',
# mmpose 0.24.0 only accepts mmcv in [1.3.8, 1.5.0]; the original 1.3.9
# pin builds with very old setuptools and breaks on newer Python toolchains.
# 1.5.0 is the highest compatible with mmpose 0.24 and installs cleanly.
'mmcv>=1.3.8,<=1.5.0',
'timm',
'einops',
'xtcocotools',
Expand Down
42 changes: 30 additions & 12 deletions vie/my_bsdf/bundlesdf_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@

class BundleSDFProcessor:
def __init__(
self, video_dir, out_folder, use_segmenter, use_gui, stride, debug_level, K
self, video_dir, out_folder, use_segmenter, use_gui, stride, debug_level, K,
n_step=None,
):
self.video_dir = video_dir
self.out_folder = out_folder
Expand All @@ -45,6 +46,7 @@ def __init__(
self.stride = stride
self.debug_level = debug_level
self.K = K
self.n_step = n_step # If set, overrides config.yml's n_step for NeRF training.
self.bundle_sdf_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "BundleSDF"
)
Expand Down Expand Up @@ -102,6 +104,8 @@ def configure_nerf(self, cfg_bundletrack):
cfg_nerf["down_scale_ratio"] = 1
cfg_nerf["fs_sdf"] = 0.1
cfg_nerf["far"] = cfg_bundletrack["depth_processing"]["zfar"]
if self.n_step is not None:
cfg_nerf["n_step"] = int(self.n_step)
cfg_nerf["datadir"] = (
f"{cfg_bundletrack['debug_dir']}/nerf_with_bundletrack_online"
)
Expand Down Expand Up @@ -144,27 +148,34 @@ def process(
os.makedirs(ob_in_cam_dir, exist_ok=True)
os.makedirs(pose_overlayed_rgb_dir, exist_ok=True)

for i in range(0, len(reader.color_files), self.stride):
# Pre-cache all masks into RAM in one pass when not using a live segmenter.
# Mask files are small (~tens of KB) and reading them inside the hot loop
# adds disk-seek latency for no benefit. With ~hundreds of frames this is
# at most a few MB of memory.
mask_cache = None
if segmenter is None:
mask_cache = []
for i in range(0, len(reader.color_files), self.stride):
m = reader.get_mask(i)
mask_cache.append((i, m))

frame_times = []
for cache_idx, i in enumerate(range(0, len(reader.color_files), self.stride)):
if rospy.is_shutdown():
break
t_frame = time.time()
color_file = reader.color_files[i]
color = cv2.imread(color_file)
H0, W0 = color.shape[:2]
depth = reader.get_depth(i)
H, W = depth.shape[:2]
color = cv2.resize(color, (W, H), interpolation=cv2.INTER_NEAREST)
depth = cv2.resize(depth, (W, H), interpolation=cv2.INTER_NEAREST)
if i == 0:
mask = reader.get_mask(0)
mask = cv2.resize(mask, (W, H), interpolation=cv2.INTER_NEAREST)
if segmenter:
mask = segmenter.run(color_file.replace("rgb", "masks"))
if segmenter:
mask = segmenter.run(color_file.replace("rgb", "masks"))
else:
if segmenter:
mask = segmenter.run(color_file.replace("rgb", "masks"))
else:
mask = reader.get_mask(i)
mask = cv2.resize(mask, (W, H), interpolation=cv2.INTER_NEAREST)
_, mask = mask_cache[cache_idx]
mask = cv2.resize(mask, (W, H), interpolation=cv2.INTER_NEAREST)
if cfg_bundletrack["erode_mask"] > 0:
kernel = np.ones(
(cfg_bundletrack["erode_mask"], cfg_bundletrack["erode_mask"]),
Expand Down Expand Up @@ -219,6 +230,7 @@ def process(
PILImg.fromarray(row1_rgb).save(
os.path.join(pose_overlayed_rgb_dir, f"{_filename}.png")
)
frame_times.append(time.time() - t_frame)

# cue (jishnu): uncomment to publish the first frame pose and ob_in_cam
# pub = rospy.Publisher("/bundleSDF/poses", Float64MultiArray, queue_size=10)
Expand All @@ -233,4 +245,10 @@ def process(
tracker.on_finish()
self.keep_relevant_files()
process_time = time.time() - start_time
if frame_times:
avg = sum(frame_times) / len(frame_times)
print(
f"[bundlesdf] processed {len(frame_times)} frames | "
f"avg {avg*1000:.1f} ms/frame | total {process_time:.1f}s"
)
return process_time
24 changes: 24 additions & 0 deletions vie/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,32 @@ mediapipe
# -------------------
# Hamer
# -------------------
# Note: mmcv 1.5.0 is installed separately by setup_vie.sh because mmpose 0.24
# requires mmcv in [1.3.8, 1.5.0] but old mmcv setup.py needs setuptools<70.
mmpose
mmengine
yacs
smplx==0.1.28
einops
jaxtyping
iopath
fvcore
omegaconf
hydra-core
pytorch_lightning
torchmetrics
timm

# transformers >=5 removed BertModel.get_head_mask which the pinned old
# GroundingDINO commit (2b62f419) needs. Pin downward.
transformers==4.47.1
huggingface_hub
tokenizers
safetensors

# Build-time pin: legacy mmcv 1.x setup.py imports pkg_resources, which newer
# setuptools dropped. Keeping it constrained here avoids the cycle.
setuptools<70

# -------------------
# rfp-grasp-transfer
Expand Down
2 changes: 1 addition & 1 deletion vie/rfp-grasp-transfer
Loading