torch-pointcloud 0.0.2__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/.gitignore +1 -0
  2. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/PKG-INFO +4 -4
  3. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/README.md +3 -3
  4. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/pyproject.toml +1 -1
  5. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/_utils.py +48 -6
  6. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/inferer.py +21 -15
  7. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/knn_window.py +46 -28
  8. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/potential_sphere.py +32 -19
  9. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/sliding_window.py +132 -61
  10. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/tta.py +26 -22
  11. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/voxel_partition.py +44 -22
  12. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/serialized_attention.py +59 -20
  13. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/lightning/metrics.py +80 -53
  14. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/center.py +3 -3
  15. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/__init__.py +20 -0
  16. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/classification.py +139 -0
  17. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/detection.py +380 -0
  18. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/instance_segmentation.py +359 -0
  19. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/nuscenes.py +404 -0
  20. torch_pointcloud-0.0.4/src/torch_pointcloud/metrics/segmentation.py +310 -0
  21. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/_base.py +10 -4
  22. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/concerto.py +8 -4
  23. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/detr3d.py +3 -3
  24. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/dgcnn.py +1 -8
  25. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/kpconv.py +13 -14
  26. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/octformer.py +13 -12
  27. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_bert.py +10 -4
  28. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_m2ae.py +2 -9
  29. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_mae.py +2 -7
  30. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_mamba.py +2 -2
  31. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_transformer.py +9 -9
  32. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_transformer_v2.py +11 -12
  33. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/point_transformer_v3.py +44 -21
  34. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointcnn.py +12 -22
  35. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointconv.py +9 -17
  36. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointmlp.py +12 -22
  37. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointnet2.py +8 -8
  38. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointnext.py +23 -34
  39. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pvcnn.py +16 -12
  40. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pvcnn2.py +17 -11
  41. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/randlanet.py +14 -20
  42. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/sonata.py +8 -4
  43. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/spformer_unet.py +41 -14
  44. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/spunet.py +40 -16
  45. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/spvcnn.py +45 -24
  46. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/utonia.py +4 -4
  47. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/voxel_mamba.py +2 -2
  48. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/transforms/transforms.py +2 -2
  49. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/__init__.py +1 -1
  50. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/box3d.py +22 -6
  51. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/data.py +1 -0
  52. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/geometry.py +2 -2
  53. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/heatmap.py +8 -8
  54. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/serialization.py +3 -3
  55. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/types.py +26 -0
  56. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/voxelization.py +5 -5
  57. torch_pointcloud-0.0.2/src/torch_pointcloud/utils/metrics.py +0 -1191
  58. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/LICENSE +0 -0
  59. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/THIRD_PARTY_NOTICES.md +0 -0
  60. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/__init__.py +0 -0
  61. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/config.py +0 -0
  62. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/__init__.py +0 -0
  63. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/concat.py +0 -0
  64. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/kitti.py +0 -0
  65. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/mix.py +0 -0
  66. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/modelnet.py +0 -0
  67. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/nuscenes.py +0 -0
  68. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/parislille3d.py +0 -0
  69. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/pointcloud.py +0 -0
  70. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/repeat.py +0 -0
  71. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/s3dis.py +0 -0
  72. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/scannet.py +0 -0
  73. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/scanobjectnn.py +0 -0
  74. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/semantic3d.py +0 -0
  75. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/semantickitti.py +0 -0
  76. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/shapenetpart.py +0 -0
  77. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/sunrgbd.py +0 -0
  78. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/toronto3d.py +0 -0
  79. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/datasets/utils.py +0 -0
  80. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/__init__.py +0 -0
  81. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/part_refinement.py +0 -0
  82. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/inferers/simple.py +0 -0
  83. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/__init__.py +0 -0
  84. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/_modules.py +0 -0
  85. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/act.py +0 -0
  86. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/affine.py +0 -0
  87. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/anchors.py +0 -0
  88. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/bev_backbone.py +0 -0
  89. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/conv2d_blocks.py +0 -0
  90. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/conv3d_blocks.py +0 -0
  91. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/dropouts.py +0 -0
  92. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/fps.py +0 -0
  93. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/geometric_affine.py +0 -0
  94. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/grid_pool.py +0 -0
  95. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/linear_blocks.py +0 -0
  96. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/norms.py +0 -0
  97. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/octree_attention.py +0 -0
  98. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/octree_blocks.py +0 -0
  99. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pdnorm.py +0 -0
  100. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/point_patch_embed.py +0 -0
  101. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pointconv.py +0 -0
  102. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pointconv_sa.py +0 -0
  103. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pointnet2_blocks.py +0 -0
  104. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pointnext_blocks.py +0 -0
  105. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pools.py +0 -0
  106. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/pvcnn_blocks.py +0 -0
  107. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/rope.py +0 -0
  108. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/serialized_pool.py +0 -0
  109. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/spconv_blocks.py +0 -0
  110. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/tnet.py +0 -0
  111. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/transformer.py +0 -0
  112. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/vfe.py +0 -0
  113. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/view.py +0 -0
  114. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/layers/xconv.py +0 -0
  115. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/lightning/__init__.py +0 -0
  116. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/lightning/callbacks.py +0 -0
  117. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/lightning/datamodule.py +0 -0
  118. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/lightning/module.py +0 -0
  119. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/__init__.py +0 -0
  120. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/_utils.py +0 -0
  121. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/anchor.py +0 -0
  122. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/chamfer.py +0 -0
  123. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/detr3d.py +0 -0
  124. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/lovasz.py +0 -0
  125. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/pointrcnn.py +0 -0
  126. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/sum.py +0 -0
  127. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/transfusion.py +0 -0
  128. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/losses/votenet.py +0 -0
  129. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/__init__.py +0 -0
  130. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/_registry.py +0 -0
  131. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/lion.py +0 -0
  132. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointgpt.py +0 -0
  133. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointnet.py +0 -0
  134. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointpillars.py +0 -0
  135. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/pointrcnn.py +0 -0
  136. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/second.py +0 -0
  137. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/sphereformer.py +0 -0
  138. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/votenet.py +0 -0
  139. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/models/voxelnext.py +0 -0
  140. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/py.typed +0 -0
  141. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/transforms/__init__.py +0 -0
  142. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/transforms/functional.py +0 -0
  143. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/cluster.py +0 -0
  144. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/conversion.py +0 -0
  145. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/diffusion.py +0 -0
  146. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/ensemble.py +0 -0
  147. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/hilbert.py +0 -0
  148. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/imports.py +0 -0
  149. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/io.py +0 -0
  150. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/misc.py +0 -0
  151. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/neighbors.py +0 -0
  152. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/octree.py +0 -0
  153. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/ops.py +0 -0
  154. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/optim.py +0 -0
  155. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/random.py +0 -0
  156. {torch_pointcloud-0.0.2 → torch_pointcloud-0.0.4}/src/torch_pointcloud/utils/state_dict.py +0 -0
