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.
- {reverse3d-0.11.2/reverse3d.egg-info → reverse3d-0.11.3}/PKG-INFO +1 -1
- reverse3d-0.11.3/reverse3d/_version.py +1 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/silhouette.py +48 -19
- {reverse3d-0.11.2 → reverse3d-0.11.3/reverse3d.egg-info}/PKG-INFO +1 -1
- reverse3d-0.11.2/reverse3d/_version.py +0 -1
- {reverse3d-0.11.2 → reverse3d-0.11.3}/LICENSE +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/NOTICE +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/README.md +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/pyproject.toml +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/__main__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/_about.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/compact.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/mcp_server.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/prompts.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/skill/SKILL.md +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/agent/tools.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/autopilot.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/cli.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/config.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/doctor.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/errors.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/hardware.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/imaging.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/logging_utils.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/mesh_io.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/offline.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/profiles.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/py.typed +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/recipes.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/depth_estimation.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/photogrammetry.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/pointcloud.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reconstruction/texture.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/_blender_job.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/blender_render.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/rendering/preview.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/geometry_fit.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/measure.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/mesh_cleaner.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/reverse_eng/parametric.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/scenes.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/security.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/setup_agents.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/userconfig.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/__init__.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/classifier.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/feature_matcher.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/mask_extractor.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/vision/segmenter.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d/watch.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/SOURCES.txt +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/dependency_links.txt +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/entry_points.txt +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/requires.txt +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/reverse3d.egg-info/top_level.txt +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/setup.cfg +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_agent_tools.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_authorship.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_automation.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_cli.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_cli_automation.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_color.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_config.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_geometry_fit.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_heic.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_mesh.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_offline_autopilot.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_optional_extras.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_reconstruction.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_render.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_scenes.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_security.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_silhouette.py +0 -0
- {reverse3d-0.11.2 → reverse3d-0.11.3}/tests/test_texture.py +0 -0
- {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.
|
|
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
|
-
|
|
788
|
-
cube =
|
|
789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|