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.
- {autoforge-1.9.2.2 → autoforge-1.9.3}/PKG-INFO +4 -3
- {autoforge-1.9.2.2 → autoforge-1.9.3}/README.md +1 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/pyproject.toml +3 -3
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/PKG-INFO +4 -3
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/SOURCES.txt +2 -1
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/requires.txt +1 -1
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OutputHelper.py +49 -16
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/PruningHelper.py +99 -0
- autoforge-1.9.3/src/autoforge/Misc/color_fit_all.py +448 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Modules/Optimizer.py +99 -2
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/auto_forge.py +13 -0
- autoforge-1.9.3/tests/test_remove_height_spikes.py +84 -0
- autoforge-1.9.2.2/src/autoforge/Misc/color_fit_all.py +0 -1892
- {autoforge-1.9.2.2 → autoforge-1.9.3}/setup.cfg +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/dependency_links.txt +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/entry_points.txt +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/AutoForge.egg-info/top_level.txt +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/AmpUtils.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/CAdamW.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/FilamentHelper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/ImageHelper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OptimizerHelper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/OtherHelper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Helper/filamentcolors_library.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Init_comparer/plot_results.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/LossFunctions.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/PerceptionLoss.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Loss/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Misc/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/Modules/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/__init__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/__main__.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/src/autoforge/priority_mask_editor.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_amp_autocast_probe.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_cadamw.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_christofides_height_map.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_christofides_heightmap.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_depth_adjusted_height_map.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_depth_estimate_height_map.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_end_to_end_optimizer.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_filament_and_output.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_filament_helper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_flatforge.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_heightmap_init.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_image_helper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_loss_functions.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_helper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_module.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_optimizer_smoke.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_output_helper.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_output_helper_extended.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_perception_and_pruning.py +0 -0
- {autoforge-1.9.2.2 → autoforge-1.9.3}/tests/test_property_based.py +0 -0
- {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.
|
|
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.
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: torch>=2.
|
|
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.
|
|
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.
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
12
|
license = { text = "CC BY-NC-SA 4.0" }
|
|
13
13
|
dependencies = [
|
|
14
14
|
"numpy",
|
|
15
|
-
"torch>=2.
|
|
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.
|
|
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.
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: torch>=2.
|
|
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.
|
|
@@ -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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|