Reorganize GeoMatch checkpoints into per-version subfolders; add RobotFingerPrint (arXiv:2409.14519) paper reproduction checkpoints
Browse files- README.md +161 -31
- geomatch_pp_checkpoint_epoch100.pth → geomatch_pp/checkpoint_epoch100.pth +0 -0
- geomatch_pp_checkpoint_epoch140.pth → geomatch_pp/checkpoint_epoch140.pth +0 -0
- geomatch_pp_checkpoint_epoch50.pth → geomatch_pp/checkpoint_epoch50.pth +0 -0
- geomatch_pp_final.pth → geomatch_pp/final.pth +0 -0
- checkpoint_epoch100.pth → geomatch_v1/checkpoint_epoch100.pth +0 -0
- checkpoint_epoch150.pth → geomatch_v1/checkpoint_epoch150.pth +0 -0
- checkpoint_epoch50.pth → geomatch_v1/checkpoint_epoch50.pth +0 -0
- geomatch_final.pth → geomatch_v1/final.pth +0 -0
- geomatch_v2_epoch100.pth → geomatch_v2/checkpoint_epoch100.pth +0 -0
- geomatch_v2_epoch150.pth → geomatch_v2/checkpoint_epoch150.pth +0 -0
- geomatch_v2_epoch50.pth → geomatch_v2/checkpoint_epoch50.pth +0 -0
- geomatch_v2_final.pth → geomatch_v2/final.pth +0 -0
- robotfingerprint/fullrobots/final.ckpt +3 -0
- robotfingerprint/model/__init__.py +0 -0
- robotfingerprint/model/grasp_network.py +162 -0
- robotfingerprint/model/loss.py +65 -0
- robotfingerprint/model/modules.py +289 -0
- robotfingerprint/unseen_barrett/final.ckpt +3 -0
- robotfingerprint/unseen_ezgripper/final.ckpt +3 -0
- robotfingerprint/unseen_shadowhand/final.ckpt +3 -0
README.md
CHANGED
|
@@ -8,13 +8,15 @@ tags:
|
|
| 8 |
- contact-prediction
|
| 9 |
- morphology
|
| 10 |
- cross-attention
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
# Graspmax — GeoMatch v2 · GeoMatch++ · GeoMatch v1
|
| 14 |
|
| 15 |
-
**Graspmax** contains geometry-aware contact prediction models for dexterous robotic
|
| 16 |
-
trained on the CMapDataset across 5 robot end-effectors
|
| 17 |
-
Allegro, ShadowHand).
|
| 18 |
|
| 19 |
> **⚠️ Version notice:** GeoMatch v1 and GeoMatch++ were trained with a corrupted
|
| 20 |
> `robot_keypoints.json` (2× scale factor and wrong shadowhand axis-swap stage).
|
|
@@ -24,11 +26,12 @@ Allegro, ShadowHand).
|
|
| 24 |
|
| 25 |
## Models at a Glance
|
| 26 |
|
| 27 |
-
| Model | Status |
|
| 28 |
|---|---|---|---|---|
|
| 29 |
-
| **GeoMatch v2** | ✅ **Recommended** | `
|
| 30 |
-
| GeoMatch++ | ⚠️ Deprecated (built on v1 encoders) | `
|
| 31 |
-
| GeoMatch v1 | ⚠️ Deprecated (corrupted keypoints) | `
|
|
|
|
| 32 |
|
| 33 |
> The lower loss/higher accuracy of v1 and GeoMatch++ are an artefact of training on corrupted
|
| 34 |
> keypoints — the 2× scale inflated keypoint distances making the contact maps geometrically
|
|
@@ -54,7 +57,26 @@ robot morphology before contact prediction. Pretrained GeoMatch v1 encoders are
|
|
| 54 |
|
| 55 |
> Based on: [*GeoMatch++: Morphology-Aware Grasping via Correspondence Learning*](https://arxiv.org/abs/2412.18998)
|
| 56 |
|
| 57 |
-
###
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
| Component | GeoMatch v1 / v2 | GeoMatch++ |
|
| 60 |
|---|---|---|
|
|
@@ -167,34 +189,80 @@ physical hand envelope) versus the corrected ~0.01 m.
|
|
| 167 |
|
| 168 |
---
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
## Checkpoints
|
| 171 |
|
| 172 |
### GeoMatch v2 ✅ (Use these)
|
| 173 |
|
| 174 |
| File | Epoch | Val Loss | Notes |
|
| 175 |
|---|---|---|---|
|
| 176 |
-
| `
|
| 177 |
-
| `
|
| 178 |
-
| `
|
| 179 |
-
| `
|
| 180 |
|
| 181 |
### GeoMatch++ ⚠️ (Deprecated)
|
| 182 |
|
| 183 |
| File | Epoch | Notes |
|
| 184 |
|---|---|---|
|
| 185 |
-
| `
|
| 186 |
-
| `
|
| 187 |
-
| `
|
| 188 |
-
| `
|
| 189 |
|
| 190 |
### GeoMatch v1 ⚠️ (Deprecated)
|
| 191 |
|
| 192 |
| File | Epoch | Notes |
|
| 193 |
|---|---|---|
|
| 194 |
-
| `checkpoint_epoch50.pth` | 50 | Early convergence |
|
| 195 |
-
| `checkpoint_epoch100.pth` | 100 | Mid-training |
|
| 196 |
-
| `checkpoint_epoch150.pth` | 150 | Near-converged |
|
| 197 |
-
| `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
---
|
| 200 |
|
|
@@ -209,7 +277,7 @@ import config
|
|
| 209 |
from models.geomatch import GeoMatch
|
| 210 |
|
| 211 |
model = GeoMatch(config).cuda()
|
| 212 |
-
model.load_state_dict(torch.load("
|
| 213 |
model.eval()
|
| 214 |
|
| 215 |
with torch.no_grad():
|
|
@@ -234,7 +302,7 @@ import config
|
|
| 234 |
from models.geomatch_pp import GeoMatchPP
|
| 235 |
|
| 236 |
model = GeoMatchPP(config).cuda()
|
| 237 |
-
model.load_state_dict(torch.load("
|
| 238 |
model.eval()
|
| 239 |
|
| 240 |
with torch.no_grad():
|
|
@@ -252,17 +320,66 @@ with torch.no_grad():
|
|
| 252 |
|
| 253 |
Morphology graphs are pre-built per robot using `preprocess_morphology.py` → `gnn_morphology_new.pt`.
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
---
|
| 256 |
|
| 257 |
## Repository Structure
|
| 258 |
|
| 259 |
```
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
generate_keypoints_json.py # Fixed keypoint generator (used for v2 training data)
|
| 267 |
```
|
| 268 |
|
|
@@ -282,11 +399,24 @@ generate_keypoints_json.py # Fixed keypoint generator (used for v2 training dat
|
|
| 282 |
journal = {arXiv preprint arXiv:2412.18998},
|
| 283 |
year = {2024},
|
| 284 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
```
|
| 286 |
|
| 287 |
---
|
| 288 |
|
| 289 |
## License
|
| 290 |
|
| 291 |
-
Original GeoMatch code © 2023 DeepMind Technologies Limited, licensed under the Apache License 2.0.
|
| 292 |
-
GeoMatch++ extension, v2 training, and all checkpoints produced by
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
- contact-prediction
|
| 9 |
- morphology
|
| 10 |
- cross-attention
|
| 11 |
+
- cvae
|
| 12 |
+
- coordinate-space
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# Graspmax — GeoMatch v2 · GeoMatch++ · GeoMatch v1 · RobotFingerPrint
|
| 16 |
|
| 17 |
+
**Graspmax** contains geometry-aware contact/coordinate prediction models for dexterous robotic
|
| 18 |
+
grasping, trained on the CMapDataset / GenDexGrasp dataset across 5 robot end-effectors
|
| 19 |
+
(EZGripper, Barrett, Robotiq 3-Finger, Allegro, ShadowHand).
|
| 20 |
|
| 21 |
> **⚠️ Version notice:** GeoMatch v1 and GeoMatch++ were trained with a corrupted
|
| 22 |
> `robot_keypoints.json` (2× scale factor and wrong shadowhand axis-swap stage).
|
|
|
|
| 26 |
|
| 27 |
## Models at a Glance
|
| 28 |
|
| 29 |
+
| Model | Status | Folder | Val loss | Val acc |
|
| 30 |
|---|---|---|---|---|
|
| 31 |
+
| **GeoMatch v2** | ✅ **Recommended** | `geomatch_v2/` | **1.594** | **0.695** |
|
| 32 |
+
| GeoMatch++ | ⚠️ Deprecated (built on v1 encoders) | `geomatch_pp/` | 0.350 | 0.940 |
|
| 33 |
+
| GeoMatch v1 | ⚠️ Deprecated (corrupted keypoints) | `geomatch_v1/` | 0.435 | 0.959 |
|
| 34 |
+
| **RobotFingerPrint** | ✅ Paper reproduction | `robotfingerprint/` | see below | — |
|
| 35 |
|
| 36 |
> The lower loss/higher accuracy of v1 and GeoMatch++ are an artefact of training on corrupted
|
| 37 |
> keypoints — the 2× scale inflated keypoint distances making the contact maps geometrically
|
|
|
|
| 57 |
|
| 58 |
> Based on: [*GeoMatch++: Morphology-Aware Grasping via Correspondence Learning*](https://arxiv.org/abs/2412.18998)
|
| 59 |
|
| 60 |
+
### RobotFingerPrint
|
| 61 |
+
|
| 62 |
+
A conditional VAE (`GcsCVAE`) that predicts a per-point **Unified Gripper Coordinate Space (UGCS)**
|
| 63 |
+
2D coordinate map over an object's point cloud, conditioned on the object geometry. Unlike GeoMatch's
|
| 64 |
+
discrete per-keypoint contact classification, RobotFingerPrint regresses a continuous (u, v)
|
| 65 |
+
coordinate for every object point, which is what allows a single trained model to transfer grasps
|
| 66 |
+
across grippers with a *different number of fingers* without any manual re-targeting.
|
| 67 |
+
|
| 68 |
+
- **Encoder**: PointNet-style per-point Conv1d stack over `(object_pc, gt_uv)` → max-pool global
|
| 69 |
+
feature → linear heads to VAE latent mean/logvar (`encoder_layers_size=[5, 64, 128, 512, 512]`,
|
| 70 |
+
`latent_size=128`)
|
| 71 |
+
- **Decoder**: per-point features + global object feature + latent code → Conv1d stack
|
| 72 |
+
(`decoder_decoder_layers_size=[64+512+128, 512, 64]`) → two parallel U/V prediction heads
|
| 73 |
+
(`uv_layers_size=[64, 32, 1]`)
|
| 74 |
+
- **Loss**: reconstruction (weighted L2 on predicted vs. ground-truth UV coordinates) + annealed
|
| 75 |
+
KL divergence (weight increased every `ann_per_epochs` epochs following a temperature schedule)
|
| 76 |
+
|
| 77 |
+
> Based on: [*RobotFingerPrint: Unified Gripper Coordinate Space for Multi-Gripper Grasp Synthesis*](https://arxiv.org/abs/2409.14519) (Khargonkar, Casas, Prabhakaran, Xiang)
|
| 78 |
+
|
| 79 |
+
### Component Comparison (GeoMatch family)
|
| 80 |
|
| 81 |
| Component | GeoMatch v1 / v2 | GeoMatch++ |
|
| 82 |
|---|---|---|
|
|
|
|
| 189 |
|
| 190 |
---
|
| 191 |
|
| 192 |
+
### RobotFingerPrint ✅ (Paper reproduction — 4 experiments)
|
| 193 |
+
|
| 194 |
+
All 4 experiments use the exact recipe published by the paper's authors (this repo's own README):
|
| 195 |
+
`--n_epochs 16 --ann_temp 1.5 --ann_per_epochs 2`, plus code defaults `lr=1e-4`, `batch_size=64`,
|
| 196 |
+
`lw_recon=1000.0`, `lw_kld=0.01`, `attn_alpha=3`, Adam(β₁=0.9, β₂=0.999), seed=42.
|
| 197 |
+
|
| 198 |
+
| Setting | Value |
|
| 199 |
+
|---|---|
|
| 200 |
+
| Dataset | GenDexGrasp CMapDataset-sqrt_align + RobotFingerPrint UGCS coordinates |
|
| 201 |
+
| Batch size | 64 |
|
| 202 |
+
| Optimizer | Adam (β₁=0.9, β₂=0.999) |
|
| 203 |
+
| Learning rate | 1e-4 (StepLR decay disabled — `decay_lr_freq=1000` > total epochs) |
|
| 204 |
+
| Epochs | 16 |
|
| 205 |
+
| KL annealing | temperature 1.5, weight increased every 2 epochs |
|
| 206 |
+
| Hardware | AMD Instinct MI300X (192 GB HBM3), ROCm 6.2.4, single GPU per run |
|
| 207 |
+
| Precision | FP32 |
|
| 208 |
+
|
| 209 |
+
| Experiment | Seen grippers | Held-out (unseen) | Training time | Final val recon loss | Final val KLD | Final val overall |
|
| 210 |
+
|---|---|---|---|---|---|---|
|
| 211 |
+
| `fullrobots/` | ezgripper, barrett, robotiq_3finger, allegro, shadowhand | none (main paper result) | 449 s | **0.2495** | 112.57 | 268.72 |
|
| 212 |
+
| `unseen_barrett/` | ezgripper, robotiq_3finger, allegro, shadowhand | barrett | 366 s | **0.2568** | 105.99 | 274.90 |
|
| 213 |
+
| `unseen_ezgripper/` | barrett, robotiq_3finger, allegro, shadowhand | ezgripper | 383 s | **0.2791** | 109.36 | 297.77 |
|
| 214 |
+
| `unseen_shadowhand/` | ezgripper, barrett, robotiq_3finger, allegro | shadowhand | 366 s | **0.2590** | 111.11 | 278.02 |
|
| 215 |
+
|
| 216 |
+
> "Val recon loss" is the reconstruction term of the CVAE loss (weighted L2 between predicted and
|
| 217 |
+
> ground-truth UGCS coordinates) — lower is better. The 3 unseen-gripper runs measure how well the
|
| 218 |
+
> model's learned coordinate space generalizes to a gripper never seen during training (evaluated
|
| 219 |
+
> only on the 4 remaining seen grippers' validation split, same as the paper's ablation setup — a
|
| 220 |
+
> full cross-gripper zero-shot transfer evaluation requires the downstream grasp generation +
|
| 221 |
+
> IsaacGym stability test pipeline, not covered by this checkpoint alone).
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
## Checkpoints
|
| 226 |
|
| 227 |
### GeoMatch v2 ✅ (Use these)
|
| 228 |
|
| 229 |
| File | Epoch | Val Loss | Notes |
|
| 230 |
|---|---|---|---|
|
| 231 |
+
| `geomatch_v2/checkpoint_epoch50.pth` | 50 | 1.675 | Early convergence |
|
| 232 |
+
| `geomatch_v2/checkpoint_epoch100.pth` | 100 | 1.649 | Mid-training |
|
| 233 |
+
| `geomatch_v2/checkpoint_epoch150.pth` | 150 | 1.603 | Near-converged |
|
| 234 |
+
| `geomatch_v2/final.pth` | 199 | 1.594 | **Final model (recommended)** |
|
| 235 |
|
| 236 |
### GeoMatch++ ⚠️ (Deprecated)
|
| 237 |
|
| 238 |
| File | Epoch | Notes |
|
| 239 |
|---|---|---|
|
| 240 |
+
| `geomatch_pp/checkpoint_epoch50.pth` | 50 | Early convergence |
|
| 241 |
+
| `geomatch_pp/checkpoint_epoch100.pth` | 100 | Mid-training |
|
| 242 |
+
| `geomatch_pp/checkpoint_epoch140.pth` | 140 | Near-converged |
|
| 243 |
+
| `geomatch_pp/final.pth` | 149 | Final (deprecated) |
|
| 244 |
|
| 245 |
### GeoMatch v1 ⚠️ (Deprecated)
|
| 246 |
|
| 247 |
| File | Epoch | Notes |
|
| 248 |
|---|---|---|
|
| 249 |
+
| `geomatch_v1/checkpoint_epoch50.pth` | 50 | Early convergence |
|
| 250 |
+
| `geomatch_v1/checkpoint_epoch100.pth` | 100 | Mid-training |
|
| 251 |
+
| `geomatch_v1/checkpoint_epoch150.pth` | 150 | Near-converged |
|
| 252 |
+
| `geomatch_v1/final.pth` | 200 | Final (deprecated) |
|
| 253 |
+
|
| 254 |
+
### RobotFingerPrint ✅ (final checkpoint only per experiment)
|
| 255 |
+
|
| 256 |
+
| File | Experiment | Notes |
|
| 257 |
+
|---|---|---|
|
| 258 |
+
| `robotfingerprint/fullrobots/final.ckpt` | All 5 grippers seen | **Main paper result — recommended** |
|
| 259 |
+
| `robotfingerprint/unseen_barrett/final.ckpt` | Barrett held out | Generalization ablation |
|
| 260 |
+
| `robotfingerprint/unseen_ezgripper/final.ckpt` | EZGripper held out | Generalization ablation |
|
| 261 |
+
| `robotfingerprint/unseen_shadowhand/final.ckpt` | ShadowHand held out | Generalization ablation |
|
| 262 |
+
|
| 263 |
+
> These are full PyTorch Lightning checkpoints (model weights + hyperparameters, no optimizer/epoch
|
| 264 |
+
> intermediates — only the last training epoch of each run is kept). Only the final epoch is
|
| 265 |
+
> published; intermediate per-epoch checkpoints are not included here.
|
| 266 |
|
| 267 |
---
|
| 268 |
|
|
|
|
| 277 |
from models.geomatch import GeoMatch
|
| 278 |
|
| 279 |
model = GeoMatch(config).cuda()
|
| 280 |
+
model.load_state_dict(torch.load("geomatch_v2/final.pth", map_location="cuda"))
|
| 281 |
model.eval()
|
| 282 |
|
| 283 |
with torch.no_grad():
|
|
|
|
| 302 |
from models.geomatch_pp import GeoMatchPP
|
| 303 |
|
| 304 |
model = GeoMatchPP(config).cuda()
|
| 305 |
+
model.load_state_dict(torch.load("geomatch_pp/final.pth", map_location="cuda"))
|
| 306 |
model.eval()
|
| 307 |
|
| 308 |
with torch.no_grad():
|
|
|
|
| 320 |
|
| 321 |
Morphology graphs are pre-built per robot using `preprocess_morphology.py` → `gnn_morphology_new.pt`.
|
| 322 |
|
| 323 |
+
### RobotFingerPrint
|
| 324 |
+
|
| 325 |
+
Lightning checkpoints store hyperparameters alongside weights, so the model reconstructs itself
|
| 326 |
+
directly from the `.ckpt` file — no separate config needed:
|
| 327 |
+
|
| 328 |
+
```python
|
| 329 |
+
import sys
|
| 330 |
+
sys.path.append(".")
|
| 331 |
+
from robotfingerprint.model.grasp_network import GcsGraspModel
|
| 332 |
+
|
| 333 |
+
model = GcsGraspModel.load_from_checkpoint("robotfingerprint/fullrobots/final.ckpt")
|
| 334 |
+
model.eval().cuda()
|
| 335 |
+
|
| 336 |
+
with torch.no_grad():
|
| 337 |
+
# input_pc: [B, N, 3] object point cloud; gt_gcs only used to establish shape during
|
| 338 |
+
# training — at inference time use model.model.predict(input_pc) for sampling-based prediction
|
| 339 |
+
pred_uv = model.model.predict(input_pc.cuda())
|
| 340 |
+
# pred_uv: [B, N, 2] predicted Unified Gripper Coordinate Space (u, v) per object point
|
| 341 |
+
```
|
| 342 |
+
|
| 343 |
+
For the full downstream pipeline (coordinate inference on held-out objects → grasp generation →
|
| 344 |
+
IsaacGym stability testing), see the
|
| 345 |
+
[RobotFingerPrint repository](https://github.com/IRVLUTD/robot-finger-print) `gcs_gdx_inf_cvae.py`
|
| 346 |
+
and `gcs_gdx_grasp_gen.py` scripts, using these checkpoints via `--logdir`/`--ckpt`.
|
| 347 |
+
|
| 348 |
---
|
| 349 |
|
| 350 |
## Repository Structure
|
| 351 |
|
| 352 |
```
|
| 353 |
+
geomatch_v1/ # GeoMatch v1 checkpoints (deprecated, corrupted keypoints)
|
| 354 |
+
checkpoint_epoch50.pth
|
| 355 |
+
checkpoint_epoch100.pth
|
| 356 |
+
checkpoint_epoch150.pth
|
| 357 |
+
final.pth
|
| 358 |
+
geomatch_pp/ # GeoMatch++ checkpoints (deprecated, built on v1 encoders)
|
| 359 |
+
checkpoint_epoch50.pth
|
| 360 |
+
checkpoint_epoch100.pth
|
| 361 |
+
checkpoint_epoch140.pth
|
| 362 |
+
final.pth
|
| 363 |
+
geomatch_v2/ # GeoMatch v2 checkpoints (recommended)
|
| 364 |
+
checkpoint_epoch50.pth
|
| 365 |
+
checkpoint_epoch100.pth
|
| 366 |
+
checkpoint_epoch150.pth
|
| 367 |
+
final.pth
|
| 368 |
+
robotfingerprint/ # RobotFingerPrint (arXiv:2409.14519) paper reproduction
|
| 369 |
+
fullrobots/final.ckpt
|
| 370 |
+
unseen_barrett/final.ckpt
|
| 371 |
+
unseen_ezgripper/final.ckpt
|
| 372 |
+
unseen_shadowhand/final.ckpt
|
| 373 |
+
model/
|
| 374 |
+
grasp_network.py # GcsGraspModel (Lightning module)
|
| 375 |
+
modules.py # GcsCVAE, PointNetCmapEncoder/Decoder
|
| 376 |
+
loss.py # GcsLoss (recon + annealed KLD)
|
| 377 |
+
models/ # Shared GeoMatch source (v1/v2/++)
|
| 378 |
+
geomatch.py
|
| 379 |
+
geomatch_pp.py
|
| 380 |
+
gnn.py
|
| 381 |
+
mlp.py
|
| 382 |
+
config.py # Hyperparameters for GeoMatch models
|
| 383 |
generate_keypoints_json.py # Fixed keypoint generator (used for v2 training data)
|
| 384 |
```
|
| 385 |
|
|
|
|
| 399 |
journal = {arXiv preprint arXiv:2412.18998},
|
| 400 |
year = {2024},
|
| 401 |
}
|
| 402 |
+
|
| 403 |
+
@article{khargonkar2024robotfingerprint,
|
| 404 |
+
title = {RobotFingerPrint: Unified Gripper Coordinate Space for Multi-Gripper Grasp Synthesis},
|
| 405 |
+
author = {Khargonkar, Ninad and Casas, Luis Felipe and Prabhakaran, Balakrishnan and Xiang, Yu},
|
| 406 |
+
journal = {arXiv preprint arXiv:2409.14519},
|
| 407 |
+
year = {2024},
|
| 408 |
+
}
|
| 409 |
```
|
| 410 |
|
| 411 |
---
|
| 412 |
|
| 413 |
## License
|
| 414 |
|
| 415 |
+
Original GeoMatch code © 2023 DeepMind Technologies Limited, licensed under the Apache License 2.0.
|
| 416 |
+
GeoMatch++ extension, v2 training, and all GeoMatch checkpoints produced by
|
| 417 |
+
[Dimios45](https://huggingface.co/Dimios45) as part of the Graspmax project.
|
| 418 |
+
|
| 419 |
+
RobotFingerPrint model source (`robotfingerprint/model/`) is from the original authors'
|
| 420 |
+
[repository](https://github.com/IRVLUTD/robot-finger-print) (MIT-style license, see their repo for
|
| 421 |
+
exact terms); checkpoints in `robotfingerprint/` were trained by
|
| 422 |
+
[Dimios45](https://huggingface.co/Dimios45) reproducing the paper's published recipe.
|
geomatch_pp_checkpoint_epoch100.pth → geomatch_pp/checkpoint_epoch100.pth
RENAMED
|
File without changes
|
geomatch_pp_checkpoint_epoch140.pth → geomatch_pp/checkpoint_epoch140.pth
RENAMED
|
File without changes
|
geomatch_pp_checkpoint_epoch50.pth → geomatch_pp/checkpoint_epoch50.pth
RENAMED
|
File without changes
|
geomatch_pp_final.pth → geomatch_pp/final.pth
RENAMED
|
File without changes
|
checkpoint_epoch100.pth → geomatch_v1/checkpoint_epoch100.pth
RENAMED
|
File without changes
|
checkpoint_epoch150.pth → geomatch_v1/checkpoint_epoch150.pth
RENAMED
|
File without changes
|
checkpoint_epoch50.pth → geomatch_v1/checkpoint_epoch50.pth
RENAMED
|
File without changes
|
geomatch_final.pth → geomatch_v1/final.pth
RENAMED
|
File without changes
|
geomatch_v2_epoch100.pth → geomatch_v2/checkpoint_epoch100.pth
RENAMED
|
File without changes
|
geomatch_v2_epoch150.pth → geomatch_v2/checkpoint_epoch150.pth
RENAMED
|
File without changes
|
geomatch_v2_epoch50.pth → geomatch_v2/checkpoint_epoch50.pth
RENAMED
|
File without changes
|
geomatch_v2_final.pth → geomatch_v2/final.pth
RENAMED
|
File without changes
|
robotfingerprint/fullrobots/final.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:51dba9d45120fbdd3db72b92f90ab9f6c4818ed68b3f0bdf2408df504c86df69
|
| 3 |
+
size 11523772
|
robotfingerprint/model/__init__.py
ADDED
|
File without changes
|
robotfingerprint/model/grasp_network.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import lightning as L
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from .modules import GcsCVAE
|
| 5 |
+
from .loss import GcsLoss
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class GcsGraspModel(L.LightningModule):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
learning_rate: float = 1e-4,
|
| 12 |
+
cmap_loss_wrecon: float = 100.0,
|
| 13 |
+
cmap_loss_wkld: float = 0.1,
|
| 14 |
+
cmap_loss_temp: float = 1.5,
|
| 15 |
+
cmap_loss_ann_per_epoch: int = 2,
|
| 16 |
+
pred_type: str = "gcs",
|
| 17 |
+
loss_attn_weight: float = 3,
|
| 18 |
+
decay_lr_freq: int = 1000,
|
| 19 |
+
):
|
| 20 |
+
super().__init__()
|
| 21 |
+
assert pred_type in {"cmap", "gcs", "gcs+cmap", "gcs_u", "gcs_v"}
|
| 22 |
+
|
| 23 |
+
self.lr = learning_rate
|
| 24 |
+
self.loss_criterion = GcsLoss(
|
| 25 |
+
cmap_loss_wrecon,
|
| 26 |
+
cmap_loss_wkld,
|
| 27 |
+
cmap_loss_temp,
|
| 28 |
+
cmap_loss_ann_per_epoch,
|
| 29 |
+
loss_attn_weight,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
self.pred_type = pred_type
|
| 33 |
+
self.decay_lr_freq = decay_lr_freq
|
| 34 |
+
if pred_type == "gcs":
|
| 35 |
+
input_size = 5 # B,N,3 pts and N, 2 gcs uvmap
|
| 36 |
+
output_size = (
|
| 37 |
+
64 # B, N, 64 global feats --> gets mapped to B, N, 2 uv preds
|
| 38 |
+
)
|
| 39 |
+
else:
|
| 40 |
+
raise NotImplementedError
|
| 41 |
+
|
| 42 |
+
self.model = GcsCVAE(
|
| 43 |
+
encoder_layers_size=[input_size, 64, 128, 512, 512],
|
| 44 |
+
decoder_decoder_layers_size=[64 + 512 + 128, 512, 64],
|
| 45 |
+
uv_layers_size=[64, 32, 1],
|
| 46 |
+
)
|
| 47 |
+
self.cmap_loss_ann_per_epoch = cmap_loss_ann_per_epoch
|
| 48 |
+
|
| 49 |
+
print(
|
| 50 |
+
"Hparams:",
|
| 51 |
+
decay_lr_freq,
|
| 52 |
+
loss_attn_weight,
|
| 53 |
+
cmap_loss_ann_per_epoch,
|
| 54 |
+
cmap_loss_temp,
|
| 55 |
+
learning_rate,
|
| 56 |
+
)
|
| 57 |
+
self.save_hyperparameters()
|
| 58 |
+
|
| 59 |
+
def get_inputs(self, batch):
|
| 60 |
+
input_pc = batch["full_pc"] # (B, N, 3)
|
| 61 |
+
target_cmap = batch["cmaps_fullpc"] # (B, N, 1)
|
| 62 |
+
target_gcs = batch["gcs_fullpc"] # (B, N, 2)
|
| 63 |
+
|
| 64 |
+
if self.pred_type == "cmap":
|
| 65 |
+
target_map = target_cmap
|
| 66 |
+
elif self.pred_type == "gcs_u":
|
| 67 |
+
target_map = target_gcs[:, :, :1]
|
| 68 |
+
elif self.pred_type == "gcs_v":
|
| 69 |
+
target_map = target_gcs[:, :, 1:]
|
| 70 |
+
elif self.pred_type == "gcs":
|
| 71 |
+
target_map = target_gcs
|
| 72 |
+
elif self.pred_type == "gcs+cmap":
|
| 73 |
+
target_map = torch.cat(
|
| 74 |
+
(
|
| 75 |
+
target_gcs,
|
| 76 |
+
target_cmap.unsqueeze(-1) if target_cmap.ndim == 2 else target_cmap,
|
| 77 |
+
),
|
| 78 |
+
dim=-1,
|
| 79 |
+
)
|
| 80 |
+
return input_pc, target_map, target_cmap, target_gcs
|
| 81 |
+
|
| 82 |
+
def forward(self, input_pc, gt_gcs):
|
| 83 |
+
return self.model(input_pc, gt_gcs)
|
| 84 |
+
|
| 85 |
+
def training_step(self, batch, batch_idx):
|
| 86 |
+
|
| 87 |
+
input_pc, target_map, target_cmap, target_gcs = self.get_inputs(batch)
|
| 88 |
+
|
| 89 |
+
_, pred_map, means, logvars, z_latent_code = self.model(input_pc, target_map)
|
| 90 |
+
|
| 91 |
+
# For training use the attention weights dictated by the gt cmap
|
| 92 |
+
loss_cmap, loss_recon, loss_kld = self.loss_criterion(
|
| 93 |
+
means, logvars, target_map, pred_map, target_cmap
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
loss = loss_cmap
|
| 97 |
+
loss_dict = {
|
| 98 |
+
"loss/trn/overall": loss.item(),
|
| 99 |
+
"loss/trn/cmap_rec": loss_recon.item(),
|
| 100 |
+
"loss/trn/cmap_kld": loss_kld.item(),
|
| 101 |
+
}
|
| 102 |
+
self.log_dict(
|
| 103 |
+
loss_dict,
|
| 104 |
+
on_step=True,
|
| 105 |
+
on_epoch=False,
|
| 106 |
+
logger=True,
|
| 107 |
+
)
|
| 108 |
+
self.log("trn_loss", loss_recon.item(), prog_bar=True)
|
| 109 |
+
return loss
|
| 110 |
+
|
| 111 |
+
def on_train_epoch_end(self):
|
| 112 |
+
if (self.current_epoch + 1) % self.cmap_loss_ann_per_epoch == 0:
|
| 113 |
+
self.loss_criterion.update_kld_weight()
|
| 114 |
+
|
| 115 |
+
def validation_step(self, batch, batch_idx):
|
| 116 |
+
input_pc, target_map, target_cmap, target_gcs = self.get_inputs(batch)
|
| 117 |
+
_, pred_map, means, logvars, z_latent_code = self.model(input_pc, target_map)
|
| 118 |
+
|
| 119 |
+
loss_cmap, loss_recon, loss_kld = self.loss_criterion(
|
| 120 |
+
means, logvars, target_map, pred_map, target_cmap
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
loss = loss_cmap
|
| 124 |
+
|
| 125 |
+
loss_dict = {
|
| 126 |
+
"loss/val/overall": loss.item(),
|
| 127 |
+
"loss/val/cmap_rec": loss_recon.item(),
|
| 128 |
+
"loss/val/cmap_kld": loss_kld.item(),
|
| 129 |
+
}
|
| 130 |
+
self.log_dict(
|
| 131 |
+
loss_dict,
|
| 132 |
+
logger=True,
|
| 133 |
+
on_epoch=True,
|
| 134 |
+
on_step=False,
|
| 135 |
+
sync_dist=True,
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
def test_step(self, batch, batch_idx):
|
| 139 |
+
input_pc, target_map, target_cmap, target_gcs = self.get_inputs(batch)
|
| 140 |
+
_, pred_map, means, logvars, z_latent_code = self.model(input_pc, target_map)
|
| 141 |
+
loss_cmap, loss_recon, loss_kld = self.loss_criterion(
|
| 142 |
+
means, logvars, target_map, pred_map
|
| 143 |
+
)
|
| 144 |
+
loss = loss_cmap
|
| 145 |
+
|
| 146 |
+
loss_dict = {
|
| 147 |
+
"loss/tst/overall": loss,
|
| 148 |
+
"loss/tst/cmap_rec": loss_recon,
|
| 149 |
+
"loss/tst/cmap_kld": loss_kld,
|
| 150 |
+
}
|
| 151 |
+
self.log_dict(loss_dict, logger=True, sync_dist=True)
|
| 152 |
+
|
| 153 |
+
def predict_step(self, batch, batch_idx):
|
| 154 |
+
input_pc, _, _, _ = self.get_inputs(batch)
|
| 155 |
+
return self.model.predict(input_pc)
|
| 156 |
+
|
| 157 |
+
def configure_optimizers(self):
|
| 158 |
+
optimizer = torch.optim.Adam(self.parameters(), lr=self.lr, betas=(0.9, 0.999))
|
| 159 |
+
lr_scheduler = torch.optim.lr_scheduler.StepLR(
|
| 160 |
+
optimizer, step_size=self.decay_lr_freq, gamma=0.2
|
| 161 |
+
)
|
| 162 |
+
return [optimizer], [lr_scheduler]
|
robotfingerprint/model/loss.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch.nn import functional as F
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class GcsLoss(nn.Module):
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
lw_init_recon: float,
|
| 10 |
+
lw_init_kld: float,
|
| 11 |
+
ann_temp: float,
|
| 12 |
+
ann_per_epochs: int = 1,
|
| 13 |
+
attn_weight: float = 3,
|
| 14 |
+
):
|
| 15 |
+
super(GcsLoss, self).__init__()
|
| 16 |
+
self.lw_recon = lw_init_recon
|
| 17 |
+
self.lw_kld = lw_init_kld
|
| 18 |
+
self.ann_temp = ann_temp
|
| 19 |
+
self.iter_counter = 0
|
| 20 |
+
self.ann_per_epochs = ann_per_epochs
|
| 21 |
+
self.use_attn = attn_weight > 1
|
| 22 |
+
self.attn_alpha = attn_weight
|
| 23 |
+
|
| 24 |
+
def forward(self, means, logvars, uv_coords_gt, uv_coords_pred, cmap_gt=None):
|
| 25 |
+
"""
|
| 26 |
+
:param means:
|
| 27 |
+
:param logvars:
|
| 28 |
+
:param uv_coords_gt: (B, N, 2+)
|
| 29 |
+
:param uv_coords_pred: (B, N, 2+)
|
| 30 |
+
|
| 31 |
+
:return:
|
| 32 |
+
loss, loss_reconstruction, loss_KL_Divergence
|
| 33 |
+
"""
|
| 34 |
+
loss_kld = (
|
| 35 |
+
-0.5 * torch.sum(1 + logvars - means.pow(2) - logvars.exp(), dim=-1).mean()
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
if self.use_attn and cmap_gt is not None:
|
| 39 |
+
square_error = torch.square(uv_coords_gt - uv_coords_pred).sum(dim=-1)
|
| 40 |
+
attention_weights = torch.exp(cmap_gt.squeeze(-1) * self.attn_alpha)
|
| 41 |
+
square_error = square_error * attention_weights
|
| 42 |
+
# now we (B, N) sum-> (B, ) div-> (B,) .mean() -> (scalar)
|
| 43 |
+
loss_recon = torch.sqrt(
|
| 44 |
+
(square_error.sum(dim=-1) / attention_weights.sum(dim=-1))
|
| 45 |
+
).mean()
|
| 46 |
+
else:
|
| 47 |
+
loss_recon = torch.sqrt(
|
| 48 |
+
torch.square(uv_coords_gt - uv_coords_pred).sum(dim=-1)
|
| 49 |
+
).mean()
|
| 50 |
+
|
| 51 |
+
loss = self.lw_kld * loss_kld + self.lw_recon * loss_recon
|
| 52 |
+
return loss, loss_recon, loss_kld
|
| 53 |
+
|
| 54 |
+
@staticmethod
|
| 55 |
+
def metrics(self, uv_coords_gt, uv_coords_pred):
|
| 56 |
+
return F.smooth_l1_loss(uv_coords_gt, uv_coords_pred, reduction="mean")
|
| 57 |
+
|
| 58 |
+
def update_kld_weight(self):
|
| 59 |
+
self.lw_kld *= self.ann_temp
|
| 60 |
+
|
| 61 |
+
def apply_iter(self):
|
| 62 |
+
# NOT USED with Pytorch Lightning mode
|
| 63 |
+
if self.iter_counter % self.ann_per_epochs == self.ann_per_epochs - 1:
|
| 64 |
+
self.lw_kld *= self.ann_temp
|
| 65 |
+
self.iter_counter += 1
|
robotfingerprint/model/modules.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
class PointNetCmapEncoder(nn.Module):
|
| 5 |
+
def __init__(self, layers_size=[4, 64, 128, 512]):
|
| 6 |
+
super(PointNetCmapEncoder, self).__init__()
|
| 7 |
+
self.layers_size = layers_size
|
| 8 |
+
self.conv_layers = nn.ModuleList()
|
| 9 |
+
self.bn_layers = nn.ModuleList()
|
| 10 |
+
self.activate_func = nn.ReLU()
|
| 11 |
+
|
| 12 |
+
for i in range(len(layers_size) - 1):
|
| 13 |
+
self.conv_layers.append(nn.Conv1d(layers_size[i], layers_size[i + 1], 1))
|
| 14 |
+
self.bn_layers.append(nn.BatchNorm1d(layers_size[i + 1]))
|
| 15 |
+
nn.init.xavier_normal_(self.conv_layers[-1].weight)
|
| 16 |
+
|
| 17 |
+
def forward(self, x):
|
| 18 |
+
# input: B * N * 4
|
| 19 |
+
# output: B * latent_size
|
| 20 |
+
x = x.transpose(1, 2)
|
| 21 |
+
for i in range(len(self.conv_layers) - 1):
|
| 22 |
+
x = self.conv_layers[i](x)
|
| 23 |
+
x = self.bn_layers[i](x)
|
| 24 |
+
x = self.activate_func(x)
|
| 25 |
+
x = self.bn_layers[-1](self.conv_layers[-1](x))
|
| 26 |
+
x = torch.max(x, 2, keepdim=True)[0]
|
| 27 |
+
x = x.view(-1, self.layers_size[-1])
|
| 28 |
+
return x
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class PointNetCmapDecoder(nn.Module):
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
global_feat_size=512,
|
| 35 |
+
latent_size=128,
|
| 36 |
+
pointwise_layers_size=[3, 64, 64],
|
| 37 |
+
global_layers_size=[64, 128, 512],
|
| 38 |
+
decoder_layers_size=[64 + 512 + 128, 512, 64, 64, 1],
|
| 39 |
+
):
|
| 40 |
+
super(PointNetCmapDecoder, self).__init__()
|
| 41 |
+
assert global_feat_size == global_layers_size[-1]
|
| 42 |
+
assert (
|
| 43 |
+
decoder_layers_size[0]
|
| 44 |
+
== latent_size + global_feat_size + pointwise_layers_size[-1]
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
self.global_feat_size = global_feat_size
|
| 48 |
+
self.latent_size = latent_size
|
| 49 |
+
self.pointwise_layers_size = pointwise_layers_size
|
| 50 |
+
self.global_layers_size = global_layers_size
|
| 51 |
+
self.decoder_layers_size = decoder_layers_size
|
| 52 |
+
|
| 53 |
+
self.pointwise_conv_layers = nn.ModuleList()
|
| 54 |
+
self.pointwise_bn_layers = nn.ModuleList()
|
| 55 |
+
self.global_conv_layers = nn.ModuleList()
|
| 56 |
+
self.global_bn_layers = nn.ModuleList()
|
| 57 |
+
self.activate_func = nn.ReLU()
|
| 58 |
+
|
| 59 |
+
for i in range(len(pointwise_layers_size) - 1):
|
| 60 |
+
self.pointwise_conv_layers.append(
|
| 61 |
+
nn.Conv1d(pointwise_layers_size[i], pointwise_layers_size[i + 1], 1)
|
| 62 |
+
)
|
| 63 |
+
self.pointwise_bn_layers.append(
|
| 64 |
+
nn.BatchNorm1d(pointwise_layers_size[i + 1])
|
| 65 |
+
)
|
| 66 |
+
nn.init.xavier_normal_(self.pointwise_conv_layers[-1].weight)
|
| 67 |
+
|
| 68 |
+
for i in range(len(global_layers_size) - 1):
|
| 69 |
+
self.global_conv_layers.append(
|
| 70 |
+
nn.Conv1d(global_layers_size[i], global_layers_size[i + 1], 1)
|
| 71 |
+
)
|
| 72 |
+
self.global_bn_layers.append(nn.BatchNorm1d(global_layers_size[i + 1]))
|
| 73 |
+
nn.init.xavier_normal_(self.global_conv_layers[-1].weight)
|
| 74 |
+
|
| 75 |
+
self.decoder_conv_layers = nn.ModuleList()
|
| 76 |
+
self.decoder_bn_layers = nn.ModuleList()
|
| 77 |
+
self.sigmoid = nn.Sigmoid()
|
| 78 |
+
|
| 79 |
+
for i in range(len(decoder_layers_size) - 1):
|
| 80 |
+
self.decoder_conv_layers.append(
|
| 81 |
+
nn.Conv1d(decoder_layers_size[i], decoder_layers_size[i + 1], 1)
|
| 82 |
+
)
|
| 83 |
+
self.decoder_bn_layers.append(nn.BatchNorm1d(decoder_layers_size[i + 1]))
|
| 84 |
+
nn.init.xavier_normal_(self.decoder_conv_layers[-1].weight)
|
| 85 |
+
|
| 86 |
+
# self.h2_decoder_conv_layers = nn.ModuleList()
|
| 87 |
+
# self.h2_decoder_bn_layers = nn.ModuleList()
|
| 88 |
+
# for i in range(len(decoder_layers_size) - 1):
|
| 89 |
+
# self.h2_decoder_conv_layers.append(
|
| 90 |
+
# nn.Conv1d(decoder_layers_size[i], decoder_layers_size[i + 1], 1)
|
| 91 |
+
# )
|
| 92 |
+
# self.h2_decoder_bn_layers.append(nn.BatchNorm1d(decoder_layers_size[i + 1]))
|
| 93 |
+
# nn.init.xavier_normal_(self.h2_decoder_conv_layers[-1].weight)
|
| 94 |
+
|
| 95 |
+
def forward(self, x, z_latent_code):
|
| 96 |
+
"""
|
| 97 |
+
:param x: B x N x 3
|
| 98 |
+
:param z_latent_code: B x latent_size
|
| 99 |
+
:return:
|
| 100 |
+
"""
|
| 101 |
+
bs = x.shape[0]
|
| 102 |
+
npts = x.shape[1]
|
| 103 |
+
|
| 104 |
+
pointwise_feature = x.transpose(1, 2)
|
| 105 |
+
for i in range(len(self.pointwise_conv_layers) - 1):
|
| 106 |
+
pointwise_feature = self.pointwise_conv_layers[i](pointwise_feature)
|
| 107 |
+
pointwise_feature = self.pointwise_bn_layers[i](pointwise_feature)
|
| 108 |
+
pointwise_feature = self.activate_func(pointwise_feature)
|
| 109 |
+
pointwise_feature = self.pointwise_bn_layers[-1](
|
| 110 |
+
self.pointwise_conv_layers[-1](pointwise_feature)
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
global_feature = pointwise_feature.clone()
|
| 114 |
+
for i in range(len(self.global_conv_layers) - 1):
|
| 115 |
+
global_feature = self.global_conv_layers[i](global_feature)
|
| 116 |
+
global_feature = self.global_bn_layers[i](global_feature)
|
| 117 |
+
global_feature = self.activate_func(global_feature)
|
| 118 |
+
global_feature = self.global_bn_layers[-1](
|
| 119 |
+
self.global_conv_layers[-1](global_feature)
|
| 120 |
+
)
|
| 121 |
+
global_feature = torch.max(global_feature, 2, keepdim=True)[0]
|
| 122 |
+
global_feature = global_feature.view(bs, self.global_feat_size)
|
| 123 |
+
|
| 124 |
+
global_feature = torch.cat([global_feature, z_latent_code], dim=1)
|
| 125 |
+
global_feature = global_feature.view(
|
| 126 |
+
bs, self.global_feat_size + self.latent_size, 1
|
| 127 |
+
).repeat(1, 1, npts)
|
| 128 |
+
pointwise_feature = torch.cat([pointwise_feature, global_feature], dim=1)
|
| 129 |
+
# pointwise_feature_h2 = pointwise_feature.clone()
|
| 130 |
+
for i in range(len(self.decoder_conv_layers) - 1):
|
| 131 |
+
pointwise_feature = self.decoder_conv_layers[i](pointwise_feature)
|
| 132 |
+
pointwise_feature = self.decoder_bn_layers[i](pointwise_feature)
|
| 133 |
+
pointwise_feature = self.activate_func(pointwise_feature)
|
| 134 |
+
pointwise_feature = self.decoder_bn_layers[-1](
|
| 135 |
+
self.decoder_conv_layers[-1](pointwise_feature)
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# for i in range(len(self.h2_decoder_conv_layers) - 1):
|
| 139 |
+
# pointwise_feature_h2 = self.h2_decoder_conv_layers[i](pointwise_feature_h2)
|
| 140 |
+
# pointwise_feature_h2 = self.h2_decoder_bn_layers[i](pointwise_feature_h2)
|
| 141 |
+
# pointwise_feature_h2 = self.activate_func(pointwise_feature_h2)
|
| 142 |
+
# pointwise_feature_h2 = self.h2_decoder_bn_layers[-1](
|
| 143 |
+
# self.h2_decoder_conv_layers[-1](pointwise_feature_h2)
|
| 144 |
+
# )
|
| 145 |
+
|
| 146 |
+
### pointwise_feature shape B x out_size x N
|
| 147 |
+
# pointwise_feature = (
|
| 148 |
+
# self.sigmoid(pointwise_feature).view(bs, npts, -1).squeeze(-1)
|
| 149 |
+
# )
|
| 150 |
+
# Keep this without sigmoid, since we might do additional transforms
|
| 151 |
+
# return pointwise_feature.view(bs, npts, -1).squeeze(-1)
|
| 152 |
+
return pointwise_feature # shape (bs, -1, npts)
|
| 153 |
+
# output = torch.cat((pointwise_feature, pointwise_feature_h2), dim=1).view(bs, npts, -1)
|
| 154 |
+
# return output
|
| 155 |
+
# return self.sigmoid(output)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class GcsCVAE(nn.Module):
|
| 159 |
+
def __init__(
|
| 160 |
+
self,
|
| 161 |
+
latent_size=128,
|
| 162 |
+
encoder_layers_size=[5, 64, 128, 512],
|
| 163 |
+
decoder_global_feat_size=512,
|
| 164 |
+
decoder_pointwise_layers_size=[3, 64, 64],
|
| 165 |
+
decoder_global_layers_size=[64, 128, 512],
|
| 166 |
+
decoder_decoder_layers_size=[64 + 512 + 128, 512, 64, 64, 1],
|
| 167 |
+
num_coarse_pts=2048,
|
| 168 |
+
uv_layers_size=None,
|
| 169 |
+
):
|
| 170 |
+
# NOTE:
|
| 171 |
+
# encoder_layers_size[0] is 5 instead of 4 since our input is (obj_pc, obj_uv_coords)
|
| 172 |
+
super(GcsCVAE, self).__init__()
|
| 173 |
+
self.num_coarse = num_coarse_pts
|
| 174 |
+
self.latent_size = latent_size
|
| 175 |
+
|
| 176 |
+
self.cmap_encoder = PointNetCmapEncoder(layers_size=encoder_layers_size)
|
| 177 |
+
self.cmap_decoder = PointNetCmapDecoder(
|
| 178 |
+
latent_size=latent_size,
|
| 179 |
+
global_feat_size=decoder_global_feat_size,
|
| 180 |
+
pointwise_layers_size=decoder_pointwise_layers_size,
|
| 181 |
+
global_layers_size=decoder_global_layers_size,
|
| 182 |
+
decoder_layers_size=decoder_decoder_layers_size,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
self.encoder_z_means = nn.Linear(encoder_layers_size[-1], latent_size)
|
| 186 |
+
self.encoder_z_logvars = nn.Linear(encoder_layers_size[-1], latent_size)
|
| 187 |
+
|
| 188 |
+
self.pred_uv = False
|
| 189 |
+
if uv_layers_size:
|
| 190 |
+
# uv_layers_size = [64, 64, 1]
|
| 191 |
+
self.pred_uv = True
|
| 192 |
+
num_layers = len(uv_layers_size) - 1
|
| 193 |
+
|
| 194 |
+
self._pred_u_layers = nn.ModuleList()
|
| 195 |
+
self._pred_v_layers = nn.ModuleList()
|
| 196 |
+
for i in range(num_layers):
|
| 197 |
+
curr_size, next_size = uv_layers_size[i], uv_layers_size[i + 1]
|
| 198 |
+
|
| 199 |
+
self._pred_u_layers.append(nn.Conv1d(curr_size, next_size, 1))
|
| 200 |
+
nn.init.xavier_normal_(self._pred_u_layers[-1].weight)
|
| 201 |
+
self._pred_u_layers.append(nn.BatchNorm1d(next_size))
|
| 202 |
+
|
| 203 |
+
self._pred_v_layers.append(nn.Conv1d(curr_size, next_size, 1))
|
| 204 |
+
nn.init.xavier_normal_(self._pred_v_layers[-1].weight)
|
| 205 |
+
self._pred_v_layers.append(nn.BatchNorm1d(next_size))
|
| 206 |
+
|
| 207 |
+
if i < num_layers:
|
| 208 |
+
self._pred_u_layers.append(nn.ReLU())
|
| 209 |
+
self._pred_v_layers.append(nn.ReLU())
|
| 210 |
+
|
| 211 |
+
self.pred_u_net = nn.Sequential(*self._pred_u_layers)
|
| 212 |
+
self.pred_v_net = nn.Sequential(*self._pred_v_layers)
|
| 213 |
+
|
| 214 |
+
def forward(self, obj_pts, gcs_gt):
|
| 215 |
+
"""
|
| 216 |
+
:param obj_pts: B, N, 3
|
| 217 |
+
:param gcs_gt: B, N, 2
|
| 218 |
+
:return:
|
| 219 |
+
"""
|
| 220 |
+
|
| 221 |
+
bs = obj_pts.shape[0]
|
| 222 |
+
npts = obj_pts.shape[1]
|
| 223 |
+
obj_cmap = torch.cat(
|
| 224 |
+
(
|
| 225 |
+
obj_pts,
|
| 226 |
+
gcs_gt.unsqueeze(-1) if gcs_gt.ndim == 2 else gcs_gt,
|
| 227 |
+
),
|
| 228 |
+
dim=-1,
|
| 229 |
+
)
|
| 230 |
+
means, logvars = self.forward_encoder(object_cmap=obj_cmap)
|
| 231 |
+
z_latent_code = self.reparameterize(means=means, logvars=logvars)
|
| 232 |
+
cmap_values = self.forward_decoder(obj_pts, z_latent_code).view(bs, npts, -1)
|
| 233 |
+
return obj_pts, cmap_values, means, logvars, z_latent_code
|
| 234 |
+
|
| 235 |
+
def predict(self, object_pts):
|
| 236 |
+
"""
|
| 237 |
+
Test time prediction of contact maps from randomly sampled latent vectors on a given input
|
| 238 |
+
|
| 239 |
+
Input:
|
| 240 |
+
object_pts: (B, N, 3) tensor
|
| 241 |
+
|
| 242 |
+
Returns:
|
| 243 |
+
gcs_values: (B, N, 2) tensor of contact map values for each batched obj pc
|
| 244 |
+
"""
|
| 245 |
+
bsize = object_pts.shape[0]
|
| 246 |
+
z_samples = torch.randn(
|
| 247 |
+
bsize, self.latent_size, device=object_pts.device
|
| 248 |
+
).float()
|
| 249 |
+
return self.inference(object_pts, z_samples)
|
| 250 |
+
|
| 251 |
+
def inference(self, object_pts, z_latent_code):
|
| 252 |
+
"""
|
| 253 |
+
:param object_pts: B x N x 3
|
| 254 |
+
:param z_latent_code: B x latent_size
|
| 255 |
+
:return:
|
| 256 |
+
"""
|
| 257 |
+
cmap_values = self.forward_decoder(object_pts, z_latent_code)
|
| 258 |
+
return cmap_values
|
| 259 |
+
|
| 260 |
+
def reparameterize(self, means, logvars):
|
| 261 |
+
std = torch.exp(0.5 * logvars)
|
| 262 |
+
eps = torch.randn_like(std)
|
| 263 |
+
return means + eps * std
|
| 264 |
+
|
| 265 |
+
def forward_encoder(self, object_cmap):
|
| 266 |
+
cmap_feat = self.cmap_encoder(object_cmap)
|
| 267 |
+
means = self.encoder_z_means(cmap_feat)
|
| 268 |
+
logvars = self.encoder_z_logvars(cmap_feat)
|
| 269 |
+
return means, logvars
|
| 270 |
+
|
| 271 |
+
def forward_decoder(self, object_pts, z_latent_code):
|
| 272 |
+
"""
|
| 273 |
+
:param object_pts: B x N x 3
|
| 274 |
+
:param z_latent_code: B x latent_size
|
| 275 |
+
:return:
|
| 276 |
+
"""
|
| 277 |
+
cmap_values = self.cmap_decoder(object_pts, z_latent_code)
|
| 278 |
+
bs = cmap_values.shape[0]
|
| 279 |
+
npts = cmap_values.shape[-1]
|
| 280 |
+
if self.pred_uv:
|
| 281 |
+
u = self.pred_u_net(cmap_values).view(bs, npts, -1)
|
| 282 |
+
v = self.pred_v_net(cmap_values).view(bs, npts, -1)
|
| 283 |
+
# u = torch.sigmoid(u)
|
| 284 |
+
# v = torch.sigmoid(v)
|
| 285 |
+
output = torch.cat([u, v], dim=-1).view(bs, npts, -1)
|
| 286 |
+
return output
|
| 287 |
+
else:
|
| 288 |
+
return cmap_values.view(bs, npts, -1)
|
| 289 |
+
|
robotfingerprint/unseen_barrett/final.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e1e725f0e6bf613e55b88ddd4e862c01486bf2e9c1e73ca12bdb7f2def1d3314
|
| 3 |
+
size 11523772
|
robotfingerprint/unseen_ezgripper/final.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cd50763647da294b6e988fdd4c4ae691b7e3ccf4c0bfc29991c9ab82cdd66dce
|
| 3 |
+
size 11523772
|
robotfingerprint/unseen_shadowhand/final.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:db094eaeb2b39e4872ca229c1101e6afdfb0f8398feec9a2997b9077a4043a52
|
| 3 |
+
size 11523772
|