@@ -164,6 +164,7 @@ cython_debug/
164
164
  weights/**
165
165
  data/**
166
166
  docs/api/**
167
+ docs/assets/data/
167
168
  docs/examples/**
168
169
  docs/scripts/**
169
170
  !docs/examples/*.ipynb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: torch-pointcloud
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: PyTorch Point Cloud models, scripts, pretrained weights and more
5
5
  Project-URL: Homepage, https://github.com/arthurdjn/pytorch-pointcloud
6
6
  Project-URL: Repository, https://github.com/arthurdjn/pytorch-pointcloud
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
  [![tests](https://img.shields.io/github/actions/workflow/status/arthurdjn/pytorch-pointcloud/test.yml?branch=main&label=tests&labelColor=11001C&logo=github&logoColor=white)](https://github.com/arthurdjn/pytorch-pointcloud/actions/workflows/test.yml)
52
52
  [![pypi](https://img.shields.io/pypi/v/torch-pointcloud?color=EE4C2C&labelColor=11001C&logo=pypi&logoColor=white)](https://pypi.org/project/torch-pointcloud/)
53
53
  [![license](https://img.shields.io/badge/license-Apache--2.0-red.svg?color=EE4C2C&labelColor=11001C)](https://github.com/arthurdjn/pytorch-pointcloud/blob/main/LICENSE)
54
- [![doi](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22159633-red.svg?color=EE4C2C&labelColor=11001C)](https://doi.org/10.5281/zenodo.22159633)
54
+ [![doi](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22159632-red.svg?color=EE4C2C&labelColor=11001C)](https://doi.org/10.5281/zenodo.22159632)
55
55
  [![docs](https://img.shields.io/badge/docs-pytorch--pointcloud.org-red.svg?color=EE4C2C&labelColor=11001C&logo=materialformkdocs&logoColor=white)](https://pytorch-pointcloud.org/)
56
56
 
57
57
  </div>
@@ -158,7 +158,7 @@ Every checkpoint ships the transform that turns a raw point cloud into what the
158
158
  # Requires torch-scatter, torch-cluster, spconv
159
159
  model, info = tp.create_model("ptv3-base.scannet20.pointcept", task="segmentation", pretrained=True, return_info=True)
160
160
  info["transform"] # the preprocessing pipeline of that checkpoint
161
- info["weights"]["metrics"] # {"mIoU": 76.29}
161
+ info["weights"]["metrics"] # {"mIoU": 77.40}
162
162
 
163
163
  tp.list_models("pointnext*") # every registered PointNeXt config
164
164
  tp.list_models(task="detection", pretrained=True) # all detection checkpoints
@@ -186,7 +186,7 @@ If you find this project useful, please consider citing:
186
186
  title = {PyTorch PointCloud},
187
187
  year = {2026},
188
188
  url = {https://github.com/arthurdjn/pytorch-pointcloud},
189
- doi = {10.5281/zenodo.22159633},
189
+ doi = {10.5281/zenodo.22159632},
190
190
  license = {Apache-2.0}
191
191
  }
192
192
  ```
@@ -8,7 +8,7 @@
8
8
  [![tests](https://img.shields.io/github/actions/workflow/status/arthurdjn/pytorch-pointcloud/test.yml?branch=main&label=tests&labelColor=11001C&logo=github&logoColor=white)](https://github.com/arthurdjn/pytorch-pointcloud/actions/workflows/test.yml)
9
9
  [![pypi](https://img.shields.io/pypi/v/torch-pointcloud?color=EE4C2C&labelColor=11001C&logo=pypi&logoColor=white)](https://pypi.org/project/torch-pointcloud/)
10
10
  [![license](https://img.shields.io/badge/license-Apache--2.0-red.svg?color=EE4C2C&labelColor=11001C)](https://github.com/arthurdjn/pytorch-pointcloud/blob/main/LICENSE)
11
- [![doi](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22159633-red.svg?color=EE4C2C&labelColor=11001C)](https://doi.org/10.5281/zenodo.22159633)
11
+ [![doi](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22159632-red.svg?color=EE4C2C&labelColor=11001C)](https://doi.org/10.5281/zenodo.22159632)
12
12
  [![docs](https://img.shields.io/badge/docs-pytorch--pointcloud.org-red.svg?color=EE4C2C&labelColor=11001C&logo=materialformkdocs&logoColor=white)](https://pytorch-pointcloud.org/)
13
13
 
14
14
  </div>
@@ -115,7 +115,7 @@ Every checkpoint ships the transform that turns a raw point cloud into what the
115
115
  # Requires torch-scatter, torch-cluster, spconv
116
116
  model, info = tp.create_model("ptv3-base.scannet20.pointcept", task="segmentation", pretrained=True, return_info=True)
117
117
  info["transform"] # the preprocessing pipeline of that checkpoint
118
- info["weights"]["metrics"] # {"mIoU": 76.29}
118
+ info["weights"]["metrics"] # {"mIoU": 77.40}
119
119
 
120
120
  tp.list_models("pointnext*") # every registered PointNeXt config
121
121
  tp.list_models(task="detection", pretrained=True) # all detection checkpoints
@@ -143,7 +143,7 @@ If you find this project useful, please consider citing:
143
143
  title = {PyTorch PointCloud},
144
144
  year = {2026},
145
145
  url = {https://github.com/arthurdjn/pytorch-pointcloud},
146
- doi = {10.5281/zenodo.22159633},
146
+ doi = {10.5281/zenodo.22159632},
147
147
  license = {Apache-2.0}
148
148
  }
149
149
  ```
@@ -16,7 +16,7 @@ include = [
16
16
 
17
17
  [project]
18
18
  name = "torch-pointcloud"
19
- version = "0.0.2"
19
+ version = "0.0.4"
20
20
  description = "PyTorch Point Cloud models, scripts, pretrained weights and more"
21
21
  readme = "README.md"
22
22
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
- """Shared inferer helpers: dict indexing, chunk splitting, and Gaussian distance weighting."""
1
+ """Shared inferer helpers: dict indexing, per-fragment transforms, chunk splitting, and Gaussian distance weighting."""
2
2
 
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
4
4
 
5
5
  import torch
6
6
  from torch import Tensor
@@ -21,6 +21,47 @@ def index_select_dict(data: Dict[str, Any], idx: Tensor, n_points: int) -> Dict[
21
21
  return out
22
22
 
23
23
 
24
+ def apply_transform(
25
+ sample: Dict[str, Any],
26
+ transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]],
27
+ *,
28
+ pos_key: str,
29
+ inverse_key: Optional[str],
30
+ ) -> Tuple[Dict[str, Any], Optional[Tensor]]:
31
+ r"""Run the per-fragment `transform` over `sample` and return it with its source-to-predictor row map.
32
+
33
+ Any value already at `inverse_key` is dropped before `transform` runs, so a scene-level `inverse` never
34
+ becomes the prior the fragment's own map composes through. The map the transform writes under `inverse_key`
35
+ is popped from the returned sample, so it never reaches the predictor.
36
+
37
+ Args:
38
+ sample: Data dict of one fragment (block, sub-cloud, sphere).
39
+ transform: Callable applied to `sample`, or `None` to leave it unchanged.
40
+ pos_key: Dict key for the position tensor, whose row count defines the fragment size.
41
+ inverse_key: Dict key under which a row-altering `transform` records its long index map of shape
42
+ $(N_\text{source},)$ with values in $[0, N_\text{predictor})$. `None` when rows are preserved.
43
+
44
+ Returns:
45
+ The transformed sample and its inverse map, `None` when the transform wrote none. Predictions of shape
46
+ $(N_\text{predictor}, C)$ index with the map back to the $N_\text{source}$ rows of `sample`.
47
+
48
+ Raises:
49
+ ValueError: If `transform` changes the row count without writing a map under `inverse_key`.
50
+ """
51
+ n_source = int(sample[pos_key].size(0))
52
+ sample = {key: value for key, value in sample.items() if key != inverse_key}
53
+ if transform is not None:
54
+ sample = transform(sample)
55
+ inverse_map = sample.pop(inverse_key, None) if inverse_key is not None else None
56
+ n_predictor = int(sample[pos_key].size(0))
57
+ if inverse_map is None and n_predictor != n_source:
58
+ raise ValueError(
59
+ f"`transform` changed the fragment's row count ({n_source} -> {n_predictor}) without recording an "
60
+ f"index map. Make it write one (e.g. `dst_inverse_key`) and pass that key as `inverse_key`."
61
+ )
62
+ return sample, inverse_map
63
+
64
+
24
65
  def check_batch_alignment(pos: Tensor, batch: Tensor, pos_key: str, batch_key: str) -> None:
25
66
  """Raise when the per-point batch index does not line up with the positions row for row.
26
67
 
@@ -35,7 +76,7 @@ def check_batch_alignment(pos: Tensor, batch: Tensor, pos_key: str, batch_key: s
35
76
  )
36
77
 
37
78
 
38
- def split_chunks(n: int, max_size: Optional[int], rng: torch.Generator) -> List[Tensor]:
79
+ def split_chunks(n: int, max_size: Optional[int], rng: Optional[torch.Generator], device: torch.device) -> List[Tensor]:
39
80
  r"""Partition `range(n)` into index chunks of at most `max_size` points each.
40
81
 
41
82
  When `max_size` is `None` or `n` is within `max_size`, returns a single in-order
@@ -46,13 +87,14 @@ def split_chunks(n: int, max_size: Optional[int], rng: torch.Generator) -> List[
46
87
  Args:
47
88
  n: Number of indices to partition.
48
89
  max_size: Maximum points per chunk, or `None` for a single unsplit chunk.
49
- rng: Generator for the permutation. Only consumed when a split is needed.
90
+ rng: Generator for the permutation, or `None` for the global generator. Only consumed when a split
91
+ is needed.
92
+ device: Device of the returned indices, which a given `rng` must live on.
50
93
 
51
94
  Returns:
52
- List of 1-D `long` index tensors on `rng`'s device. The chunks partition
95
+ List of 1-D `long` index tensors on `device`. The chunks partition
53
96
  `range(n)` exactly: every index appears in one and only one chunk.
54
97
  """
55
- device = rng.device
56
98
  if max_size is None:
57
99
  return [torch.arange(n, device=device)]
58
100
  if n > max_size:
@@ -27,21 +27,27 @@ class Inferer(metaclass=ABCMeta):
27
27
 
28
28
  `data` is a packed-batch dict (at minimum containing position and batch indices).
29
29
  `predictor` is any callable taking such a dict and returning per-point logits of shape
30
- $(N, C_\text{out})$. Inferers are stateless with respect to the scene, so
31
- one instance can be reused across scenes and wrapped by another inferer.
32
-
33
- Every concrete inferer exposes a knob controlling whether partial predictions are
34
- converted to softmax probabilities before aggregation. The defaults differ:
35
-
36
- | Inferer | Parameter | Default | Aggregated quantity |
37
- | ------------------------ | ------------- | ------- | ------------------------------------------------------------ |
38
- | `SimpleInferer` | `softmax` | `False` | predictor output as-is |
39
- | `SlidingWindowInferer` | `softmax` | `True` | softmax probabilities per block (`"max"` / `"vote"` always) |
40
- | `KNNWindowInferer` | `softmax` | `False` | raw logits (`"weighted_mean"`); always probabilities (`"ema"`) |
41
- | `VoxelPartitionInferer` | `softmax` | `False` | raw logits per pass |
42
- | `PotentialSphereInferer` | none | | always an EMA of softmax probabilities |
43
- | `TTAInferer` | `ema_softmax` | `True` | base output as-is (`"mean"`); softmax of base output (`"ema"`) |
44
- | `PartRefinementInferer` | none | | one-hot refined labels of the base output's argmax |
30
+ $(N, C_\text{out})$. Inferers keep nothing of the scene, so one instance can be
31
+ reused across scenes and wrapped by another inferer.
32
+
33
+ Inferers that draw random numbers take a `seed`. `None` follows the global generator,
34
+ like the transforms do, so one `torch.manual_seed` (or `seed_everything`) seeds the
35
+ whole evaluation. An int gives the inferer its own stream, offset by the number of
36
+ calls the instance has made: successive calls (e.g. `TTAInferer` votes) draw different
37
+ numbers, and a fresh instance replays the same sequence.
38
+
39
+ The `softmax` parameter controls whether partial predictions are converted to softmax
40
+ probabilities before aggregation, and `aggregate` how they are combined:
41
+
42
+ | Inferer | `softmax` | Aggregated quantity |
43
+ | ------------------------ | --------- | ----------------------------------------------------------- |
44
+ | `SimpleInferer` | `False` | predictor output as-is |
45
+ | `SlidingWindowInferer` | `True` | softmax probabilities per block (`"max"` / `"vote"` always) |
46
+ | `KNNWindowInferer` | `False` | raw logits (`"mean"`); always probabilities (`"ema"`) |
47
+ | `VoxelPartitionInferer` | `False` | raw logits per pass |
48
+ | `PotentialSphereInferer` | none | always an EMA of softmax probabilities |
49
+ | `TTAInferer` | `False` | base output as-is |
50
+ | `PartRefinementInferer` | none | one-hot refined labels of the base output's argmax |
45
51
 
46
52
  When the input scene is empty ($N = 0$), inferers that never call the predictor
47
53
  return a $(0, 0)$ tensor (the channel count cannot be inferred without a predictor
@@ -19,11 +19,11 @@ from tqdm import tqdm
19
19
 
20
20
  from torch_pointcloud.utils.data import DataKeys
21
21
 
22
- from ._utils import check_batch_alignment, gaussian_weights, index_select_dict
22
+ from ._utils import apply_transform, check_batch_alignment, gaussian_weights, index_select_dict
23
23
  from .inferer import Inferer
24
24
 
25
25
  WindowMode = Literal["constant", "gaussian"]
26
- AggregateMode = Literal["weighted_mean", "ema"]
26
+ AggregateMode = Literal["mean", "ema"]
27
27
 
28
28
 
29
29
  def _knn_centers(pos_src: Tensor, centers: Tensor, k: int) -> Tensor:
@@ -62,12 +62,13 @@ def knn_window_inference(
62
62
  overlap: float = 0.5,
63
63
  mode: WindowMode = "constant",
64
64
  sigma_scale: float = 0.125,
65
- aggregate: AggregateMode = "weighted_mean",
65
+ aggregate: AggregateMode = "mean",
66
66
  ema_smoothing: float = 0.95,
67
67
  softmax: bool = False,
68
68
  transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None,
69
69
  pos_key: str = DataKeys.POS,
70
70
  batch_key: str = DataKeys.BATCH,
71
+ inverse_key: Optional[str] = None,
71
72
  progress: bool = False,
72
73
  seed: Optional[int] = None,
73
74
  ) -> Tensor:
@@ -81,7 +82,7 @@ def knn_window_inference(
81
82
 
82
83
  `aggregate` controls how overlapping window predictions are combined:
83
84
 
84
- - `"weighted_mean"`: accumulates distance-weighted logits and divides by total
85
+ - `"mean"`: accumulates distance-weighted logits and divides by total
85
86
  weight at the end, producing a weighted-average logit tensor.
86
87
  - `"ema"`: per-update softmax EMA
87
88
  ($\text{new} = \alpha \cdot \text{old} + (1 - \alpha) \cdot \text{softmax}(\text{logits})$),
@@ -109,10 +110,10 @@ def knn_window_inference(
109
110
  mode: Distance weighting for each window. `"constant"` gives equal weight
110
111
  to every point; `"gaussian"` weights by $\exp(-d^2 / 2\sigma^2)$ with
111
112
  $\sigma = \text{sigma\_scale} \cdot \max_i d_i$. `"gaussian"` requires
112
- `aggregate="weighted_mean"`; EMA updates ignore distance weights.
113
+ `aggregate="mean"`; EMA updates ignore distance weights.
113
114
  sigma_scale: Gaussian sigma scale factor (only used when `mode="gaussian"`).
114
115
  aggregate: How predictions from overlapping windows are combined.
115
- `"weighted_mean"`: weighted-average logits (divide by total weight at end).
116
+ `"mean"`: weighted-average logits (divide by total weight at end).
116
117
  `"ema"`: softmax EMA
117
118
  ($\text{new} = \alpha \cdot \text{old} + (1 - \alpha) \cdot \text{softmax}(\text{logits})$);
118
119
  use `sw_batch_size=1` with EMA to match the reference evaluation protocol.
@@ -123,14 +124,23 @@ def knn_window_inference(
123
124
  softmax probabilities.
124
125
  transform: Optional callable applied to each window's data dict before the
125
126
  predictor (typical example: `T.Shift(keys=DataKeys.POS, method="centroid")`).
127
+ If it changes the row count (pad, voxelize, ...) it must record a
128
+ source-to-predictor index map under `inverse_key` so the inferer can gather
129
+ predictions back to the window's points.
126
130
  pos_key: Dict key for the position tensor.
127
131
  batch_key: Dict key for the per-point batch index.
132
+ inverse_key: Dict key under which a row-altering `transform` records a
133
+ source-to-predictor long index map of shape $(k,)$ with values in
134
+ $[0, N_\text{predictor})$. Any scene-level value at this key is dropped before
135
+ `transform` runs, and the map is popped before the predictor is called. Leave
136
+ `None` when the transform preserves row count.
128
137
  progress: If `True`, show a `tqdm` progress bar per batch element.
129
- seed: RNG seed for the per-point initial possibility scores.
138
+ seed: RNG seed for the per-point initial possibility scores. `None` draws from the global generator, so
139
+ `torch.manual_seed` seeds the inferer together with the transforms.
130
140
 
131
141
  Returns:
132
142
  Per-point output tensor of shape $(N, C_\text{out})$. Aggregation produces
133
- logits when `aggregate="weighted_mean"` (probabilities with `softmax=True`)
143
+ logits when `aggregate="mean"` (probabilities with `softmax=True`)
134
144
  and probabilities when `aggregate="ema"`. An empty scene ($N = 0$) returns
135
145
  a $(0, 0)$ tensor: the predictor is never called, so the channel count
136
146
  cannot be inferred.
@@ -143,8 +153,8 @@ def knn_window_inference(
143
153
  raise ValueError(f"`overlap` must be in (0, 1), got {overlap}.")
144
154
  if mode not in ("constant", "gaussian"):
145
155
  raise ValueError(f"`mode` must be 'constant' or 'gaussian', got {mode!r}.")
146
- if aggregate not in ("weighted_mean", "ema"):
147
- raise ValueError(f"`aggregate` must be 'weighted_mean' or 'ema', got {aggregate!r}.")
156
+ if aggregate not in ("mean", "ema"):
157
+ raise ValueError(f"`aggregate` must be 'mean' or 'ema', got {aggregate!r}.")
148
158
  if sw_batch_size < 1:
149
159
  raise ValueError(f"`sw_batch_size` must be >= 1, got {sw_batch_size}.")
150
160
  if not 0.0 <= ema_smoothing < 1.0:
@@ -152,7 +162,7 @@ def knn_window_inference(
152
162
  if aggregate == "ema" and mode == "gaussian":
153
163
  raise ValueError(
154
164
  "`mode='gaussian'` is incompatible with `aggregate='ema'`: EMA updates blend by `ema_smoothing`, not "
155
- "by per-point distance weights. Use `aggregate='weighted_mean'` or `mode='constant'`."
165
+ "by per-point distance weights. Use `aggregate='mean'` or `mode='constant'`."
156
166
  )
157
167
  if aggregate == "ema" and sw_batch_size > 1:
158
168
  warnings.warn(
@@ -170,9 +180,7 @@ def knn_window_inference(
170
180
  n_total = pos.size(0)
171
181
  output: Optional[Tensor] = None
172
182
 
173
- rng = torch.Generator(device=device)
174
- if seed is not None:
175
- rng.manual_seed(int(seed))
183
+ rng = None if seed is None else torch.Generator(device=device).manual_seed(int(seed))
176
184
 
177
185
  for b in torch.unique(batch).tolist():
178
186
  idx_b = torch.where(batch == b)[0]
@@ -201,37 +209,39 @@ def knn_window_inference(
201
209
  local_idxs = _knn_centers(pos_b, center_pos, k)
202
210
  flat_idxs = local_idxs.reshape(-1)
203
211
  per_window_dicts: List[Dict[str, Any]] = []
212
+ inverse_maps: List[Optional[Tensor]] = []
204
213
  for w_i in range(sw):
205
214
  w_idx = local_idxs[w_i]
206
215
  wd = index_select_dict(data_b, w_idx, n_b)
207
216
  wd[batch_key] = torch.full((k,), w_i, device=device, dtype=torch.long)
208
- if transform is not None:
209
- wd = transform(wd)
210
- if int(wd[pos_key].size(0)) != k:
211
- raise ValueError(
212
- f"`transform` must preserve each window's row count; got {k} -> "
213
- f"{int(wd[pos_key].size(0))} rows."
214
- )
215
-
217
+ wd, inverse_map = apply_transform(wd, transform, pos_key=pos_key, inverse_key=inverse_key)
218
+ wd[batch_key] = torch.full((int(wd[pos_key].size(0)),), w_i, device=device, dtype=torch.long)
216
219
  per_window_dicts.append(wd)
220
+ inverse_maps.append(inverse_map)
217
221
 
222
+ sizes = [int(wd[pos_key].size(0)) for wd in per_window_dicts]
218
223
  window_data: Dict[str, Any] = {}
219
224
  for key in per_window_dicts[0]:
220
225
  values = [wd[key] for wd in per_window_dicts]
221
- if torch.is_tensor(values[0]) and values[0].dim() > 0 and values[0].size(0) == k:
226
+ if torch.is_tensor(values[0]) and values[0].dim() > 0 and values[0].size(0) == sizes[0]:
222
227
  window_data[key] = torch.cat(values, dim=0)
223
228
  else:
224
229
  window_data[key] = values[0]
225
230
 
226
231
  window_logits = predictor(window_data).to(device)
227
232
  num_classes = int(window_logits.size(-1))
228
- window_logits = window_logits.reshape(sw, k, num_classes)
233
+ window_logits = torch.stack(
234
+ [
235
+ part if inverse_map is None else part[inverse_map]
236
+ for part, inverse_map in zip(window_logits.split(sizes), inverse_maps)
237
+ ]
238
+ ) # (sw, k, C)
229
239
 
230
240
  if output is None:
231
241
  output = torch.zeros(n_total, num_classes, device=device, dtype=torch.float32)
232
242
  if scores_b is None:
233
243
  scores_b = torch.zeros(n_b, num_classes, device=device, dtype=torch.float32)
234
- if aggregate == "weighted_mean":
244
+ if aggregate == "mean":
235
245
  weights_b = torch.zeros(n_b, device=device, dtype=torch.float32)
236
246
 
237
247
  distances = torch.linalg.norm(pos_b[local_idxs] - center_pos.unsqueeze(1), dim=-1)
@@ -289,7 +299,9 @@ class KNNWindowInferer(Inferer):
289
299
  the least-covered points until all points are covered. Reuse the same instance
290
300
  across scenes; compose with `TTAInferer` for multi-augmentation averaging.
291
301
 
292
- All parameters are forwarded verbatim to `knn_window_inference`.
302
+ All parameters are forwarded verbatim to `knn_window_inference`, except `seed`, which is offset by
303
+ the number of calls the instance has made: repeated calls (e.g. `TTAInferer` votes) draw different random
304
+ numbers, and a fresh instance replays the same sequence.
293
305
 
294
306
  Example:
295
307
  ```python
@@ -308,12 +320,13 @@ class KNNWindowInferer(Inferer):
308
320
  overlap: float = 0.5,
309
321
  mode: WindowMode = "constant",
310
322
  sigma_scale: float = 0.125,
311
- aggregate: AggregateMode = "weighted_mean",
323
+ aggregate: AggregateMode = "mean",
312
324
  ema_smoothing: float = 0.95,
313
325
  softmax: bool = False,
314
326
  transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None,
315
327
  pos_key: str = DataKeys.POS,
316
328
  batch_key: str = DataKeys.BATCH,
329
+ inverse_key: Optional[str] = None,
317
330
  progress: bool = False,
318
331
  seed: Optional[int] = None,
319
332
  ) -> None:
@@ -328,14 +341,18 @@ class KNNWindowInferer(Inferer):
328
341
  self.transform = transform
329
342
  self.pos_key = pos_key
330
343
  self.batch_key = batch_key
344
+ self.inverse_key = inverse_key
331
345
  self.progress = progress
332
346
  self.seed = seed
347
+ self._num_calls = 0
333
348
 
334
349
  def forward(
335
350
  self,
336
351
  data: Dict[str, Any],
337
352
  predictor: Callable[[Dict[str, Any]], Tensor],
338
353
  ) -> Tensor:
354
+ seed = None if self.seed is None else self.seed + self._num_calls
355
+ self._num_calls += 1
339
356
  return knn_window_inference(
340
357
  data,
341
358
  predictor=predictor,
@@ -350,6 +367,7 @@ class KNNWindowInferer(Inferer):
350
367
  transform=self.transform,
351
368
  pos_key=self.pos_key,
352
369
  batch_key=self.batch_key,
370
+ inverse_key=self.inverse_key,
353
371
  progress=self.progress,
354
- seed=self.seed,
372
+ seed=seed,
355
373
  )
@@ -15,7 +15,7 @@ from tqdm import tqdm
15
15
  from torch_pointcloud.utils.data import DataKeys, collate
16
16
  from torch_pointcloud.utils.ops import voxel_grid_fnv
17
17
 
18
- from ._utils import check_batch_alignment, index_select_dict
18
+ from ._utils import apply_transform, check_batch_alignment, index_select_dict
19
19
  from .inferer import Inferer
20
20
 
21
21
 
@@ -23,7 +23,7 @@ def _next_sphere(
23
23
  pos_b: Tensor,
24
24
  coarse_pos: Tensor,
25
25
  potentials: Tensor,
26
- rng: torch.Generator,
26
+ rng: Optional[torch.Generator],
27
27
  radius: float,
28
28
  jitter: float,
29
29
  ) -> Tuple[Tensor, Tensor]:
@@ -55,6 +55,7 @@ def potential_sphere_inference(
55
55
  transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None,
56
56
  pos_key: str = DataKeys.POS,
57
57
  batch_key: str = DataKeys.BATCH,
58
+ inverse_key: Optional[str] = None,
58
59
  progress: bool = False,
59
60
  seed: Optional[int] = None,
60
61
  ) -> Tensor:
@@ -91,12 +92,19 @@ def potential_sphere_inference(
91
92
  sw_batch_size: Number of spheres packed into one predictor call. Centers are still drawn one at a
92
93
  time with the potentials updated in between, as the reference sampler does.
93
94
  transform: Optional per-sphere callable applied to the centered sphere dict before the predictor.
94
- The transform must preserve the sphere's row count and keep positions centered on the sphere (the
95
- `inner_ratio` mask is evaluated on the transformed positions, as the reference does).
95
+ The transform must keep positions centered on the sphere (the `inner_ratio` mask is evaluated on
96
+ the transformed positions, as the reference does). If it changes the row count (pad, voxelize, ...)
97
+ it must record a source-to-predictor index map under `inverse_key` so the inferer can gather
98
+ predictions and the mask back to the sphere's points.
96
99
  pos_key: Dict key for the position tensor.
97
100
  batch_key: Dict key for the per-point batch index.
101
+ inverse_key: Dict key under which a row-altering `transform` records a source-to-predictor long index
102
+ map of shape $(N_\text{sphere},)$ with values in $[0, N_\text{predictor})$. Any scene-level value at
103
+ this key is dropped before `transform` runs, and the map is popped before the predictor is called.
104
+ Leave `None` when the transform preserves row count.
98
105
  progress: If `True`, show a `tqdm` progress bar per batch element.
99
- seed: Optional RNG seed for the initial potentials and the center jitter.
106
+ seed: RNG seed for the initial potentials and the center jitter. `None` draws from the global generator, so
107
+ `torch.manual_seed` seeds the inferer together with the transforms.
100
108
 
101
109
  Returns:
102
110
  Per-point score tensor of shape $(N, C)$: the EMA of softmax probabilities over the spheres covering
@@ -132,9 +140,7 @@ def potential_sphere_inference(
132
140
  n = pos.size(0)
133
141
  inner_sq = (inner_ratio * radius) ** 2
134
142
 
135
- rng = torch.Generator(device=device)
136
- if seed is not None:
137
- rng.manual_seed(int(seed))
143
+ rng = None if seed is None else torch.Generator(device=device).manual_seed(int(seed))
138
144
 
139
145
  output: Optional[Tensor] = None
140
146
  for b in torch.unique(batch).tolist():
@@ -152,6 +158,7 @@ def potential_sphere_inference(
152
158
  while float(potentials.min().item()) < num_votes:
153
159
  spheres: List[Dict[str, Any]] = []
154
160
  sphere_idx: List[Tensor] = []
161
+ inverse_maps: List[Optional[Tensor]] = []
155
162
  for _ in range(sw_batch_size):
156
163
  if float(potentials.min().item()) >= num_votes:
157
164
  break
@@ -162,16 +169,11 @@ def potential_sphere_inference(
162
169
 
163
170
  sphere = index_select_dict(data_b, idx, n_b)
164
171
  sphere[pos_key] = sphere[pos_key] - center
165
- if transform is not None:
166
- sphere = transform(sphere)
167
- if int(sphere[pos_key].size(0)) != int(idx.numel()):
168
- raise ValueError(
169
- f"`transform` must preserve each sphere's row count; got {int(idx.numel())} -> "
170
- f"{int(sphere[pos_key].size(0))} rows."
171
- )
172
+ sphere, inverse_map = apply_transform(sphere, transform, pos_key=pos_key, inverse_key=inverse_key)
172
173
 
173
174
  spheres.append(sphere)
174
175
  sphere_idx.append(idx)
176
+ inverse_maps.append(inverse_map)
175
177
 
176
178
  if not spheres:
177
179
  continue
@@ -182,11 +184,14 @@ def potential_sphere_inference(
182
184
  scores_b = torch.zeros(n_b, int(probs.size(-1)), device=device, dtype=probs.dtype)
183
185
 
184
186
  offset = 0
185
- for sphere, idx in zip(spheres, sphere_idx):
186
- count = int(idx.numel())
187
+ for sphere, idx, inverse_map in zip(spheres, sphere_idx, inverse_maps):
188
+ count = int(sphere[pos_key].size(0))
187
189
  sphere_probs = probs[offset : offset + count]
188
190
  offset += count
189
191
  inner = sphere[pos_key].square().sum(dim=-1) < inner_sq
192
+ if inverse_map is not None:
193
+ sphere_probs = sphere_probs[inverse_map]
194
+ inner = inner[inverse_map]
190
195
  rows = idx[inner]
191
196
  scores_b[rows] = ema_smoothing * scores_b[rows] + (1.0 - ema_smoothing) * (
192
197
  sphere_probs[inner].to(scores_b.dtype)
@@ -225,7 +230,9 @@ class PotentialSphereInferer(Inferer):
225
230
  each sphere's softmax predictions into the running per-point scores by an exponential moving average,
226
231
  until every region has been covered about `num_votes` times.
227
232
 
228
- All parameters are forwarded verbatim to `potential_sphere_inference`.
233
+ All parameters are forwarded verbatim to `potential_sphere_inference`, except `seed`, which is offset by
234
+ the number of calls the instance has made: repeated calls (e.g. `TTAInferer` votes) draw different random
235
+ numbers, and a fresh instance replays the same sequence.
229
236
 
230
237
  Example:
231
238
  ```{.python notest}
@@ -248,6 +255,7 @@ class PotentialSphereInferer(Inferer):
248
255
  transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None,
249
256
  pos_key: str = DataKeys.POS,
250
257
  batch_key: str = DataKeys.BATCH,
258
+ inverse_key: Optional[str] = None,
251
259
  progress: bool = False,
252
260
  seed: Optional[int] = None,
253
261
  ) -> None:
@@ -261,14 +269,18 @@ class PotentialSphereInferer(Inferer):
261
269
  self.transform = transform
262
270
  self.pos_key = pos_key
263
271
  self.batch_key = batch_key
272
+ self.inverse_key = inverse_key
264
273
  self.progress = progress
265
274
  self.seed = seed
275
+ self._num_calls = 0
266
276
 
267
277
  def forward(
268
278
  self,
269
279
  data: Dict[str, Any],
270
280
  predictor: Callable[[Dict[str, Any]], Tensor],
271
281
  ) -> Tensor:
282
+ seed = None if self.seed is None else self.seed + self._num_calls
283
+ self._num_calls += 1
272
284
  return potential_sphere_inference(
273
285
  data,
274
286
  predictor=predictor,
@@ -282,6 +294,7 @@ class PotentialSphereInferer(Inferer):
282
294
  transform=self.transform,
283
295
  pos_key=self.pos_key,
284
296
  batch_key=self.batch_key,
297
+ inverse_key=self.inverse_key,
285
298
  progress=self.progress,
286
- seed=self.seed,
299
+ seed=seed,
287
300
  )