Skip to content

HGGD

Own family RA-L 2023 RGB-D input equal to the reference ✓ authors' weights scored here

From an RGB-D image, it predicts a heat-map of grasp centres in 2-D, lifts each peak to 3-D and completes the grasp with a PointNet over a ball of scene points.

Paper · Reference code · Configs, results and notes

Run HGGD in five minutes

Four steps from the authors' checkpoint to grasps. A test parses each command and runs the demo and the Python code on a toy detector of the method's family.

1. Get the weights

GraspPanda publishes no weights for HGGD: the file to download is the authors' checkpoint, under its licence. Download HGGD_realsense_checkpoint (Tsinghua Cloud) and convert it once, from the root of a GraspPanda checkout:

python -m tools.convert_checkpoint HGGD_realsense_checkpoint hggd_realsense.pth \
    --source-format hggd --camera realsense
Licence, checksum and what the converter writes
  • Licence: MIT (the upstream repository); trained on GraspNet-1Billion (CC BY-NC-SA 4.0)
  • SHA-256 of the download: b95da06d8723298ad28e4c720c3a6a8af73dfe6f2e8126f43de8845bf62ad6df
  • What the converter writes: hggd_realsense.pth, every tensor of the downloaded file under the name of the module that plays its role here, no value changed, with a note of the file it came from. A tensor left over or missing stops the conversion with a report; nothing is ever loaded partially. The converter is a tool of the repository; it is not installed with the package.

2. Grasps on the example scene

grasppanda demo hggd/hggd_r34w8_g1b-realsense_15e \
    --weights hggd_realsense.pth --example --collision-filter

--example is a synthetic table-top scene shipped with GraspPanda: no dataset, no download. 14.5 s for the whole command, 72 grasps kept (one NVIDIA RTX A6000 shared with other jobs).

What it prints and writes, and every measured run

