reverse3d 0.11.2__tar.gz → 0.11.3__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 (80) hide show
  1. {reverse3d-0.11.2/reverse3d.egg-info → reverse3d-0.11.3}/PKG-INFO +1 -1
  2. reverse3d-0.11.3/reverse3d/_version.py +1 -0
  3. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/silhouette.py +48 -19
  4. {reverse3d-0.11.2 → reverse3d-0.11.3/reverse3d.egg-info}/PKG-INFO +1 -1
  5. reverse3d-0.11.2/reverse3d/_version.py +0 -1
  6. {reverse3d-0.11.2 → reverse3d-0.11.3}/LICENSE +0 -0
  7. {reverse3d-0.11.2 → reverse3d-0.11.3}/NOTICE +0 -0
  8. {reverse3d-0.11.2 → reverse3d-0.11.3}/README.md +0 -0
  9. {reverse3d-0.11.2 → reverse3d-0.11.3}/pyproject.toml +0 -0
  10. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/__init__.py +0 -0
  11. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/__main__.py +0 -0
  12. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/_about.py +0 -0
  13. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/__init__.py +0 -0
  14. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/compact.py +0 -0
  15. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/mcp_server.py +0 -0
  16. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/prompts.py +0 -0
  17. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/skill/SKILL.md +0 -0
  18. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/tools.py +0 -0
  19. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/autopilot.py +0 -0
  20. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/cli.py +0 -0
  21. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/config.py +0 -0
  22. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/doctor.py +0 -0
  23. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/errors.py +0 -0
  24. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/hardware.py +0 -0
  25. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/imaging.py +0 -0
  26. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/logging_utils.py +0 -0
  27. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/mesh_io.py +0 -0
  28. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/offline.py +0 -0
  29. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/profiles.py +0 -0
  30. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/py.typed +0 -0
  31. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/recipes.py +0 -0
  32. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/__init__.py +0 -0
  33. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/depth_estimation.py +0 -0
  34. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/photogrammetry.py +0 -0
  35. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/pointcloud.py +0 -0
  36. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/texture.py +0 -0
  37. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/__init__.py +0 -0
  38. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/_blender_job.py +0 -0
  39. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/blender_render.py +0 -0
  40. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/preview.py +0 -0
  41. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/__init__.py +0 -0
  42. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/geometry_fit.py +0 -0
  43. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/measure.py +0 -0
  44. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/mesh_cleaner.py +0 -0
  45. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/parametric.py +0 -0
  46. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/scenes.py +0 -0
  47. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/security.py +0 -0
  48. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/setup_agents.py +0 -0
  49. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/userconfig.py +0 -0
  50. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/__init__.py +0 -0
  51. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/classifier.py +0 -0
  52. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/feature_matcher.py +0 -0
  53. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/mask_extractor.py +0 -0
  54. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/segmenter.py +0 -0
  55. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/watch.py +0 -0
  56. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/SOURCES.txt +0 -0
  57. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/dependency_links.txt +0 -0
  58. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/entry_points.txt +0 -0
  59. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/requires.txt +0 -0
  60. {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/top_level.txt +0 -0
  61. {reverse3d-0.11.2 → reverse3d-0.11.3}/setup.cfg +0 -0
  62. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_agent_tools.py +0 -0
  63. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_authorship.py +0 -0
  64. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_automation.py +0 -0
  65. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_cli.py +0 -0
  66. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_cli_automation.py +0 -0
  67. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_color.py +0 -0
  68. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_config.py +0 -0
  69. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_geometry_fit.py +0 -0
  70. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_heic.py +0 -0
  71. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_mesh.py +0 -0
  72. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_offline_autopilot.py +0 -0
  73. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_optional_extras.py +0 -0
  74. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_reconstruction.py +0 -0
  75. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_render.py +0 -0
  76. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_scenes.py +0 -0
  77. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_security.py +0 -0
  78. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_silhouette.py +0 -0
  79. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_texture.py +0 -0
  80. {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_vision.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reverse3d
3
- Version: 0.11.2
3
+ Version: 0.11.3
4
4
  Summary: AI-agent-ready toolkit for 3D reverse engineering: image analysis, photogrammetry, mesh repair, primitive fitting and headless rendering.
5
5
  Author-email: Voxel Enterprise <info@voxel.lat>
6
6
  Maintainer-email: Voxel Enterprise <info@voxel.lat>
@@ -0,0 +1 @@
1
+ __version__ = "0.11.3"
@@ -298,6 +298,9 @@ def _support_plane(
298
298
  return None
299
299
 
300
300
 
301
+ # A re-cut may add what the first boxes cropped (a base: well under +60 %), not more.
302
+ _MAX_GROWTH = 1.6
303
+
301
304
  # Base completion: the typical cross-section is measured in this band of the
302
305
  # object's height, and a complete one is searched for no higher than this.
303
306
  _BASE_TYPICAL = (0.2, 0.6)
@@ -679,6 +682,20 @@ def visual_hull(
679
682
 
680
683
  n = resolution
681
684
 
685
+ def project_all(grid: np.ndarray) -> list[tuple[np.ndarray, np.ndarray, np.ndarray]]:
686
+ """Pixel of every voxel in every photo (int16, see below) and whether it is inside."""
687
+ projections = []
688
+ for (small, scale), view in zip(images, views, strict=True):
689
+ uv, z = _project(grid, view)
690
+ # int16 pixel coordinates (work images are at most 640 px): 4x less memory
691
+ # than int64 for millions of voxels times hundreds of photos.
692
+ u = np.clip(np.round(uv[:, 0] * scale), -1, _PIXEL_MAX).astype(np.int16)
693
+ v = np.clip(np.round(uv[:, 1] * scale), -1, _PIXEL_MAX).astype(np.int16)
694
+ inside = (z > 0) & (u >= 0) & (v >= 0) & (u < small.shape[1]) & (v < small.shape[0])
695
+ projections.append((u, v, inside))
696
+
697
+ return projections
698
+
682
699
  def frame(
683
700
  center: np.ndarray,
684
701
  radius: float,
@@ -715,16 +732,7 @@ def visual_hull(
715
732
  axis = np.linspace(-radius, radius, n)
716
733
  gx, gy, gz = np.meshgrid(axis, axis, axis, indexing="ij")
717
734
  grid = np.stack([gx.ravel(), gy.ravel(), gz.ravel()], axis=1) + center
718
- projections = []
719
- for (small, scale), view in zip(images, views, strict=True):
720
- uv, z = _project(grid, view)
721
- # int16 pixel coordinates (work images are at most 640 px): 4x less memory
722
- # than int64 for millions of voxels times hundreds of photos.
723
- u = np.clip(np.round(uv[:, 0] * scale), -1, _PIXEL_MAX).astype(np.int16)
724
- v = np.clip(np.round(uv[:, 1] * scale), -1, _PIXEL_MAX).astype(np.int16)
725
- inside = (z > 0) & (u >= 0) & (v >= 0) & (u < small.shape[1]) & (v < small.shape[0])
726
- projections.append((u, v, inside))
727
-
735
+ projections = project_all(grid)
728
736
  return masks, grid, projections
729
737
 
730
738
  masks, grid, projections = frame(center, radius)
@@ -781,32 +789,46 @@ def visual_hull(
781
789
  return (seen >= 3) & (votes >= seen - allowed)
782
790
 
783
791
  keep = carve(masks, 2)
792
+
784
793
  # The cameras' convergence point is biased when many photos are close-ups of one
785
794
  # part (a cap): if the carved object reaches the edge of the cube, re-centre the
786
795
  # cube on it, enlarge it and start again (masks included, their box depends on it).
787
- for _ in range(2):
788
- cube = keep.reshape(n, n, n)
789
- if not (
796
+ def touches_edge(mask: np.ndarray) -> bool:
797
+ cube = mask.reshape(n, n, n)
798
+ return bool(
790
799
  cube[0].any()
791
800
  or cube[-1].any()
792
801
  or cube[:, 0].any()
793
802
  or cube[:, -1].any()
794
803
  or cube[:, :, 0].any()
795
804
  or cube[:, :, -1].any()
796
- ):
797
- break
805
+ )
806
+
807
+ if touches_edge(keep):
808
+ # Re-centred once; kept only if the object then fits (a cut-out that leaked
809
+ # into the table or the room would keep growing instead).
810
+ saved = (center, radius, masks, grid, keep)
798
811
  occupied = grid[keep]
799
812
  center = (occupied.min(axis=0) + occupied.max(axis=0)) / 2
800
813
  radius *= 1.5
801
- logger.info("Object reaches the carving volume's edge: re-centred and enlarged")
802
814
  projections.clear() # free the previous projections first (gigabytes)
803
815
  masks, grid, projections = frame(center, radius)
804
816
  keep = carve(masks, 2)
817
+ if touches_edge(keep) or int(keep.sum()) > _MAX_GROWTH * 3.375 * int(saved[4].sum()):
818
+ logger.info("Re-centring did not help; keeping the first carving volume")
819
+ center, radius, masks, grid, keep = saved
820
+ projections.clear()
821
+ projections = project_all(grid)
822
+ else:
823
+ logger.info("Object reached the carving volume's edge: re-centred and enlarged")
805
824
  # The first boxes come from the cameras' convergence point and can crop the object
806
825
  # (its base, when many photos are close-ups of the top). Re-cut every photo inside
807
- # a box around where the carved object projects, with margin, while it grows.
826
+ # a box around where the carved object projects, with margin, while it grows -
827
+ # but only by what a cropped base can explain: a cut-out that leaks into the
828
+ # table or the room grows far more, and is undone.
808
829
  for _ in range(2):
809
830
  before = int(keep.sum())
831
+ saved_masks, saved_keep = masks, keep
810
832
  boxes = [
811
833
  _box_around(keep & inside, u, v, small.shape)
812
834
  for (u, v, inside), (small, _) in zip(projections, images, strict=True)
@@ -814,9 +836,16 @@ def visual_hull(
814
836
  projections.clear() # free the previous projections first (gigabytes)
815
837
  masks, grid, projections = frame(center, radius, boxes)
816
838
  keep = carve(masks, 2)
817
- if int(keep.sum()) < 1.05 * before:
839
+ after = int(keep.sum())
840
+ if after > _MAX_GROWTH * before or (touches_edge(keep) and not touches_edge(saved_keep)):
841
+ logger.info(
842
+ "Photo boxes leaked into the background (%d -> %d voxels): undone", before, after
843
+ )
844
+ masks, keep = saved_masks, saved_keep
845
+ break
846
+ if after < 1.05 * before:
818
847
  break
819
- logger.info("Object grew to %d voxels with photo boxes around it", int(keep.sum()))
848
+ logger.info("Object grew to %d voxels with photo boxes around it", after)
820
849
  # The AI segmenter is only loaded (or downloaded) once the photos proved usable.
821
850
  ai = segmenter() if segmenter is not None else None
822
851
  if ai is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reverse3d
3
- Version: 0.11.2
3
+ Version: 0.11.3
4
4
  Summary: AI-agent-ready toolkit for 3D reverse engineering: image analysis, photogrammetry, mesh repair, primitive fitting and headless rendering.
5
5
  Author-email: Voxel Enterprise <info@voxel.lat>
6
6
  Maintainer-email: Voxel Enterprise <info@voxel.lat>
@@ -1 +0,0 @@
1
- __version__ = "0.11.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes