cloudanalyzer 0.2.0__tar.gz → 0.4.0__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 (207) hide show
  1. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/PKG-INFO +5 -1
  2. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/__init__.py +1 -1
  3. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/ground_evaluate.py +30 -8
  4. cloudanalyzer-0.4.0/ca/core/map_evaluate.py +302 -0
  5. cloudanalyzer-0.4.0/ca/core/slam_run.py +329 -0
  6. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/height_band.py +5 -5
  7. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/voxel_confusion.py +5 -4
  8. cloudanalyzer-0.4.0/ca/experiments/map_evaluate/common.py +99 -0
  9. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/map_evaluate/evaluate.py +19 -11
  10. cloudanalyzer-0.4.0/ca/experiments/map_evaluate/nn_thresholds.py +16 -0
  11. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/process_docs.py +10 -0
  12. cloudanalyzer-0.4.0/ca/experiments/slam_run/__init__.py +46 -0
  13. cloudanalyzer-0.4.0/ca/experiments/slam_run/common.py +244 -0
  14. cloudanalyzer-0.4.0/ca/experiments/slam_run/evaluate.py +242 -0
  15. cloudanalyzer-0.4.0/ca/experiments/slam_run/identity_passthrough.py +90 -0
  16. cloudanalyzer-0.4.0/ca/experiments/slam_run/kiss_icp_driver.py +126 -0
  17. cloudanalyzer-0.4.0/ca/experiments/slam_run/kiss_slam_driver.py +175 -0
  18. cloudanalyzer-0.4.0/ca/experiments/slam_run/small_gicp_driver.py +179 -0
  19. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/geometry.py +279 -73
  20. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/split.py +19 -7
  21. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/PKG-INFO +5 -1
  22. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/SOURCES.txt +14 -0
  23. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/requires.txt +5 -0
  24. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer_cli/main.py +247 -5
  25. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/pyproject.toml +5 -0
  26. cloudanalyzer-0.4.0/tests/test_geometry_splat_ellipsoid.py +275 -0
  27. cloudanalyzer-0.4.0/tests/test_map_evaluate_core.py +81 -0
  28. cloudanalyzer-0.4.0/tests/test_phase17_vectorize.py +210 -0
  29. cloudanalyzer-0.4.0/tests/test_phase18_vectorize.py +150 -0
  30. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_process_docs.py +3 -0
  31. cloudanalyzer-0.4.0/tests/test_slam_run.py +638 -0
  32. cloudanalyzer-0.2.0/ca/experiments/map_evaluate/common.py +0 -201
  33. cloudanalyzer-0.2.0/ca/experiments/map_evaluate/nn_thresholds.py +0 -135
  34. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/LICENSE +0 -0
  35. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/README.md +0 -0
  36. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/align.py +0 -0
  37. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/baseline_history.py +0 -0
  38. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/batch.py +0 -0
  39. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/benchmark.py +0 -0
  40. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/bundle.py +0 -0
  41. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/compare.py +0 -0
  42. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/convert.py +0 -0
  43. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/__init__.py +0 -0
  44. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/check_baseline_evolution.py +0 -0
  45. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/check_scaffolding.py +0 -0
  46. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/check_triage.py +0 -0
  47. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/checks.py +0 -0
  48. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/web_progressive_loading.py +0 -0
  49. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/web_sampling.py +0 -0
  50. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/core/web_trajectory_sampling.py +0 -0
  51. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/crop.py +0 -0
  52. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/density_map.py +0 -0
  53. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/detection.py +0 -0
  54. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/diff.py +0 -0
  55. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/downsample.py +0 -0
  56. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/evaluate.py +0 -0
  57. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/__init__.py +0 -0
  58. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/__init__.py +0 -0
  59. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/common.py +0 -0
  60. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/evaluate.py +0 -0
  61. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/pareto_promote.py +0 -0
  62. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/stability_window.py +0 -0
  63. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_baseline_evolution/threshold_guard.py +0 -0
  64. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/__init__.py +0 -0
  65. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/common.py +0 -0
  66. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/evaluate.py +0 -0
  67. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/literal_profiles.py +0 -0
  68. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/object_sections.py +0 -0
  69. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_scaffolding/pipeline_overlays.py +0 -0
  70. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/__init__.py +0 -0
  71. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/common.py +0 -0
  72. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/evaluate.py +0 -0
  73. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/pareto_frontier.py +0 -0
  74. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/severity_weighted.py +0 -0
  75. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/check_triage/signature_cluster.py +0 -0
  76. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/__init__.py +0 -0
  77. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/common.py +0 -0
  78. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/evaluate.py +0 -0
  79. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/ground_evaluate/nearest_neighbor.py +0 -0
  80. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/map_evaluate/__init__.py +0 -0
  81. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/map_evaluate/voxel_entropy.py +0 -0
  82. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/__init__.py +0 -0
  83. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/common.py +0 -0
  84. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/distance_shells.py +0 -0
  85. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/evaluate.py +0 -0
  86. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/grid_tiles.py +0 -0
  87. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_progressive_loading/spatial_shuffle.py +0 -0
  88. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/__init__.py +0 -0
  89. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/common.py +0 -0
  90. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/evaluate.py +0 -0
  91. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/functional_voxel.py +0 -0
  92. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/object_random.py +0 -0
  93. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_sampling/pipeline_hybrid.py +0 -0
  94. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/__init__.py +0 -0
  95. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/common.py +0 -0
  96. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/distance_accumulator.py +0 -0
  97. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/evaluate.py +0 -0
  98. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/turn_aware.py +0 -0
  99. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/experiments/web_trajectory_sampling/uniform_stride.py +0 -0
  100. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/filter.py +0 -0
  101. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/ground_evaluate.py +0 -0
  102. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/history.py +0 -0
  103. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/info.py +0 -0
  104. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/io.py +0 -0
  105. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/kitti.py +0 -0
  106. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/log.py +0 -0
  107. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/loop_closure_report.py +0 -0
  108. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/merge.py +0 -0
  109. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/metrics.py +0 -0
  110. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/mme.py +0 -0
  111. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/normals.py +0 -0
  112. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/object_eval.py +0 -0
  113. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/pareto.py +0 -0
  114. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/pipeline.py +0 -0
  115. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/plot.py +0 -0
  116. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/point_summary.py +0 -0
  117. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/posegraph.py +0 -0
  118. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/pr_comment.py +0 -0
  119. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/registration.py +0 -0
  120. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/report.py +0 -0
  121. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/run_evaluate.py +0 -0
  122. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/sample.py +0 -0
  123. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/scan_match_debug.py +0 -0
  124. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/slam_debug.py +0 -0
  125. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/stats.py +0 -0
  126. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/tracking.py +0 -0
  127. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/trajectory.py +0 -0
  128. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/view.py +0 -0
  129. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/visualization.py +0 -0
  130. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/ca/web.py +0 -0
  131. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/dependency_links.txt +0 -0
  132. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/entry_points.txt +0 -0
  133. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer.egg-info/top_level.txt +0 -0
  134. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/cloudanalyzer_cli/__init__.py +0 -0
  135. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/setup.cfg +0 -0
  136. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/setup.py +0 -0
  137. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_align.py +0 -0
  138. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_baseline_history.py +0 -0
  139. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_batch.py +0 -0
  140. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_benchmark.py +0 -0
  141. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_bundle.py +0 -0
  142. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_check_baseline_evolution_process.py +0 -0
  143. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_check_scaffolding_process.py +0 -0
  144. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_check_suite.py +0 -0
  145. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_check_triage_process.py +0 -0
  146. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_cli.py +0 -0
  147. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_compare.py +0 -0
  148. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_convert.py +0 -0
  149. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_crop.py +0 -0
  150. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_density_map.py +0 -0
  151. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_detection_evaluate.py +0 -0
  152. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_detection_tracking_report.py +0 -0
  153. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_diff.py +0 -0
  154. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_downsample.py +0 -0
  155. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_evaluate.py +0 -0
  156. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_filter.py +0 -0
  157. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_geometry.py +0 -0
  158. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_ground_evaluate.py +0 -0
  159. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_ground_evaluate_process.py +0 -0
  160. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_history.py +0 -0
  161. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_info.py +0 -0
  162. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_io.py +0 -0
  163. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_kitti.py +0 -0
  164. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_logging.py +0 -0
  165. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_loop_closure_report.py +0 -0
  166. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_map_evaluate.py +0 -0
  167. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_merge.py +0 -0
  168. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_metrics.py +0 -0
  169. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_mme.py +0 -0
  170. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_normals.py +0 -0
  171. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_object_eval_iou.py +0 -0
  172. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_output_json.py +0 -0
  173. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_pareto.py +0 -0
  174. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_phase16_large_scale.py +0 -0
  175. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_pipeline.py +0 -0
  176. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_plot.py +0 -0
  177. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_posegraph.py +0 -0
  178. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_pr_comment.py +0 -0
  179. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_public_benchmark_pack.py +0 -0
  180. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_public_object_eval_examples.py +0 -0
  181. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_registration.py +0 -0
  182. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_report.py +0 -0
  183. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_run_batch.py +0 -0
  184. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_run_evaluate.py +0 -0
  185. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_sample.py +0 -0
  186. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_scan_match_debug.py +0 -0
  187. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_slam_debug.py +0 -0
  188. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_split.py +0 -0
  189. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_stats.py +0 -0
  190. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_threshold.py +0 -0
  191. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_tracking_evaluate.py +0 -0
  192. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_traj_batch.py +0 -0
  193. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_trajectory.py +0 -0
  194. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_visualization.py +0 -0
  195. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web.py +0 -0
  196. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_progressive_loading_core.py +0 -0
  197. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_progressive_loading_evaluate.py +0 -0
  198. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_progressive_loading_process.py +0 -0
  199. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_progressive_loading_strategies.py +0 -0
  200. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_sampling_core.py +0 -0
  201. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_sampling_evaluate.py +0 -0
  202. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_sampling_process.py +0 -0
  203. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_sampling_strategies.py +0 -0
  204. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_trajectory_sampling_core.py +0 -0
  205. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_trajectory_sampling_evaluate.py +0 -0
  206. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_trajectory_sampling_process.py +0 -0
  207. {cloudanalyzer-0.2.0 → cloudanalyzer-0.4.0}/tests/test_web_trajectory_sampling_strategies.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudanalyzer
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: CLI-first QA toolkit for point cloud, trajectory, and 3D perception outputs
5
5
  Author: rsasaki0109
