autoforge 1.9.2.2__tar.gz → 1.9.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 (60) hide show
  1. {autoforge-1.9.2.2 → autoforge-1.9.3}/PKG-INFO +4 -3
  2. {autoforge-1.9.2.2 → autoforge-1.9.3}/README.md +1 -0
  3. {autoforge-1.9.2.2 → autoforge-1.9.3}/pyproject.toml +3 -3
  4. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/PKG-INFO +4 -3
  5. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/SOURCES.txt +2 -1
  6. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/requires.txt +1 -1
  7. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OutputHelper.py +49 -16
  8. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/PruningHelper.py +99 -0
  9. autoforge-1.9.3/src/autoforge/Misc/color_fit_all.py +448 -0
  10. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Modules/Optimizer.py +99 -2
  11. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/auto_forge.py +13 -0
  12. autoforge-1.9.3/tests/test_remove_height_spikes.py +84 -0
  13. autoforge-1.9.2.2/src/autoforge/Misc/color_fit_all.py +0 -1892
  14. {autoforge-1.9.2.2 → autoforge-1.9.3}/setup.cfg +0 -0
  15. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/dependency_links.txt +0 -0
  16. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/entry_points.txt +0 -0
  17. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/top_level.txt +0 -0
  18. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/AmpUtils.py +0 -0
  19. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/CAdamW.py +0 -0
  20. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/FilamentHelper.py +0 -0
  21. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +0 -0
  22. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
  23. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
  24. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/ImageHelper.py +0 -0
  25. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OptimizerHelper.py +0 -0
  26. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OtherHelper.py +0 -0
  27. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/__init__.py +0 -0
  28. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/filamentcolors_library.py +0 -0
  29. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/__init__.py +0 -0
  30. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
  31. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/plot_results.py +0 -0
  32. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/LossFunctions.py +0 -0
  33. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/PerceptionLoss.py +0 -0
  34. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/__init__.py +0 -0
  35. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Misc/__init__.py +0 -0
  36. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Modules/__init__.py +0 -0
  37. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/__init__.py +0 -0
  38. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/__main__.py +0 -0
  39. {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/priority_mask_editor.py +0 -0
  40. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_amp_autocast_probe.py +0 -0
  41. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_cadamw.py +0 -0
  42. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_christofides_height_map.py +0 -0
  43. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_christofides_heightmap.py +0 -0
  44. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_depth_adjusted_height_map.py +0 -0
  45. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_depth_estimate_height_map.py +0 -0
  46. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_end_to_end_optimizer.py +0 -0
  47. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_filament_and_output.py +0 -0
  48. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_filament_helper.py +0 -0
  49. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_flatforge.py +0 -0
  50. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_heightmap_init.py +0 -0
  51. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_image_helper.py +0 -0
  52. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_loss_functions.py +0 -0
  53. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_helper.py +0 -0
  54. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_module.py +0 -0
  55. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_smoke.py +0 -0
  56. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_output_helper.py +0 -0
  57. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_output_helper_extended.py +0 -0
  58. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_perception_and_pruning.py +0 -0
  59. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_property_based.py +0 -0
  60. {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_pruning_helper.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.2.2
3
+ Version: 1.9.3
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
7
- Requires-Python: >=3.9
7
+ Requires-Python: >=3.10
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: numpy
10
- Requires-Dist: torch>=2.6.0
10
+ Requires-Dist: torch>=2.9.1
11
11
  Requires-Dist: torchvision>=0.21.0
12
12
  Requires-Dist: transformers>=4.48.1
13
13
  Requires-Dist: opencv-python>=4.2.0.32
@@ -158,6 +158,7 @@ This will generate separate STL files for each color, allowing you to print face
158
158
  - `--pruning_max_layer` Max number of layers allowed after pruning (default: 75).
159
159
  - `--random_seed` Random seed for reproducibility (default: 0 (disabled)).
160
160
  - `--mps` Flag to use the Metal Performance Shaders (MPS) backend if available.
161
+ - `--no-spike-removal` Disable spike removal for the final STL (not recommended).
161
162
 
162
163
  - `--tensorboard` Flag to enable TensorBoard logging.
163
164
  - `--run_name` *(Optional)* Name of the run used for TensorBoard logging.
@@ -133,6 +133,7 @@ This will generate separate STL files for each color, allowing you to print face
133
133
  - `--pruning_max_layer` Max number of layers allowed after pruning (default: 75).
134
134
  - `--random_seed` Random seed for reproducibility (default: 0 (disabled)).
135
135
  - `--mps` Flag to use the Metal Performance Shaders (MPS) backend if available.
136
+ - `--no-spike-removal` Disable spike removal for the final STL (not recommended).
136
137
 
137
138
  - `--tensorboard` Flag to enable TensorBoard logging.
138
139
  - `--run_name` *(Optional)* Name of the run used for TensorBoard logging.
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "AutoForge"
7
- version="1.9.2.2"
7
+ version="1.9.3"
8
8
  description = "AutoForge is a Python tool for generating 3D printed layered models from an input image."
9
9
  authors = [{ name = "Hendric Voss", email = "hvoss@techfak.uni-bielefeld.de" }]
10
10
  readme = "README.md"
11
- requires-python = ">=3.9"
11
+ requires-python = ">=3.10"
12
12
  license = { text = "CC BY-NC-SA 4.0" }
13
13
  dependencies = [
14
14
  "numpy",
15
- "torch>=2.6.0",
15
+ "torch>=2.9.1",
16
16
  "torchvision>=0.21.0",
17
17
  "transformers>=4.48.1",
18
18
  "opencv-python>=4.2.0.32",
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.2.2
3
+ Version: 1.9.3
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
7
- Requires-Python: >=3.9
7
+ Requires-Python: >=3.10
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: numpy
10
- Requires-Dist: torch>=2.6.0
10
+ Requires-Dist: torch>=2.9.1
11
11
  Requires-Dist: torchvision>=0.21.0
12
12
  Requires-Dist: transformers>=4.48.1
13
13
  Requires-Dist: opencv-python>=4.2.0.32
@@ -158,6 +158,7 @@ This will generate separate STL files for each color, allowing you to print face
158
158
  - `--pruning_max_layer` Max number of layers allowed after pruning (default: 75).
159
159
  - `--random_seed` Random seed for reproducibility (default: 0 (disabled)).
160
160
  - `--mps` Flag to use the Metal Performance Shaders (MPS) backend if available.
161
+ - `--no-spike-removal` Disable spike removal for the final STL (not recommended).
161
162
 
162
163
  - `--tensorboard` Flag to enable TensorBoard logging.
163
164
  - `--run_name` *(Optional)* Name of the run used for TensorBoard logging.
@@ -53,4 +53,5 @@ tests/test_output_helper.py
53
53
  tests/test_output_helper_extended.py
54
54
  tests/test_perception_and_pruning.py
55
55
  tests/test_property_based.py
56
- tests/test_pruning_helper.py
56
+ tests/test_pruning_helper.py
57
+ tests/test_remove_height_spikes.py
@@ -1,5 +1,5 @@
1
1
  numpy
2
- torch>=2.6.0
2
+ torch>=2.9.1
3
3
  torchvision>=0.21.0
4
4
  transformers>=4.48.1
5
5
  opencv-python>=4.2.0.32
@@ -906,19 +906,52 @@ def _save_stl_with_manifold_fix(triangles, filename):
906
906
  buffer.write(struct.pack("<I", num_triangles))
907
907
  buffer.write(stl_data.tobytes())
908
908
  buffer.seek(0)
909
-
910
- # Load with trimesh and fix manifold issues
911
- try:
912
- mesh = trimesh.load(buffer, file_type="stl")
913
- mesh.merge_vertices()
914
- mesh.remove_degenerate_faces()
915
- mesh.remove_duplicate_faces()
916
- mesh.fill_holes()
917
- mesh.fix_normals()
918
- mesh.export(filename)
919
- except Exception as e:
920
- print(f"Warning: Error fixing manifold for {filename}: {e}")
921
- # Fall back to saving without manifold fix
922
- with open(filename, "wb") as f:
923
- buffer.seek(0)
924
- f.write(buffer.read())
909
+
910
+ with open(filename, "wb") as f:
911
+ buffer.seek(0)
912
+ f.write(buffer.read())
913
+
914
+ # # Load with trimesh and fix manifold issues
915
+ # try:
916
+ # mesh = trimesh.load(buffer, file_type="stl")
917
+ # mesh.merge_vertices()
918
+ #
919
+ # # Try available degenerate-face removal utilities across trimesh versions
920
+ # try:
921
+ # if hasattr(mesh, "remove_degenerate_faces"):
922
+ # mesh.remove_degenerate_faces()
923
+ # elif hasattr(trimesh.repair, "remove_degenerate_faces"):
924
+ # trimesh.repair.remove_degenerate_faces(mesh)
925
+ # elif hasattr(trimesh.repair, "remove_degenerate_triangles"):
926
+ # trimesh.repair.remove_degenerate_triangles(mesh)
927
+ # except Exception as repair_err:
928
+ # print(f"Warning: Skipped degenerate-face removal for {filename}: {repair_err}")
929
+ #
930
+ # # Remove duplicate faces if the method exists
931
+ # try:
932
+ # if hasattr(mesh, "remove_duplicate_faces"):
933
+ # mesh.remove_duplicate_faces()
934
+ # elif hasattr(trimesh.repair, "remove_duplicate_faces"):
935
+ # trimesh.repair.remove_duplicate_faces(mesh)
936
+ # except Exception as repair_err:
937
+ # print(f"Warning: Skipped duplicate-face removal for {filename}: {repair_err}")
938
+ #
939
+ # # Fill holes and fix normals when available
940
+ # try:
941
+ # if hasattr(mesh, "fill_holes"):
942
+ # mesh.fill_holes()
943
+ # except Exception as repair_err:
944
+ # print(f"Warning: Skipped hole filling for {filename}: {repair_err}")
945
+ #
946
+ # try:
947
+ # mesh.fix_normals()
948
+ # except Exception as repair_err:
949
+ # print(f"Warning: Skipped normal fixing for {filename}: {repair_err}")
950
+ #
951
+ # mesh.export(filename)
952
+ # except Exception as e:
953
+ # print(f"Warning: Error fixing manifold for {filename}: {e}")
954
+ # # Fall back to saving without manifold fix
955
+ # with open(filename, "wb") as f:
956
+ # buffer.seek(0)
957
+ # f.write(buffer.read())
@@ -5,6 +5,7 @@ import torch
5
5
  from tqdm import tqdm
6
6
  from joblib import Parallel, delayed
7
7
  import threading
8
+ import numpy as np
8
9
 
9
10
  from autoforge.Helper.OptimizerHelper import composite_image_disc
10
11
  from autoforge.Loss.LossFunctions import compute_loss
@@ -864,3 +865,101 @@ def optimise_swap_positions(
864
865
 
865
866
  outer_tbar.close()
866
867
  return best_dg
868
+
869
+
870
+ def _compute_loss_for_heightmap(
871
+ optimizer: FilamentOptimizer,
872
+ disc_global: torch.Tensor,
873
+ disc_height: torch.Tensor,
874
+ *,
875
+ custom_height_logits: torch.Tensor | None = None,
876
+ ) -> float:
877
+ """Compute discrete loss for a given discrete height map/global assignment.
878
+
879
+ If ``custom_height_logits`` is provided, they are used directly (bypassing
880
+ discretization) when compositing the image; otherwise the optimizer's current
881
+ best height logits are used.
882
+ """
883
+ logits_for_disc = disc_to_logits(disc_global, optimizer.material_colors.shape[0], big_pos=1e5)
884
+ with _gpu_lock, torch.no_grad():
885
+ out_im = optimizer.get_best_discretized_image(
886
+ custom_height_logits=custom_height_logits,
887
+ custom_global_logits=logits_for_disc,
888
+ )
889
+ return compute_loss(comp=out_im, target=optimizer.target).item()
890
+
891
+
892
+ def _median_without_outliers(window: np.ndarray, threshold: float, max_outliers: int = 2) -> tuple[float, bool, int]:
893
+ """Return replacement for center pixel if it is a spike; also report spike count.
894
+
895
+ A spike is when the center pixel is >= ``threshold`` above the window median and
896
+ the window has at most ``max_outliers`` such outliers. Returns (replacement_value,
897
+ is_spike, spike_count).
898
+ """
899
+ med = np.median(window)
900
+ outlier_mask = window - med >= threshold
901
+ spike_count = int(outlier_mask.sum())
902
+ center_is_outlier = bool(outlier_mask[1, 1])
903
+ if not center_is_outlier or spike_count == 0 or spike_count > max_outliers:
904
+ return float(window[1, 1]), False, 0
905
+ non_outliers = window[~outlier_mask]
906
+ if non_outliers.size == 0:
907
+ return float(window[1, 1]), False, 0
908
+ replacement = float(np.median(non_outliers))
909
+ return replacement, True, spike_count
910
+
911
+
912
+ def remove_height_spikes(
913
+ disc_height: torch.Tensor, threshold_layers: int = 3, max_outliers: int = 2
914
+ ) -> tuple[torch.Tensor, int]:
915
+ """Remove tall spikes in a discrete height map using a 3x3 neighborhood.
916
+
917
+ A spike occurs only when the *center* pixel of a 3x3 window is at least
918
+ ``threshold_layers`` higher than the window median and the window has at most
919
+ two such outliers. Only those center pixels are replaced with the median of
920
+ the non-outlier values. Operates in layer units (0..max_layers).
921
+
922
+ Returns:
923
+ cleaned_height: torch.Tensor matching ``disc_height`` shape/device.
924
+ spike_count: int number of pixels corrected.
925
+ """
926
+ if disc_height.ndim != 2:
927
+ raise ValueError("disc_height must be 2D [H,W]")
928
+ if threshold_layers <= 0:
929
+ return disc_height, 0
930
+ if max_outliers <= 0:
931
+ return disc_height, 0
932
+
933
+ dh_np = disc_height.detach().cpu().numpy()
934
+ H, W = dh_np.shape
935
+ if H < 3 or W < 3:
936
+ return disc_height, 0
937
+
938
+ out = dh_np.copy()
939
+ spikes = 0
940
+ tbar = tqdm(range(1, H - 1), desc="Removing height spikes", leave=False)
941
+ for y in tbar:
942
+ for x in range(1, W - 1):
943
+ window = dh_np[y - 1 : y + 2, x - 1 : x + 2]
944
+ med_all = np.median(window)
945
+ mask = window - med_all >= threshold_layers
946
+ spike_count = int(mask.sum())
947
+ if not mask[1, 1] or spike_count == 0 or spike_count > max_outliers:
948
+ continue
949
+
950
+ non_outliers = window[~mask]
951
+ if non_outliers.size == 0:
952
+ continue
953
+ med_non_outliers = float(np.median(non_outliers))
954
+ center_val = float(window[1, 1])
955
+ if center_val < med_non_outliers + threshold_layers:
956
+ continue
957
+
958
+ out[y, x] = med_non_outliers # only replace the center spike
959
+ spikes += 1
960
+
961
+ if spikes == 0:
962
+ return disc_height, 0
963
+
964
+ cleaned = torch.tensor(out, dtype=disc_height.dtype, device=disc_height.device)
965
+ return cleaned, spikes