The command prints the config and its hash, how the weights loaded (every tensor matched, none missing), the test pipeline, the cloud, how many grasps the model predicted and how many the collision filter left (the config's variant cd: ModelFreeCollisionFilter then GraspNMS), and the ten best grasps. It writes into work_dirs/hggd_r34w8_g1b-realsense_15e/demo/:

  • grasps.npy: the grasps that are left, (G, 17) float32 in the GraspNet layout, best first, camera frame, metres;
  • grasps.png and grasps.gif: the cloud with the 30 best grasps, still and swinging around the scene;
  • example/: the scene as files (depth.png, rgb.png, mask.png, intrinsics.txt).

Measured with the authors' checkpoint, converted: 14.5 s for the whole command; 25,600 points to the model; 470 grasps predicted, then 287 after ModelFreeCollisionFilter → 72 after GraspNMS (2026-10-15, commit ea6268b, one NVIDIA RTX A6000 shared with other jobs). Run again at commit 0ddca31 (2026-10-15): the same counts, 16.9 s.

From Python (step 3): 72 grasps, 8.2 s for the script (2026-10-15, commit 0ddca31, one NVIDIA RTX A6000 shared with other jobs).

3. From Python, a browser or your camera

from grasppanda import GraspInferencer
from grasppanda.apis import example_scene
scene = example_scene()  # the scene of --example
inferencer = GraspInferencer(
    "hggd/hggd_r34w8_g1b-realsense_15e", "hggd_realsense.pth"
)
grasps = inferencer(
    depth=scene.depth, intrinsics=scene.intrinsics,
    depth_scale=scene.depth_scale, rgb=scene.rgb, collision_filter=True,
)
print(len(grasps), grasps.sort_by_score().to_graspnet_array()[:3])

It runs the demo's pipeline on the demo's scene and returns its 72 grasps. HGGD reads the colour image (rgb=) and takes no mask. More: From Python.

# ~/grasppanda_weights: the folder that holds hggd_realsense.pth
grasppanda explore --weights-dir ~/grasppanda_weights

Open http://127.0.0.1:8765/ and choose HGGD in the Run tab: hggd_realsense.pth is chosen for you by its name. Keep the example scene or upload a depth image with its intrinsics, and press Run: the page shows the picture, the best ten grasps, the counts through the post-processing and the command that does the same in a terminal. The server listens on your machine only and downloads nothing: Run in the browser.

grasppanda demo hggd/hggd_r34w8_g1b-realsense_15e \
    --weights hggd_realsense.pth \
    --depth work_dirs/hggd_r34w8_g1b-realsense_15e/demo/example/depth.png \
    --intrinsics work_dirs/hggd_r34w8_g1b-realsense_15e/demo/example/intrinsics.txt \
    --rgb work_dirs/hggd_r34w8_g1b-realsense_15e/demo/example/rgb.png \
    --collision-filter --out work_dirs/camera

The scene files of step 2 stand in for a camera. HGGD reads the colour image registered to the depth image (--rgb) and crops no workspace. Units, frames, the depth scale and the intrinsics: Your own camera.

4. A first number on the benchmark

With GraspNet-1Billion on disk (Datasets), score every 64th frame of the seen test scenes, 120 frames:

grasppanda test hggd/hggd_r34w8_g1b-realsense_15e \
    --weights hggd_realsense.pth \
    --opts data.test.dataset.split=test_seen data.test.dataset.frame_step=64 \
    evaluation.num_workers=4

Measured with the authors' checkpoint, converted: AP 58.16 without the collision filter and 64.14 with it, within 0.44 AP of the whole split.

What it prints, and every measured run against the whole split

It prints AP, AP0.8 and AP0.4 for every variant of the config, raw (the predictions as they are) and cd (with the collision filter), and writes a result record per variant into the run directory under work_dirs/. Testing needs no derived asset; the evaluator extracts a cache from the dataset's object models the first time it runs, which the times below do not include.

Weights, provenance Collision filter AP, 120 frames of test_seen AP, whole seen split Difference Wall time, commit
the authors' checkpoint, converted, author-ckpt off 58.16 58.33 (record) −0.17 53 s, ea6268b
on 64.14 63.70 (record) +0.44

Provenance: author-ckpt, the authors' checkpoint, evaluated by GraspPanda. Inputs: no input is derived from ground truth (the config crops no workspace). On these 120 frames the AP is within 0.44 AP of the whole split, so the subset tells you quickly whether your installation gives what the records say; it is not a benchmark number. Measured on 2026-10-15 on one NVIDIA RTX A6000 shared with other jobs.

The machine: one NVIDIA RTX A6000 (48 GB) shared with other jobs, an AMD EPYC 7262 with OMP_NUM_THREADS=3; Python 3.12, PyTorch 2.7.1 with CUDA 11.8, spconv 2.3.8.

Numbers

GraspNet-1Billion, RealSense, the whole test split: AP on seen / similar / novel, from the metafile.

Weights Collision filter Seen Similar Novel Record
reported (paper) not stated 59.36 51.20 22.17 —
reported (repository README) not stated 64.45 53.59 24.59 —
authors' weights off 58.33 47.96 22.03 record
authors' weights on 63.70 52.90 24.18 record
Where these numbers come from
  • The status of these numbers: authors' weights scored here, the authors' checkpoint, converted and scored on whole test splits; nothing trained here yet.
  • reported: paper; row 'In paper' of the repository README; repository README, row 'In repo' (also the arXiv version).
  • authors' weights, scored here: the authors' checkpoint, converted and run through grasppanda test on every frame of the test split; the record holds the per-scene values, the config hash and the hashes of code and weights.
  • Inputs: nothing derived from ground truth: HGGD crops no workspace (gt_assisted: none).
  • Other cameras, GraspClutter6D and what is known about each gap: the README; every method side by side: Model zoo.

The options of every slot

Every option that fits HGGD, except target-mask, was composed under it; those accepted were probed on real frames; the others say why.

A probe shows that a combination trains on real frames of GraspNet-1Billion, not how good it is. Accuracy is claimed only for a method trained and evaluated here.

Slot Shipped Composes Refused Runs here Explorer
Image encoder its built-in encoder 37 · 1 needs consent 0 36/37 compare
Post-processing variants cd, raw 14 · 1 no record 0 14/14 compare
Optimizer AdamW 15 2 14/15 compare
Schedule StepLR 7 0 7/7 compare
Hooks accumulate-2 5 2 4/5 compare
Augmentation its own 1 2 1/3 compare
Recipe its own 1 2 1/1 compare
Image encoder: 38 options

--set image_encoder=<option>. What each option is: the image encoder page.

Option Runs Trains like shipped Under HGGD
convnextv2-atto HGGD ✓ HGGD: worse learns ? (as shipped); predicts ✗: no grasp on any frame
convnextv2-tiny HGGD ✓ HGGD: on a par learns ? (as shipped)
dinov2-small-reg-depth-stem HGGD ✓ HGGD: on a par ✓
dinov2-small-reg HGGD ✓ HGGD: worse learns ? (as shipped)
dinov3-convnext-tiny HGGD ✓ HGGD: on a par learns ? (as shipped)
dinov3-vits16 HGGD ✓ HGGD: diverging learns ? (as shipped)
efficientnetv2-s HGGD ✓ HGGD: on a par learns ? (as shipped)
efficientvit-b1 HGGD ✓ HGGD: worse learns ? (as shipped)
eva02-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
fastvit-sa12 HGGD ✓ HGGD: on a par learns ? (as shipped)
fastvit-t8 HGGD ✓ HGGD: on a par learns ? (as shipped)
hf-convnextv2-tiny HGGD ✓ HGGD: on a par learns ? (as shipped)
hf-depth-anything-v2-small HGGD ✓ HGGD: worse ✓
hf-dinov2-small-reg HGGD ✓ HGGD: on a par learns ? (as shipped)
hf-dinov2-small HGGD ✓ HGGD: worse ✓
hf-hiera-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
hf-radio-v3-base HGGD ?
undecided (needs your consent (remote code))
not compared needs your consent: model.image_encoder: HFImageEncoder: model_args.trust_remote_code: true runs Python code downloaded from the hub repository 'nvidia/C-RADIOv3-B' on this machine. Read that repository, then state that you accept it with accept_remote_code: true (--opts model.image_encoder.accept_remote_code=true).
hf-siglip2-base HGGD ✓ HGGD: on a par learns ? (as shipped)
hf-swinv2-tiny HGGD ✓ HGGD: on a par ✓
hggd-residual own only
own in HGGD
not compared learns ? (as shipped)
hiera-small-abswin HGGD ✓ HGGD: on a par learns ? (as shipped)
maxvit-tiny HGGD ✓ HGGD: on a par learns ? (as shipped)
mobilenetv4-conv-small HGGD ✓ HGGD: on a par learns ? (as shipped)
mobilenetv4-hybrid-medium HGGD ✓ HGGD: on a par learns ? (as shipped); predicts ✗: no grasp on any frame
pe-spatial-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
repvit-m1 HGGD ✓ HGGD: worse learns ? (as shipped)
resnet18 HGGD ✓ HGGD: on a par learns ? (as shipped)
resnet34-rgbd HGGD ✓ HGGD: on a par learns ? (as shipped)
resnet34 HGGD ✓ HGGD: on a par ✓
resnet50 HGGD ✓ HGGD: on a par learns ? (as shipped)
sam2-hiera-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
siglip2-base HGGD ✓ HGGD: on a par learns ? (as shipped)
swinv2-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
tv-convnext-tiny HGGD ✓ HGGD: worse learns ? (as shipped)
tv-efficientnetv2-s HGGD ✓ HGGD: on a par learns ? (as shipped)
tv-mobilenetv3-large HGGD ✓ HGGD: on a par learns ? (as shipped)
tv-regnet-y-800mf HGGD ✓ HGGD: on a par ✓
tv-resnet34 HGGD ✓ HGGD: on a par learns ? (as shipped)

learns ? (as shipped): HGGD as shipped does not reach learns in a short run either (anchor_offset flat), so under HGGD that level says nothing about an option.

Post-processing: 15 options

--set post=<option>. What each option is: the post-processing page.

Option Runs Trains like shipped Under HGGD
approach-cone 6/6 — learns ? (as shipped)
center-gripper 6/6 — learns ? (as shipped)
collision-strict 6/6 — learns ? (as shipped)
collision 6/6 — ✓
furthest-sampling 6/6 — ✓
gripper-limits 6/6 — ✓
mesh-collision 6/6 — ✓
nms 6/6 — ✓
score-threshold 6/6 — learns ? (as shipped)
table-plane 6/6 — learns ? (as shipped)
to-base-frame 6/6 — ✓
top-k 6/6 — ✓
width-from-cloud 6/6 — ✓
workspace 6/6 — learns ? (as shipped)
target-mask no record
reads observation.masks, which no shipped dataset fills
— no record

learns ? (as shipped): HGGD as shipped does not reach learns in a short run either (anchor_offset flat), so under HGGD that level says nothing about an option.

Trains like shipped: — (post-processing trains nothing: it changes predictions, not the training).

Training recipe: 37 options (optimizer, schedule, hooks, augmentation, recipe)

Optimizer, 17 options: --set optimizer=<option>. What each option is: the optimizer page.

Option Runs Trains like shipped Under HGGD
adamw-no-decay-1d 6/6 4/6 on a par · 2 worse
worse under FineGrasp, HGGD
✓
adamw 6/6 3/6 on a par · 3 worse
worse under Scale-Balanced-Grasp, FineGrasp, HGGD
✓
ademamix 6/6 4/6 on a par · 2 worse
worse under FineGrasp, HGGD
✓
prodigy 6/6 4/6 on a par · 2 worse
worse under Scale-Balanced-Grasp, FineGrasp
✓
schedulefree-adamw 6/6 not compared learns ? (as shipped)
schedulefree-radam 6/6 not compared learns ? (as shipped)
schedulefree-sgd 3/6 not compared ✓
soap 5/6 4/5 on a par · 1 worse
worse under FineGrasp
not verified: one CUDA illegal memory access on a shared GPU
stableadamw 6/6 3/6 on a par · 3 worse
worse under GraspNet-baseline, FineGrasp, HGGD
✓
timm-adan 6/6 2/6 on a par · 1 diverging · 3 worse ✓
timm-adopt 6/6 3/6 on a par · 3 worse
worse under EconomicGrasp, FineGrasp, HGGD
learns ? (as shipped)
timm-lamb 6/6 5/6 on a par · 1 diverging
diverging under FineGrasp
✓
timm-lion 6/6 0/6 on a par · 1 diverging · 5 worse ✓
timm-madgrad 6/6 3/6 on a par · 1 diverging · 2 worse ✓
timm-mars 6/6 4/6 on a par · 2 worse
worse under FineGrasp, HGGD
learns ? (as shipped)
encoder-lr-0.1 5/5 1/5 on a par · 1 diverging · 3 worse refused (incompatible by design): it is written for the scene encoder of the seed-based skeleton (model.backbone), which HGGD does not have.
layer-decay 5/5 2/5 on a par · 1 diverging · 2 worse
worse under GSNet, EconomicGrasp; diverging under FineGrasp
refused (incompatible by design): it is written for the scene encoder of the seed-based skeleton (model.backbone), which HGGD does not have.

learns ? (as shipped): HGGD as shipped does not reach learns in a short run either (anchor_offset flat), so under HGGD that level says nothing about an option.

Who is left out, and why:

  • schedulefree-adamw, trains like shipped: not compared under GraspNet-baseline, GSNet, Scale-Balanced-Grasp, EconomicGrasp, FineGrasp, HGGD: it trained on a schedule of its own, not on that of the method as shipped.
  • schedulefree-radam, trains like shipped: not compared under GraspNet-baseline, GSNet, Scale-Balanced-Grasp, EconomicGrasp, FineGrasp, HGGD: it trained on a schedule of its own, not on that of the method as shipped.
  • schedulefree-sgd, runs: not GSNet, EconomicGrasp, FineGrasp (unstable twice in the short probe).
  • schedulefree-sgd, trains like shipped: not compared under GraspNet-baseline, Scale-Balanced-Grasp, HGGD: it trained on a schedule of its own, not on that of the method as shipped; not compared under GSNet, EconomicGrasp, FineGrasp: the swap did not run at level 1.
  • soap, runs: not HGGD (not verified: one CUDA illegal memory access on a shared GPU).
  • timm-adan, trains like shipped: worse under GSNet, EconomicGrasp, HGGD; diverging under FineGrasp.
  • timm-lion, trains like shipped: worse under GraspNet-baseline, GSNet, EconomicGrasp, FineGrasp, HGGD; diverging under Scale-Balanced-Grasp.
  • timm-madgrad, trains like shipped: diverging under GraspNet-baseline; worse under Scale-Balanced-Grasp, FineGrasp.
  • encoder-lr-0.1, trains like shipped: worse under GraspNet-baseline, GSNet, EconomicGrasp; diverging under FineGrasp.

Schedule, 7 options: --set schedule=<option>. What each option is: the schedule page.

Option Runs Trains like shipped Under HGGD
constant 5/6
not FineGrasp (unstable twice in the short probe)
— ✓
cosine-restarts 5/6
not FineGrasp (unstable twice in the short probe)
— ✓
cosine 5/6
not FineGrasp (unstable twice in the short probe)
— ✓
multistep-warmup 5/6
not FineGrasp (unstable twice in the short probe)
— learns ? (as shipped)
onecycle 6/6 — ✓
poly 5/6
not FineGrasp (unstable twice in the short probe)
— ✓
warmup-cosine 5/6
not FineGrasp (unstable twice in the short probe)
— learns ? (as shipped)

Trains like shipped: — (level 2 holds the schedule of the method as shipped fixed (a constant rate; FineGrasp: its own warm-up), so a schedule swap is not compared).

Hooks, 7 options: --set hooks=<option>. What each option is: the hooks page.

Option Runs Trains like shipped Under HGGD
accumulate-2 5/5
own in HGGD
2/5 on a par · 3 worse
worse under GraspNet-baseline, GSNet, EconomicGrasp
shipped
agc 6/6 6/6 on a par ✓
amp 0/6
not supported yet
not compared refused (not supported yet): train.amp: mixed precision is not supported by HGGD yet: its loss is not written for half-precision tensors (the detector does not declare amp_ready = True).
ema 6/6 6/6 on a par learns ? (as shipped)
grad-clip 6/6 5/6 on a par · 1 worse
worse under FineGrasp
✓
swa 4/6 4/4 on a par ✓
freeze-encoder 4/5
not FineGrasp (unstable twice in the short probe)
4/4 on a par refused (incompatible by design): it is written for the scene encoder of the seed-based skeleton (model.backbone), which HGGD does not have.

Who is left out, and why:

  • swa, runs: not GraspNet-baseline, Scale-Balanced-Grasp (not verified: the probe is shorter than the epoch at which averaging starts).
  • freeze-encoder, trains like shipped: not compared under FineGrasp: the swap did not run at level 1.

Augmentation, 3 options: --set augment=<option>. What each option is: the augmentation page.

Option Runs Trains like shipped Under HGGD
dropout 5/6 5/5 on a par learns ? (as shipped)
jitter 5/6
not HGGD (not supported yet)
5/5 on a par refused (not supported yet): data.train.dataset.transforms: RandomJitter moves the points, but this dataset section also serves image-space data (rgb, depth, view_grasps), which would stay where it was.
shift 5/6
not HGGD (not supported yet)
3/5 on a par · 2 worse
worse under EconomicGrasp, FineGrasp
refused (not supported yet): data.train.dataset.transforms: RandomShift moves the points, but this dataset section also serves image-space data (rgb, depth, view_grasps), which would stay where it was.

Who is left out, and why:

  • dropout, runs: not FineGrasp (fails: the transform order of the composed config is wrong (DepthNormals must run first)).

Recipe, 3 options: --set recipe=<option>. What each option is: the recipe page.

Option Runs Trains like shipped Under HGGD
adamw-onecycle 6/6 not compared ✓
finetune-encoder 4/5
not FineGrasp (unstable twice in the short probe)
not compared refused (incompatible by design): it is written for the scene encoder of the seed-based skeleton (model.backbone), which HGGD does not have.
ptv3-adamw-onecycle 5/5 not compared refused (incompatible by design): it needs --set backbone=ptv3 in the same request, and HGGD has no slot backbone.

Who is left out, and why:

  • adamw-onecycle, trains like shipped: not compared under GraspNet-baseline, GSNet, Scale-Balanced-Grasp, EconomicGrasp, FineGrasp, HGGD: it trained on a schedule of its own, not on that of the method as shipped.
  • finetune-encoder, trains like shipped: not compared under GraspNet-baseline, GSNet, Scale-Balanced-Grasp, EconomicGrasp: it trained on a schedule of its own, not on that of the method as shipped; not compared under FineGrasp: the swap did not run at level 1.
  • ptv3-adamw-onecycle, trains like shipped: not compared under GraspNet-baseline, GSNet, Scale-Balanced-Grasp, EconomicGrasp, FineGrasp: it trained on a schedule of its own, not on that of the method as shipped.
How to swap, and how to read these lists

Swap one option in with a single line, or list the options of a slot:

grasppanda compose hggd/hggd_r34w8_g1b-realsense_15e \
    --set <slot>=<option> -o swap.yaml
grasppanda compose hggd/hggd_r34w8_g1b-realsense_15e --list <slot>

The summary table:

  • Composes: the options the config check accepts under HGGD, the one it ships included. needs consent: an option it holds back until you accept the code it downloads (the row says how), neither accepted nor refused. no record: never composed; the row says why.
  • Refused: the options the config check refuses under HGGD; each row gives the first sentence of the reason.
  • Runs here: the swaps whose probe trained on real frames under HGGD without an error, out of every option tried there as a swap: a refusal counts, the option the method ships does not. Not counted either: encoder-lr-0.1, layer-decay, freeze-encoder, finetune-encoder and ptv3-adamw-onecycle, refused under HGGD with no probe record. Does it run? counts the same way.

The lists of each slot:

  • Runs: the methods under which the swap trained on real frames without an error, out of every method it is a swap under; the methods that ship the option are own and not counted. Who is left out, and why, stands under the chip, or below the table when it is long. Measured: 20 training iterations on 8 RealSense frames of GraspNet-1Billion (one scene), seed 0, one run per cell; nothing was measured on GraspClutter6D or on Kinect frames.
  • Trains like shipped: trained 250 iterations beside the component the method ships, its losses fall as theirs do, out of the methods it was compared under; a method under which the swap did not run, or where it trained on a schedule of its own, is named and not counted. Level 2 needs runs, not learns: a term flat after 20 iterations can fall by 250. Measured: 250 iterations on 8 RealSense frames of GraspNet-1Billion (one scene), seed 0, at a constant learning rate (FineGrasp: its own warm-up), beside the method as shipped trained the same way twice.
  • Under HGGD: ✓ when it reached every level here; level ? (as shipped) when HGGD as shipped does not reach that level either (a note below the table says why), so the level says nothing about the option; else the level it missed and why (composes, runs, learns, predicts), or why it was refused.

The levels, the refusal kinds and the thresholds: How we measure; every record: Does it run?; every comparison at level 2: Does it train like the shipped component?.

How this port differs from the reference
  • One model instance trains and predicts; the mode selects the proposal budget.
  • Camera intrinsics come from the frame, not from per-camera constants rounded to two decimals.
  • A frame without proposals, or whose regions all lack support, yields an explicit empty group (the reference writes no file, or fails). A training batch without regions still trains the image network (the reference skips the step).
  • Gradient accumulation averages the two batches where the reference sums them, so the value clip is 0.5 instead of 1 (AdamW is invariant to the scale). The reference's first step of an epoch spans three batches and it drops the gradient of the last batch; neither is reproduced.
  • In-training evaluation uses the val_dev scenes, not test scene 100.
  • The anchor label pool is per process: under torchrun each process moves its own copy of the anchors. Train the reference recipe on one GPU.

How it was checked, stage by stage: tests/integration/test_hggd_parity.py and the README.

Next