6
6
  License-Expression: MIT
@@ -36,6 +36,10 @@ Requires-Dist: mypy; extra == "dev"
36
36
  Requires-Dist: pkginfo>=1.12.0; extra == "dev"
37
37
  Requires-Dist: pytest; extra == "dev"
38
38
  Requires-Dist: twine>=6.0.0; extra == "dev"
39
+ Provides-Extra: slam
40
+ Requires-Dist: kiss-icp>=1.2.0; extra == "slam"
41
+ Requires-Dist: kiss-slam>=0.0.2; extra == "slam"
42
+ Requires-Dist: small_gicp>=1.0.0; extra == "slam"
39
43
  Dynamic: license-file
40
44
 
41
45
  # CloudAnalyzer
@@ -1,3 +1,3 @@
1
1
  """CloudAnalyzer - AI-friendly CLI tool for point cloud analysis."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.4.0"
@@ -47,12 +47,34 @@ class GroundEvaluateStrategy(Protocol):
47
47
  """Evaluate ground segmentation quality."""
48
48
 
49
49
 
50
- def _voxel_keys(points: np.ndarray, voxel_size: float) -> set[tuple[int, int, int]]:
51
- """Compute voxel grid keys for an Nx3 point array."""
50
+ def _voxel_keys(points: np.ndarray, voxel_size: float) -> np.ndarray:
51
+ """Compute unique voxel grid keys for an Nx3 point array.
52
+
53
+ Returns an (M, 3) int64 array of unique (i, j, k) voxel indices, sorted
54
+ lexicographically. The original implementation built a ``set`` of Python
55
+ tuples via a per-row generator, which dominated runtime on city-scale
56
+ inputs (millions of points × 4 calls per evaluation).
57
+ """
52
58
  if points.shape[0] == 0:
53
- return set()
59
+ return np.empty((0, 3), dtype=np.int64)
54
60
  indices = np.floor(points / voxel_size).astype(np.int64)
55
- return {(int(row[0]), int(row[1]), int(row[2])) for row in indices}
61
+ return np.unique(indices, axis=0)
62
+
63
+
64
+ def _voxel_intersection_size(a: np.ndarray, b: np.ndarray) -> int:
65
+ """Count voxel keys present in both ``a`` and ``b``.
66
+
67
+ Both inputs are expected to be the unique (M, 3) int64 output of
68
+ :func:`_voxel_keys`. Packs each row into a single ``np.void`` of the row's
69
+ byte length so ``np.intersect1d`` can run in C.
70
+ """
71
+ if a.size == 0 or b.size == 0:
72
+ return 0
73
+ item_size = a.dtype.itemsize * a.shape[1]
74
+ void_dtype = np.dtype((np.void, item_size))
75
+ a_view = np.ascontiguousarray(a).view(void_dtype).ravel()
76
+ b_view = np.ascontiguousarray(b).view(void_dtype).ravel()
77
+ return int(np.intersect1d(a_view, b_view, assume_unique=True).size)
56
78
 
57
79
 
58
80
  def confusion_metrics(tp: int, fp: int, fn: int, tn: int) -> dict[str, float]:
@@ -83,10 +105,10 @@ class VoxelConfusionGroundEvaluateStrategy:
83
105
  ref_ground_voxels = _voxel_keys(request.reference_ground, request.voxel_size)
84
106
  ref_nonground_voxels = _voxel_keys(request.reference_nonground, request.voxel_size)
85
107
 
86
- tp = len(est_ground_voxels & ref_ground_voxels)
87
- fp = len(est_ground_voxels & ref_nonground_voxels)
88
- fn = len(est_nonground_voxels & ref_ground_voxels)
89
- tn = len(est_nonground_voxels & ref_nonground_voxels)
108
+ tp = _voxel_intersection_size(est_ground_voxels, ref_ground_voxels)
109
+ fp = _voxel_intersection_size(est_ground_voxels, ref_nonground_voxels)
110
+ fn = _voxel_intersection_size(est_nonground_voxels, ref_ground_voxels)
111
+ tn = _voxel_intersection_size(est_nonground_voxels, ref_nonground_voxels)
90
112
  metrics = confusion_metrics(tp, fp, fn, tn)
91
113
 
92
114
  return GroundEvaluateResult(
@@ -0,0 +1,302 @@
1
+ """Stable, minimal interface for point-cloud map evaluation.
2
+
3
+ This slice started as ``ca/experiments/map_evaluate/`` with two
4
+ non-comparable strategies on separate metric-family lanes:
5
+
6
+ - ``nn_thresholds`` — reference-based (GT-aware) MapEval-style accuracy and
7
+ completeness@τ via per-point nearest-neighbor distances (scipy cKDTree).
8
+ - ``voxel_entropy`` — reference-free self-consistency proxy via voxelized
9
+ neighborhood occupancy entropy.
10
+
11
+ Because the two strategies do not compete head-to-head, "promoting to core"
12
+ means lifting the **request/result contract plus the adopted reference-based
13
+ strategy** here, leaving ``voxel_entropy`` in ``ca/experiments/map_evaluate``
14
+ as the orthogonal reference-free option. Callers that just want GT-based
15
+ metrics can now depend on ``ca.core.map_evaluate`` directly and the CLI
16
+ no longer reaches into ``ca.experiments``.
17
+
18
+ The reference-free lane remains experimental until we settle on a single
19
+ GT-free metric — anyone needing it for now imports from
20
+ ``ca.experiments.map_evaluate.voxel_entropy`` explicitly.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Any, Protocol
28
+
29
+ import numpy as np
30
+
31
+
32
+ # ---------------------------------------------------------------- contract
33
+
34
+
35
+ @dataclass(slots=True)
36
+ class MapEvaluateRequest:
37
+ """Compare an estimated point cloud map to a reference (optional).
38
+
39
+ Strategies with ``reference_required=True`` raise on ``reference_points=None``;
40
+ reference-free strategies treat the same field as unused.
41
+ """
42
+
43
+ estimated_points: np.ndarray # (N, 3)
44
+ reference_points: np.ndarray | None = None # (M, 3) when present
45
+ # MapEval-inspired: allow using an external coarse alignment.
46
+ initial_transform_4x4: np.ndarray | None = None # shape (4, 4)
47
+ # "none" = use estimated_points as-is, "initial" = pre-apply initial_transform_4x4.
48
+ align_mode: str = "none"
49
+ downsample_voxel_size: float = 0.0
50
+ # Optional output dir for strategy-specific artifacts (colored PLYs, etc.)
51
+ artifact_dir: str | None = None
52
+ # MapEval "accuracy_level" thresholds for inlier ratios.
53
+ thresholds_m: tuple[float, ...] = (0.2, 0.1, 0.08, 0.05, 0.01)
54
+ # A coarse voxel size useful for structure-aware proxies.
55
+ structure_voxel_size: float = 0.5
56
+
57
+
58
+ @dataclass(slots=True)
59
+ class MapEvaluateResult:
60
+ """Common output for all strategies.
61
+
62
+ Classification fields (``metric_family``, ``reference_required``, ``mode``,
63
+ ``sampling_policy``) describe *how* the metrics were computed so consumers
64
+ (CI gates, reports, batch aggregators) can keep reference-based and
65
+ reference-free metrics in separate lanes without parsing metric names.
66
+ """
67
+
68
+ strategy: str
69
+ design: str
70
+ metrics: dict[str, float]
71
+ artifacts: dict[str, Any]
72
+ metric_family: str = "unspecified"
73
+ reference_required: bool = False
74
+ mode: str = "exact"
75
+ sampling_policy: dict[str, Any] = field(default_factory=dict)
76
+
77
+
78
+ class MapEvaluateStrategy(Protocol):
79
+ """Protocol kept in core after promoting the reference-based strategy."""
80
+
81
+ name: str
82
+ design: str
83
+
84
+ def evaluate(self, request: MapEvaluateRequest) -> MapEvaluateResult:
85
+ """Evaluate a map and return classified metrics."""
86
+
87
+
88
+ # ---------------------------------------------------------------- helpers
89
+
90
+
91
+ def _require_xyz(points: np.ndarray, name: str) -> np.ndarray:
92
+ arr = np.asarray(points, dtype=np.float64)
93
+ if arr.ndim != 2 or arr.shape[1] != 3:
94
+ raise ValueError(f"{name} must be shape (N, 3); got {arr.shape}")
95
+ return arr
96
+
97
+
98
+ def _require_transform_4x4(matrix: np.ndarray, name: str) -> np.ndarray:
99
+ mat = np.asarray(matrix, dtype=np.float64)
100
+ if mat.shape != (4, 4):
101
+ raise ValueError(f"{name} must be shape (4, 4); got {mat.shape}")
102
+ return mat
103
+
104
+
105
+ def apply_transform(points: np.ndarray, transform_4x4: np.ndarray) -> np.ndarray:
106
+ pts = _require_xyz(points, "points")
107
+ t = _require_transform_4x4(transform_4x4, "transform_4x4")
108
+ if pts.shape[0] == 0:
109
+ return pts
110
+ hom = np.concatenate([pts, np.ones((pts.shape[0], 1), dtype=np.float64)], axis=1)
111
+ out = (hom @ t.T)[:, :3]
112
+ return np.asarray(out, dtype=np.float64)
113
+
114
+
115
+ def aligned_estimated_points(request: MapEvaluateRequest) -> np.ndarray:
116
+ est = _require_xyz(request.estimated_points, "estimated_points")
117
+ mode = (request.align_mode or "none").strip().lower()
118
+ if mode == "none":
119
+ return est
120
+ if mode == "initial":
121
+ if request.initial_transform_4x4 is None:
122
+ raise ValueError("align_mode='initial' requires initial_transform_4x4.")
123
+ return apply_transform(est, request.initial_transform_4x4)
124
+ raise ValueError(f"Unsupported align_mode: {request.align_mode!r}. Use 'none' or 'initial'.")
125
+
126
+
127
+ def ensure_artifact_dir(request: MapEvaluateRequest, subdir: str) -> Path | None:
128
+ """Return an ensured artifact directory path or None if disabled."""
129
+ if request.artifact_dir is None:
130
+ return None
131
+ root = Path(request.artifact_dir)
132
+ out = root / subdir
133
+ out.mkdir(parents=True, exist_ok=True)
134
+ return out
135
+
136
+
137
+ def voxel_downsample(points: np.ndarray, voxel_size: float) -> np.ndarray:
138
+ """Deterministic voxel downsample by centroid per voxel."""
139
+ pts = _require_xyz(points, "points")
140
+ if pts.shape[0] == 0 or voxel_size <= 0:
141
+ return pts
142
+ keys = np.floor(pts / voxel_size).astype(np.int64)
143
+ order = np.lexsort((keys[:, 2], keys[:, 1], keys[:, 0]))
144
+ keys_sorted = keys[order]
145
+ pts_sorted = pts[order]
146
+ unique, start_idx = np.unique(keys_sorted, axis=0, return_index=True)
147
+ centroids: list[np.ndarray] = []
148
+ for i, s in enumerate(start_idx):
149
+ e = start_idx[i + 1] if i + 1 < len(start_idx) else pts_sorted.shape[0]
150
+ centroids.append(pts_sorted[s:e].mean(axis=0))
151
+ out = np.vstack(centroids) if centroids else np.zeros((0, 3), dtype=np.float64)
152
+ _ = unique # kept to mirror the index ordering for documentation
153
+ return np.asarray(out)
154
+
155
+
156
+ # ------------------------------------------------------- adopted strategy
157
+
158
+
159
+ def _min_distances_kdtree(a: np.ndarray, b: np.ndarray) -> np.ndarray:
160
+ """Per-point min Euclidean distance from ``a`` to ``b`` via scipy cKDTree."""
161
+ if a.shape[0] == 0:
162
+ return np.zeros((0,), dtype=np.float64)
163
+ if b.shape[0] == 0:
164
+ return np.full((a.shape[0],), np.inf, dtype=np.float64)
165
+
166
+ from scipy.spatial import cKDTree
167
+
168
+ tree = cKDTree(np.asarray(b, dtype=np.float64))
169
+ distances, _ = tree.query(np.asarray(a, dtype=np.float64), k=1)
170
+ return np.asarray(distances, dtype=np.float64)
171
+
172
+
173
+ def _error_colors(dist_m: np.ndarray, vmax_m: float) -> np.ndarray:
174
+ """Green→red ramp for distance visualization."""
175
+ vmax = float(max(vmax_m, 1e-12))
176
+ x = np.clip(dist_m / vmax, 0.0, 1.0)
177
+ r = x
178
+ g = 1.0 - x
179
+ b = np.zeros_like(x)
180
+ return np.column_stack([r, g, b]).astype(np.float64)
181
+
182
+
183
+ def _write_colored_ply(points: np.ndarray, colors: np.ndarray, path: str) -> None:
184
+ import open3d as o3d
185
+
186
+ pcd = o3d.geometry.PointCloud()
187
+ pcd.points = o3d.utility.Vector3dVector(np.asarray(points, dtype=np.float64))
188
+ pcd.colors = o3d.utility.Vector3dVector(np.asarray(colors, dtype=np.float64))
189
+ o3d.io.write_point_cloud(path, pcd, write_ascii=False)
190
+
191
+
192
+ @dataclass(slots=True)
193
+ class NNThresholdMapEvaluateStrategy:
194
+ """MapEval-style accuracy@τ / completeness@τ via per-point NN distances.
195
+
196
+ Promoted from ``ca/experiments/map_evaluate/nn_thresholds.py`` as the
197
+ adopted reference-based map evaluation strategy. The reference-free
198
+ ``voxel_entropy`` lane stays under ``ca.experiments`` because the two
199
+ strategies do not compete on the same metric family.
200
+ """
201
+
202
+ name: str = "nn_thresholds"
203
+ design: str = "functional"
204
+
205
+ def evaluate(self, request: MapEvaluateRequest) -> MapEvaluateResult:
206
+ est_aligned = aligned_estimated_points(request)
207
+ est = voxel_downsample(_require_xyz(est_aligned, "estimated_points"), request.downsample_voxel_size)
208
+ if request.reference_points is None:
209
+ raise ValueError("nn_thresholds requires reference_points (GT).")
210
+ ref = voxel_downsample(_require_xyz(request.reference_points, "reference_points"), request.downsample_voxel_size)
211
+
212
+ est_to_ref = _min_distances_kdtree(est, ref)
213
+ ref_to_est = _min_distances_kdtree(ref, est)
214
+
215
+ thresholds = tuple(float(x) for x in request.thresholds_m)
216
+ metrics: dict[str, float] = {
217
+ "n_est": float(est.shape[0]),
218
+ "n_ref": float(ref.shape[0]),
219
+ "mean_est_to_ref_m": float(np.mean(est_to_ref)) if est_to_ref.size else float("inf"),
220
+ "mean_ref_to_est_m": float(np.mean(ref_to_est)) if ref_to_est.size else float("inf"),
221
+ "chamfer_m": (
222
+ float(np.mean(est_to_ref)) + float(np.mean(ref_to_est))
223
+ if (est_to_ref.size and ref_to_est.size)
224
+ else float("inf")
225
+ ),
226
+ }
227
+
228
+ for t in thresholds:
229
+ metrics[f"accuracy@{t:.3f}m"] = float(np.mean(est_to_ref <= t)) if est_to_ref.size else 0.0
230
+ metrics[f"completeness@{t:.3f}m"] = float(np.mean(ref_to_est <= t)) if ref_to_est.size else 0.0
231
+
232
+ # One scalar summary (F-score) using the first threshold.
233
+ t0 = thresholds[0] if thresholds else 0.2
234
+ acc = metrics.get(f"accuracy@{t0:.3f}m", 0.0)
235
+ com = metrics.get(f"completeness@{t0:.3f}m", 0.0)
236
+ fscore = (2 * acc * com / (acc + com)) if (acc + com) > 0 else 0.0
237
+ metrics[f"fscore@{t0:.3f}m"] = float(fscore)
238
+
239
+ artifacts: dict[str, object] = {
240
+ "thresholds_m": thresholds,
241
+ "downsample_voxel_size": float(request.downsample_voxel_size),
242
+ "align_mode": request.align_mode,
243
+ }
244
+
245
+ # Optional: MapEval-style raw/inlier error visualization PLYs.
246
+ out_dir = ensure_artifact_dir(request, "map_evaluate/nn_thresholds")
247
+ if out_dir is not None and est.shape[0] > 0:
248
+ vmax = thresholds[0] if thresholds else 0.2
249
+ raw_colors = _error_colors(est_to_ref, vmax_m=vmax)
250
+ raw_path = str(out_dir / "estimated_error_raw.ply")
251
+ _write_colored_ply(est, raw_colors, raw_path)
252
+
253
+ inlier_mask = est_to_ref <= vmax
254
+ inlier_points = est[inlier_mask]
255
+ inlier_colors = raw_colors[inlier_mask]
256
+ inlier_path = str(out_dir / f"estimated_error_inlier_{vmax:.3f}m.ply")
257
+ _write_colored_ply(inlier_points, inlier_colors, inlier_path)
258
+
259
+ artifacts["estimated_error_raw_ply"] = raw_path
260
+ artifacts[f"estimated_error_inlier_{vmax:.3f}m_ply"] = inlier_path
261
+
262
+ downsample_voxel = float(request.downsample_voxel_size)
263
+ mode = "voxelized" if downsample_voxel > 0 else "exact"
264
+ sampling_policy: dict[str, object] = {
265
+ "downsample_voxel_size_m": downsample_voxel,
266
+ "thresholds_m": list(thresholds),
267
+ "align_mode": request.align_mode,
268
+ "nn_backend": "scipy_ckdtree",
269
+ }
270
+
271
+ return MapEvaluateResult(
272
+ strategy=self.name,
273
+ design=self.design,
274
+ metrics=metrics,
275
+ artifacts=artifacts,
276
+ metric_family="reference_based_nn_thresholds",
277
+ reference_required=True,
278
+ mode=mode,
279
+ sampling_policy=sampling_policy,
280
+ )
281
+
282
+
283
+ def evaluate_map(
284
+ request: MapEvaluateRequest,
285
+ strategy: MapEvaluateStrategy | None = None,
286
+ ) -> MapEvaluateResult:
287
+ """Evaluate a map using the stabilized strategy (defaults to NNThreshold)."""
288
+ eval_strategy = strategy or NNThresholdMapEvaluateStrategy()
289
+ return eval_strategy.evaluate(request)
290
+
291
+
292
+ __all__ = [
293
+ "MapEvaluateRequest",
294
+ "MapEvaluateResult",
295
+ "MapEvaluateStrategy",
296
+ "NNThresholdMapEvaluateStrategy",
297
+ "aligned_estimated_points",
298
+ "apply_transform",
299
+ "ensure_artifact_dir",
300
+ "evaluate_map",
301
+ "voxel_downsample",
302
+ ]