Dimios45 commited on
Commit
34da7e1
·
verified ·
1 Parent(s): 813994f

Reorganize GeoMatch checkpoints into per-version subfolders; add RobotFingerPrint (arXiv:2409.14519) paper reproduction checkpoints

Browse files
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 grasping,
16
- trained on the CMapDataset across 5 robot end-effectors (EZGripper, Barrett, Robotiq 3-Finger,
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 | File prefix | Val loss | Val acc |
28
  |---|---|---|---|---|
29
- | **GeoMatch v2** | ✅ **Recommended** | `geomatch_v2_*` | **1.594** | **0.695** |
30
- | GeoMatch++ | ⚠️ Deprecated (built on v1 encoders) | `geomatch_pp_*` | 0.350 | 0.940 |
31
- | GeoMatch v1 | ⚠️ Deprecated (corrupted keypoints) | `geomatch_final / checkpoint_epoch*` | 0.435 | 0.959 |
 
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
- ### Component Comparison
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- | `geomatch_v2_epoch50.pth` | 50 | 1.675 | Early convergence |
177
- | `geomatch_v2_epoch100.pth` | 100 | 1.649 | Mid-training |
178
- | `geomatch_v2_epoch150.pth` | 150 | 1.603 | Near-converged |
179
- | `geomatch_v2_final.pth` | 199 | 1.594 | **Final model (recommended)** |
180
 
181
  ### GeoMatch++ ⚠️ (Deprecated)
182
 
183
  | File | Epoch | Notes |
184
  |---|---|---|
185
- | `geomatch_pp_checkpoint_epoch50.pth` | 50 | Early convergence |
186
- | `geomatch_pp_checkpoint_epoch100.pth` | 100 | Mid-training |
187
- | `geomatch_pp_checkpoint_epoch140.pth` | 140 | Near-converged |
188
- | `geomatch_pp_final.pth` | 149 | Final (deprecated) |
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
- | `geomatch_final.pth` | 200 | Final (deprecated) |
 
 
 
 
 
 
 
 
 
 
 
 
 
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("geomatch_v2_final.pth", map_location="cuda"))
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("geomatch_pp_final.pth", map_location="cuda"))
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
- models/
261
- geomatch.py # GeoMatch model (shared by v1 and v2)
262
- geomatch_pp.py # GeoMatch++ model (+ morphology encoder + DCP transformer)
263
- gnn.py # Graph Convolutional Network
264
- mlp.py # MLP building block
265
- config.py # Hyperparameters for all models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 [Dimios45](https://huggingface.co/Dimios45) as part of the Graspmax project.
 
 
 
 
 
 
 
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