autoforge 1.6.2__tar.gz → 1.6.4__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.6.2 → autoforge-1.6.4}/PKG-INFO +4 -2
- {autoforge-1.6.2 → autoforge-1.6.4}/README.md +1 -1
- {autoforge-1.6.2 → autoforge-1.6.4}/pyproject.toml +4 -2
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/PKG-INFO +4 -2
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/requires.txt +2 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +50 -55
- autoforge-1.6.4/src/autoforge/Loss/LossFunctions.py +109 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Modules/Optimizer.py +77 -43
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/auto_forge.py +34 -11
- autoforge-1.6.2/src/autoforge/Loss/LossFunctions.py +0 -91
- {autoforge-1.6.2 → autoforge-1.6.4}/setup.cfg +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/SOURCES.txt +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/dependency_links.txt +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/entry_points.txt +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/AutoForge.egg-info/top_level.txt +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/backend_database.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/filament_editor.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/app.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/main.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/CAdamW.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/FilamentHelper.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/OldHeightmap.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/ImageHelper.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/OptimizerHelper.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/OutputHelper.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/PruningHelper.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/filamentcolors_library.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Init_comparer/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Init_comparer/plot_results.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Loss/PerceptionLoss.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Loss/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Misc/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Misc/color_fit_all.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Modules/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/__init__.py +0 -0
- {autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoForge
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.4
|
|
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
|
|
@@ -21,6 +21,8 @@ Requires-Dist: tensorboard
|
|
|
21
21
|
Requires-Dist: pillow>=11.1.0
|
|
22
22
|
Requires-Dist: shapely>=2.0.7
|
|
23
23
|
Requires-Dist: trimesh>=4.6.4
|
|
24
|
+
Requires-Dist: torchcam>=0.4.0
|
|
25
|
+
Requires-Dist: joblib>=1.4.2
|
|
24
26
|
|
|
25
27
|
# AutoForge
|
|
26
28
|
|
|
@@ -61,7 +63,7 @@ The pruning is set to a maximum of 8 color and 20 swaps, so each image uses at m
|
|
|
61
63
|
|
|
62
64
|
## [Google Colab Notebook](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih)
|
|
63
65
|
|
|
64
|
-
If you're new to Google Colab, follow these simple steps to run
|
|
66
|
+
If you're new to Google Colab, follow these simple steps to run autoforge and get your output:
|
|
65
67
|
|
|
66
68
|
1. **Open the Notebook:**
|
|
67
69
|
Click [here](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih) to open the notebook in Google Colab.
|
|
@@ -37,7 +37,7 @@ The pruning is set to a maximum of 8 color and 20 swaps, so each image uses at m
|
|
|
37
37
|
|
|
38
38
|
## [Google Colab Notebook](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih)
|
|
39
39
|
|
|
40
|
-
If you're new to Google Colab, follow these simple steps to run
|
|
40
|
+
If you're new to Google Colab, follow these simple steps to run autoforge and get your output:
|
|
41
41
|
|
|
42
42
|
1. **Open the Notebook:**
|
|
43
43
|
Click [here](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih) to open the notebook in Google Colab.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "AutoForge"
|
|
7
|
-
version="1.6.
|
|
7
|
+
version="1.6.4"
|
|
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"
|
|
@@ -25,7 +25,9 @@ dependencies = [
|
|
|
25
25
|
"tensorboard",
|
|
26
26
|
"pillow>=11.1.0",
|
|
27
27
|
"shapely>=2.0.7",
|
|
28
|
-
"trimesh>=4.6.4"
|
|
28
|
+
"trimesh>=4.6.4",
|
|
29
|
+
"torchcam>=0.4.0",
|
|
30
|
+
"joblib>=1.4.2"
|
|
29
31
|
]
|
|
30
32
|
|
|
31
33
|
[tool.setuptools]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoForge
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.4
|
|
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
|
|
@@ -21,6 +21,8 @@ Requires-Dist: tensorboard
|
|
|
21
21
|
Requires-Dist: pillow>=11.1.0
|
|
22
22
|
Requires-Dist: shapely>=2.0.7
|
|
23
23
|
Requires-Dist: trimesh>=4.6.4
|
|
24
|
+
Requires-Dist: torchcam>=0.4.0
|
|
25
|
+
Requires-Dist: joblib>=1.4.2
|
|
24
26
|
|
|
25
27
|
# AutoForge
|
|
26
28
|
|
|
@@ -61,7 +63,7 @@ The pruning is set to a maximum of 8 color and 20 swaps, so each image uses at m
|
|
|
61
63
|
|
|
62
64
|
## [Google Colab Notebook](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih)
|
|
63
65
|
|
|
64
|
-
If you're new to Google Colab, follow these simple steps to run
|
|
66
|
+
If you're new to Google Colab, follow these simple steps to run autoforge and get your output:
|
|
65
67
|
|
|
66
68
|
1. **Open the Notebook:**
|
|
67
69
|
Click [here](https://colab.research.google.com/drive/13sWCss9-ew2xnoJl4c4SSpComSNi-1ih) to open the notebook in Google Colab.
|
{autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py
RENAMED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import concurrent.futures
|
|
2
1
|
import os
|
|
3
2
|
import random
|
|
4
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
5
3
|
|
|
6
4
|
import numpy as np
|
|
7
5
|
from PIL import Image
|
|
8
6
|
from PIL.Image import Quantize
|
|
7
|
+
from joblib import Parallel, delayed
|
|
9
8
|
from scipy.spatial.distance import cdist
|
|
10
9
|
from skimage.color import rgb2lab
|
|
11
10
|
from sklearn.cluster import MiniBatchKMeans
|
|
12
|
-
from tqdm import tqdm
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
def build_distance_matrix(labs, nodes):
|
|
@@ -240,25 +238,28 @@ def create_mapping(final_ordering, labs, all_labels):
|
|
|
240
238
|
|
|
241
239
|
def tsp_order_christofides_path(nodes, labs, bg, fg):
|
|
242
240
|
"""
|
|
243
|
-
|
|
241
|
+
Ensure that the background and foreground nodes are always in the TSP cycle.
|
|
242
|
+
nodes: list of cluster indices (ideally including bg and fg)
|
|
244
243
|
labs: Lab-space coordinates (indexed by cluster index)
|
|
245
244
|
bg, fg: background and foreground cluster indices
|
|
246
|
-
|
|
247
245
|
Returns an ordering (list of cluster indices) that contains all nodes,
|
|
248
246
|
starts with bg and ends with fg.
|
|
249
247
|
"""
|
|
248
|
+
# Guarantee that bg and fg are included in nodes.
|
|
249
|
+
nodes = list(set(nodes) | {bg, fg})
|
|
250
|
+
|
|
250
251
|
artificial = -1
|
|
251
|
-
augmented_nodes = nodes + [artificial]
|
|
252
252
|
LARGE = 1e6
|
|
253
|
+
n = len(nodes)
|
|
253
254
|
|
|
254
|
-
# Precompute the distance matrix for
|
|
255
|
+
# Precompute the distance matrix for the given nodes.
|
|
255
256
|
D = build_distance_matrix(labs, nodes)
|
|
256
|
-
n = len(nodes)
|
|
257
257
|
|
|
258
|
-
# Build an augmented
|
|
258
|
+
# Build an augmented (n+1)x(n+1) matrix.
|
|
259
259
|
aug_mat = np.zeros((n + 1, n + 1))
|
|
260
260
|
aug_mat[:n, :n] = D
|
|
261
|
-
|
|
261
|
+
|
|
262
|
+
# For the artificial node, set cost = 0 for bg/fg and LARGE for others.
|
|
262
263
|
for i, u in enumerate(nodes):
|
|
263
264
|
if u in {bg, fg}:
|
|
264
265
|
aug_mat[i, n] = 0.0
|
|
@@ -268,32 +269,33 @@ def tsp_order_christofides_path(nodes, labs, bg, fg):
|
|
|
268
269
|
aug_mat[n, i] = LARGE
|
|
269
270
|
aug_mat[n, n] = 0.0
|
|
270
271
|
|
|
271
|
-
#
|
|
272
|
+
# Create augmented nodes list.
|
|
272
273
|
aug_nodes = nodes + [artificial]
|
|
273
274
|
graph = matrix_to_graph(aug_mat, aug_nodes)
|
|
274
275
|
|
|
275
|
-
# Run Christofides on the augmented graph.
|
|
276
|
+
# Run Christofides algorithm on the augmented graph.
|
|
276
277
|
cycle = christofides_tsp(graph)
|
|
277
|
-
# Remove the artificial node.
|
|
278
|
+
# Remove the artificial node if present.
|
|
278
279
|
cycle = [node for node in cycle if node != artificial]
|
|
279
280
|
|
|
281
|
+
# Ensure bg and fg are in the cycle.
|
|
282
|
+
if bg not in cycle:
|
|
283
|
+
cycle.insert(0, bg)
|
|
284
|
+
if fg not in cycle:
|
|
285
|
+
cycle.append(fg)
|
|
286
|
+
|
|
280
287
|
# Rotate the cycle so that bg is first.
|
|
281
|
-
if
|
|
288
|
+
if cycle[0] != bg:
|
|
282
289
|
idx = cycle.index(bg)
|
|
283
290
|
cycle = cycle[idx:] + cycle[:idx]
|
|
284
|
-
else:
|
|
285
|
-
raise ValueError("Background node missing from cycle")
|
|
286
291
|
|
|
287
292
|
# Force fg to be the last element.
|
|
288
|
-
if fg not in cycle:
|
|
289
|
-
raise ValueError("Foreground node missing from cycle")
|
|
290
293
|
if cycle[-1] != fg:
|
|
291
294
|
cycle.remove(fg)
|
|
292
295
|
cycle.append(fg)
|
|
293
296
|
|
|
294
|
-
#
|
|
297
|
+
# Optionally, check if reversing the internal order improves the ordering metric.
|
|
295
298
|
if len(cycle) > 2:
|
|
296
|
-
# Reverse the ordering of the nodes between bg and fg.
|
|
297
299
|
reversed_cycle = [cycle[0]] + cycle[1:-1][::-1] + [cycle[-1]]
|
|
298
300
|
if compute_ordering_metric(reversed_cycle, labs) < compute_ordering_metric(
|
|
299
301
|
cycle, labs
|
|
@@ -449,14 +451,7 @@ def init_height_map(
|
|
|
449
451
|
nodes = unique_clusters
|
|
450
452
|
|
|
451
453
|
# Get the ordering via TSP ordering function.
|
|
452
|
-
|
|
453
|
-
try:
|
|
454
|
-
final_ordering = tsp_order_christofides_path(nodes, labs, bg_cluster, fg_cluster)
|
|
455
|
-
break
|
|
456
|
-
except Exception:
|
|
457
|
-
print(f"Failed to find ordering, retrying {i+1}/10")
|
|
458
|
-
if i == 49:
|
|
459
|
-
raise ValueError("Failed to find ordering after 50 retries")
|
|
454
|
+
final_ordering = tsp_order_christofides_path(nodes, labs, bg_cluster, fg_cluster)
|
|
460
455
|
|
|
461
456
|
# Optionally prune out outliers.
|
|
462
457
|
final_ordering = prune_ordering(
|
|
@@ -474,7 +469,7 @@ def init_height_map(
|
|
|
474
469
|
def run_init_threads(
|
|
475
470
|
target,
|
|
476
471
|
max_layers,
|
|
477
|
-
h, # unused
|
|
472
|
+
h, # unused but preserved for API compatibility
|
|
478
473
|
background_tuple,
|
|
479
474
|
eps=1e-6,
|
|
480
475
|
random_seed=None,
|
|
@@ -485,33 +480,33 @@ def run_init_threads(
|
|
|
485
480
|
):
|
|
486
481
|
if random_seed is None:
|
|
487
482
|
random_seed = np.random.randint(1e6)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
init_method=init_method,
|
|
501
|
-
cluster_layers=cluster_layers,
|
|
502
|
-
lab_space=lab_space,
|
|
503
|
-
)
|
|
504
|
-
)
|
|
505
|
-
results = [
|
|
506
|
-
f.result()
|
|
507
|
-
for f in tqdm(
|
|
508
|
-
concurrent.futures.as_completed(futures),
|
|
509
|
-
desc="Running Height Map Initialization Threads",
|
|
510
|
-
total=num_threads,
|
|
483
|
+
|
|
484
|
+
tasks = [
|
|
485
|
+
delayed(init_height_map)(
|
|
486
|
+
target,
|
|
487
|
+
max_layers,
|
|
488
|
+
h,
|
|
489
|
+
background_tuple,
|
|
490
|
+
eps,
|
|
491
|
+
random_seed + i,
|
|
492
|
+
init_method=init_method,
|
|
493
|
+
cluster_layers=cluster_layers,
|
|
494
|
+
lab_space=lab_space,
|
|
511
495
|
)
|
|
496
|
+
for i in range(num_threads)
|
|
512
497
|
]
|
|
513
|
-
# print(f"metric values: {[r[1] for r in results]}")
|
|
514
|
-
# get lowest ordering metric
|
|
515
|
-
best_result = min(results, key=lambda x: x[1])
|
|
516
498
|
|
|
499
|
+
# Execute tasks in parallel; adjust n_jobs to match your available cores
|
|
500
|
+
results = Parallel(n_jobs=os.cpu_count(), verbose=10)(tasks)
|
|
501
|
+
|
|
502
|
+
metrics = [r[1] for r in results]
|
|
503
|
+
mean_metric = np.mean(metrics)
|
|
504
|
+
std_metric = np.std(metrics)
|
|
505
|
+
min_metric = np.min(metrics)
|
|
506
|
+
max_metric = np.max(metrics)
|
|
507
|
+
print(
|
|
508
|
+
f"mean: {mean_metric}, std: {std_metric}, min: {min_metric}, max: {max_metric}"
|
|
509
|
+
)
|
|
510
|
+
print(f"Choosing best ordering with metric: {min_metric}")
|
|
511
|
+
best_result = min(results, key=lambda x: x[1])
|
|
517
512
|
return best_result[0]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn.functional as F
|
|
3
|
+
|
|
4
|
+
from autoforge.Helper.ImageHelper import srgb_to_lab
|
|
5
|
+
from autoforge.Helper.OptimizerHelper import composite_image_cont
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def loss_fn(
|
|
9
|
+
params: dict,
|
|
10
|
+
target: torch.Tensor,
|
|
11
|
+
tau_height: float,
|
|
12
|
+
tau_global: float,
|
|
13
|
+
h: float,
|
|
14
|
+
max_layers: int,
|
|
15
|
+
material_colors: torch.Tensor,
|
|
16
|
+
material_TDs: torch.Tensor,
|
|
17
|
+
background: torch.Tensor,
|
|
18
|
+
add_penalty_loss: float = 0.0,
|
|
19
|
+
focus_map: torch.Tensor = None,
|
|
20
|
+
focus_strength: float = 10.0,
|
|
21
|
+
) -> torch.Tensor:
|
|
22
|
+
"""
|
|
23
|
+
Full forward pass for continuous assignment:
|
|
24
|
+
composite, then compute unified loss on (global_logits).
|
|
25
|
+
"""
|
|
26
|
+
comp = composite_image_cont(
|
|
27
|
+
params["pixel_height_logits"],
|
|
28
|
+
params["global_logits"],
|
|
29
|
+
tau_height,
|
|
30
|
+
tau_global,
|
|
31
|
+
h,
|
|
32
|
+
max_layers,
|
|
33
|
+
material_colors,
|
|
34
|
+
material_TDs,
|
|
35
|
+
background,
|
|
36
|
+
)
|
|
37
|
+
return compute_loss(
|
|
38
|
+
comp=comp,
|
|
39
|
+
target=target,
|
|
40
|
+
pixel_height_logits=params["pixel_height_logits"],
|
|
41
|
+
tau_height=tau_height,
|
|
42
|
+
add_penalty_loss=add_penalty_loss,
|
|
43
|
+
focus_map=focus_map,
|
|
44
|
+
focus_strength=focus_strength,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def compute_loss(
|
|
49
|
+
comp: torch.Tensor,
|
|
50
|
+
target: torch.Tensor,
|
|
51
|
+
pixel_height_logits: torch.Tensor = None,
|
|
52
|
+
tau_height: float = 1.0,
|
|
53
|
+
add_penalty_loss: float = 0.0,
|
|
54
|
+
focus_map: torch.Tensor = None,
|
|
55
|
+
focus_strength: float = 10.0,
|
|
56
|
+
) -> torch.Tensor:
|
|
57
|
+
comp_lab = srgb_to_lab(comp)
|
|
58
|
+
target_lab = srgb_to_lab(target)
|
|
59
|
+
loss1 = F.huber_loss(comp_lab, target_lab, delta=0.1)
|
|
60
|
+
|
|
61
|
+
return loss1
|
|
62
|
+
|
|
63
|
+
# if focus_map is not None and focus_strength > 0.0:
|
|
64
|
+
# # Expand focus_map to [H, W, 1] to match the color channels.
|
|
65
|
+
# focus_map_exp = focus_map.unsqueeze(-1)
|
|
66
|
+
# base_loss = F.huber_loss(comp_mse, target_mse, reduction="none")
|
|
67
|
+
# # we need to sum up to a maximum of one, so normalize based on strength
|
|
68
|
+
# min_strength = 1 / focus_strength
|
|
69
|
+
# max_strength = 1 - min_strength
|
|
70
|
+
# # focus map is normalized between 0 and 1
|
|
71
|
+
# normalized_focus = min_strength + focus_map_exp * max_strength
|
|
72
|
+
# weighted_loss = base_loss * normalized_focus
|
|
73
|
+
#
|
|
74
|
+
# mse_loss = weighted_loss.mean()
|
|
75
|
+
# else:
|
|
76
|
+
#
|
|
77
|
+
|
|
78
|
+
# loss_color = global_color_loss(comp_lab, target_lab)
|
|
79
|
+
|
|
80
|
+
# if pixel_height_logits is not None:
|
|
81
|
+
# # Existing neighbor-based smoothness loss:
|
|
82
|
+
# target_gray = target.mean(dim=2) # shape becomes [H, W]
|
|
83
|
+
# weight_x = torch.exp(-torch.abs(target_gray[:, 1:] - target_gray[:, :-1]))
|
|
84
|
+
# weight_y = torch.exp(-torch.abs(target_gray[1:, :] - target_gray[:-1, :]))
|
|
85
|
+
# weight_x = torch.clamp(weight_x, 0.5, 1.0)
|
|
86
|
+
# weight_y = torch.clamp(weight_y, 0.5, 1.0)
|
|
87
|
+
# dx = torch.abs(pixel_height_logits[:, 1:] - pixel_height_logits[:, :-1])
|
|
88
|
+
# dy = torch.abs(pixel_height_logits[1:, :] - pixel_height_logits[:-1, :])
|
|
89
|
+
# loss_dx = torch.mean(F.huber_loss(dx * weight_x, torch.zeros_like(dx)))
|
|
90
|
+
# loss_dy = torch.mean(F.huber_loss(dy * weight_y, torch.zeros_like(dy)))
|
|
91
|
+
# smoothness_loss = (loss_dx + loss_dy) * add_penalty_loss
|
|
92
|
+
#
|
|
93
|
+
# # Additional patch-based smoothness loss (using a 3x3 Laplacian):
|
|
94
|
+
# laplacian_kernel = (
|
|
95
|
+
# torch.tensor(
|
|
96
|
+
# [[0, 1, 0], [1, -4, 1], [0, 1, 0]],
|
|
97
|
+
# dtype=pixel_height_logits.dtype,
|
|
98
|
+
# device=pixel_height_logits.device,
|
|
99
|
+
# )
|
|
100
|
+
# .unsqueeze(0)
|
|
101
|
+
# .unsqueeze(0)
|
|
102
|
+
# )
|
|
103
|
+
# height_map = pixel_height_logits.unsqueeze(0).unsqueeze(0)
|
|
104
|
+
# laplacian_output = F.conv2d(height_map, laplacian_kernel, padding=1)
|
|
105
|
+
# patch_smooth_loss = F.huber_loss(
|
|
106
|
+
# laplacian_output, torch.zeros_like(laplacian_output)
|
|
107
|
+
# )
|
|
108
|
+
# total_loss = mse_loss + smoothness_loss + add_penalty_loss * patch_smooth_loss
|
|
109
|
+
# else:
|
|
@@ -5,6 +5,12 @@ import matplotlib.pyplot as plt
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import torch
|
|
7
7
|
from torch.utils.tensorboard import SummaryWriter
|
|
8
|
+
from torchcam.methods import SmoothGradCAMpp
|
|
9
|
+
from torchcam.utils import overlay_mask
|
|
10
|
+
from torchvision import models
|
|
11
|
+
from torchvision.models import ResNet18_Weights
|
|
12
|
+
from torchvision.transforms._functional_tensor import resize, normalize
|
|
13
|
+
from torchvision.transforms.functional import to_pil_image
|
|
8
14
|
from tqdm import tqdm
|
|
9
15
|
|
|
10
16
|
from autoforge.Helper.CAdamW import CAdamW
|
|
@@ -95,7 +101,9 @@ class FilamentOptimizer:
|
|
|
95
101
|
|
|
96
102
|
# Tau schedule
|
|
97
103
|
self.num_steps_done = 0
|
|
98
|
-
self.warmup_steps = min(
|
|
104
|
+
self.warmup_steps = min(
|
|
105
|
+
args.iterations - 1, args.warmup_fraction * args.iterations
|
|
106
|
+
)
|
|
99
107
|
self.decay_rate = (self.init_tau - self.final_tau) / (
|
|
100
108
|
args.iterations - self.warmup_steps
|
|
101
109
|
)
|
|
@@ -106,6 +114,49 @@ class FilamentOptimizer:
|
|
|
106
114
|
lr=self.learning_rate,
|
|
107
115
|
)
|
|
108
116
|
|
|
117
|
+
# Setup the focus map component
|
|
118
|
+
# Load a state-of-the-art ResNet50 model with pretrained weights.
|
|
119
|
+
cam_model = models.resnet18(weights=ResNet18_Weights.DEFAULT).to(
|
|
120
|
+
self.target.device
|
|
121
|
+
)
|
|
122
|
+
cam_model.eval()
|
|
123
|
+
# Use ScoreCAM from torchcam on the 'layer4' block.
|
|
124
|
+
cam_extractor = SmoothGradCAMpp(cam_model, target_layer="layer4")
|
|
125
|
+
# Preprocess the target image for ResNet50.
|
|
126
|
+
target_preprocessed = normalize(
|
|
127
|
+
resize(self.target.permute(2, 0, 1), (224, 224)) / 255.0,
|
|
128
|
+
[0.485, 0.456, 0.406],
|
|
129
|
+
[0.229, 0.224, 0.225],
|
|
130
|
+
)
|
|
131
|
+
focus_maps = []
|
|
132
|
+
for _ in tqdm(range(1), desc="Creating loss function attention map..."):
|
|
133
|
+
out = cam_model(target_preprocessed.unsqueeze(0))
|
|
134
|
+
# ScoreCAM returns a dict with keys corresponding to layers.
|
|
135
|
+
focus_map = cam_extractor(out.squeeze(0).argmax().item(), out)[0]
|
|
136
|
+
# Normalize the focus map to [0, 1]
|
|
137
|
+
focus_map = (focus_map - focus_map.min()) / (
|
|
138
|
+
focus_map.max() - focus_map.min() + 1e-8
|
|
139
|
+
)
|
|
140
|
+
focus_maps.append(focus_map)
|
|
141
|
+
# get maximum over all maps
|
|
142
|
+
self.focus_map = torch.stack(focus_maps)
|
|
143
|
+
self.focus_map = self.focus_map.max(dim=0)[0]
|
|
144
|
+
# normalize again
|
|
145
|
+
self.focus_map = (self.focus_map - self.focus_map.min()) / (
|
|
146
|
+
self.focus_map.max() - self.focus_map.min() + 1e-8
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# resize back to target size, current dim is [1,h,w]
|
|
150
|
+
self.focus_map = (
|
|
151
|
+
torch.nn.functional.interpolate(
|
|
152
|
+
self.focus_map.unsqueeze(0),
|
|
153
|
+
(self.H, self.W),
|
|
154
|
+
mode="bilinear",
|
|
155
|
+
)
|
|
156
|
+
.squeeze(0)
|
|
157
|
+
.squeeze(0)
|
|
158
|
+
)
|
|
159
|
+
|
|
109
160
|
# Setup best discrete solution tracking
|
|
110
161
|
self.best_discrete_loss = float("inf")
|
|
111
162
|
self.best_params = None
|
|
@@ -137,17 +188,24 @@ class FilamentOptimizer:
|
|
|
137
188
|
self.depth_map_ax = self.ax[1, 0].imshow(
|
|
138
189
|
np.zeros((self.H, self.W), dtype=np.uint8), cmap="viridis"
|
|
139
190
|
)
|
|
140
|
-
self.ax[1, 0].set_title("Current
|
|
191
|
+
self.ax[1, 0].set_title("Current Height Map")
|
|
141
192
|
|
|
142
193
|
self.diff_depth_map_ax = self.ax[1, 1].imshow(
|
|
143
194
|
np.zeros((self.H, self.W), dtype=np.uint8), cmap="viridis"
|
|
144
195
|
)
|
|
145
|
-
self.ax[1, 1].set_title("
|
|
196
|
+
self.ax[1, 1].set_title("Height Map Changes")
|
|
146
197
|
|
|
147
|
-
|
|
148
|
-
|
|
198
|
+
target_vis = self.target.cpu().detach().numpy()
|
|
199
|
+
focus_vis = self.focus_map.cpu().detach().numpy()
|
|
200
|
+
|
|
201
|
+
overlay = overlay_mask(
|
|
202
|
+
to_pil_image(target_vis),
|
|
203
|
+
to_pil_image(focus_vis, mode="F"),
|
|
204
|
+
alpha=0.5,
|
|
149
205
|
)
|
|
150
|
-
|
|
206
|
+
|
|
207
|
+
self.focus_map_ax = self.ax[1, 2].imshow(overlay)
|
|
208
|
+
self.ax[1, 2].set_title("Image Focus Map (currently unused)")
|
|
151
209
|
|
|
152
210
|
# Compute and store the initial height map for later difference computation.
|
|
153
211
|
with torch.no_grad():
|
|
@@ -188,17 +246,8 @@ class FilamentOptimizer:
|
|
|
188
246
|
|
|
189
247
|
tau_height, tau_global = self._get_tau()
|
|
190
248
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
else:
|
|
194
|
-
penalty_loss = min(
|
|
195
|
-
1.0,
|
|
196
|
-
(self.num_steps_done - self.args.iterations // 10)
|
|
197
|
-
/ (self.args.iterations // 5),
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
#start_fraction = self.args.height_logits_learning_start_fraction
|
|
201
|
-
#full_fraction = self.args.height_logits_learning_full_fraction
|
|
249
|
+
# start_fraction = self.args.height_logits_learning_start_fraction
|
|
250
|
+
# full_fraction = self.args.height_logits_learning_full_fraction
|
|
202
251
|
|
|
203
252
|
# total_iters = self.args.iterations
|
|
204
253
|
# current_step = self.num_steps_done
|
|
@@ -219,18 +268,17 @@ class FilamentOptimizer:
|
|
|
219
268
|
material_colors=self.material_colors,
|
|
220
269
|
material_TDs=self.material_TDs,
|
|
221
270
|
background=self.background,
|
|
222
|
-
|
|
223
|
-
|
|
271
|
+
add_penalty_loss=10.0,
|
|
272
|
+
focus_map=self.focus_map,
|
|
273
|
+
focus_strength=0.0,
|
|
224
274
|
)
|
|
225
275
|
|
|
226
276
|
loss.backward()
|
|
227
277
|
|
|
228
|
-
# We scale the gradients for the height logits by a factor
|
|
229
|
-
# and allow stronger and stronger updates as we go along.
|
|
278
|
+
# We scale the gradients for the height logits by a factor to only allow updates for very strong (wrong layer/color) gradients.
|
|
230
279
|
if self.params["pixel_height_logits"].grad is not None:
|
|
231
280
|
self.params["pixel_height_logits"].grad.mul_(1e-8)
|
|
232
281
|
|
|
233
|
-
|
|
234
282
|
self.optimizer.step()
|
|
235
283
|
|
|
236
284
|
self.num_steps_done += 1
|
|
@@ -349,24 +397,6 @@ class FilamentOptimizer:
|
|
|
349
397
|
)
|
|
350
398
|
self.best_comp_ax.set_data(best_comp_np)
|
|
351
399
|
|
|
352
|
-
with torch.no_grad():
|
|
353
|
-
height_map = (self.max_layers * self.h) * torch.sigmoid(
|
|
354
|
-
self.best_params["pixel_height_logits"]
|
|
355
|
-
)
|
|
356
|
-
height_map = height_map.cpu().detach().numpy()
|
|
357
|
-
|
|
358
|
-
# Normalize safely, checking for a constant image.
|
|
359
|
-
if np.allclose(height_map.max(), height_map.min()):
|
|
360
|
-
height_map_norm = np.zeros_like(height_map)
|
|
361
|
-
else:
|
|
362
|
-
height_map_norm = (height_map - height_map.min()) / (
|
|
363
|
-
height_map.max() - height_map.min()
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
height_map_uint8 = (height_map_norm * 255).astype(np.uint8)
|
|
367
|
-
self.disc_depth_map_ax.set_data(height_map_uint8)
|
|
368
|
-
self.disc_depth_map_ax.set_clim(0, 255)
|
|
369
|
-
|
|
370
400
|
# Update the depth map correctly.
|
|
371
401
|
with torch.no_grad():
|
|
372
402
|
height_map = (self.max_layers * self.h) * torch.sigmoid(
|
|
@@ -489,6 +519,7 @@ class FilamentOptimizer:
|
|
|
489
519
|
max_swaps_allowed: int,
|
|
490
520
|
min_layers_allowed: int,
|
|
491
521
|
max_layers_allowed: int,
|
|
522
|
+
search_seed: bool = True,
|
|
492
523
|
):
|
|
493
524
|
# Now run pruning
|
|
494
525
|
from autoforge.Helper.PruningHelper import (
|
|
@@ -509,19 +540,22 @@ class FilamentOptimizer:
|
|
|
509
540
|
self.final_tau,
|
|
510
541
|
None,
|
|
511
542
|
)
|
|
512
|
-
|
|
543
|
+
if search_seed:
|
|
544
|
+
self.rng_seed_search(self.best_discrete_loss, 100, autoset_seed=True)
|
|
513
545
|
prune_num_swaps(
|
|
514
546
|
self,
|
|
515
547
|
max_swaps_allowed,
|
|
516
548
|
self.final_tau,
|
|
517
549
|
None,
|
|
518
550
|
)
|
|
519
|
-
|
|
551
|
+
if search_seed:
|
|
552
|
+
self.rng_seed_search(self.best_discrete_loss, 100, autoset_seed=True)
|
|
520
553
|
|
|
521
554
|
# prune_fireflies(self)
|
|
522
555
|
|
|
523
556
|
prune_redundant_layers(self, None, min_layers_allowed, max_layers_allowed)
|
|
524
|
-
|
|
557
|
+
if search_seed:
|
|
558
|
+
self.rng_seed_search(self.best_discrete_loss, 100, autoset_seed=True)
|
|
525
559
|
|
|
526
560
|
def _maybe_update_best_discrete(self):
|
|
527
561
|
"""
|
|
@@ -133,6 +133,12 @@ def main():
|
|
|
133
133
|
default=True,
|
|
134
134
|
help="Perform pruning after optimization",
|
|
135
135
|
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--pruning_rounds",
|
|
138
|
+
type=int,
|
|
139
|
+
default=2,
|
|
140
|
+
help="Number of pruning rounds to perform",
|
|
141
|
+
)
|
|
136
142
|
parser.add_argument(
|
|
137
143
|
"--pruning_max_colors",
|
|
138
144
|
type=int,
|
|
@@ -211,6 +217,18 @@ def main():
|
|
|
211
217
|
parser.add_argument(
|
|
212
218
|
"--tensorboard", action="store_true", help="Enable TensorBoard logging"
|
|
213
219
|
)
|
|
220
|
+
parser.add_argument(
|
|
221
|
+
"--num_init_rounds",
|
|
222
|
+
type=int,
|
|
223
|
+
default=64,
|
|
224
|
+
help="Number of rounds to choose the starting height map from.",
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--num_init_cluster_layers",
|
|
228
|
+
type=int,
|
|
229
|
+
default=18,
|
|
230
|
+
help="Number of layers to cluster the image into.",
|
|
231
|
+
)
|
|
214
232
|
|
|
215
233
|
args = parser.parse_args()
|
|
216
234
|
|
|
@@ -302,9 +320,9 @@ def main():
|
|
|
302
320
|
args.layer_height,
|
|
303
321
|
bgr_tuple,
|
|
304
322
|
random_seed=random_seed,
|
|
305
|
-
num_threads=
|
|
323
|
+
num_threads=args.num_init_rounds,
|
|
306
324
|
init_method="kmeans",
|
|
307
|
-
cluster_layers=
|
|
325
|
+
cluster_layers=args.num_init_cluster_layers,
|
|
308
326
|
lab_space=True,
|
|
309
327
|
)
|
|
310
328
|
|
|
@@ -360,15 +378,20 @@ def main():
|
|
|
360
378
|
)
|
|
361
379
|
|
|
362
380
|
if args.perform_pruning:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
381
|
+
for i in range(args.pruning_rounds):
|
|
382
|
+
print(f"Pruning round {i + 1}/{args.pruning_rounds}:")
|
|
383
|
+
optimizer.prune(
|
|
384
|
+
max_colors_allowed=args.pruning_max_colors,
|
|
385
|
+
max_swaps_allowed=args.pruning_max_swaps,
|
|
386
|
+
min_layers_allowed=args.min_layers,
|
|
387
|
+
max_layers_allowed=args.pruning_max_layer,
|
|
388
|
+
search_seed=True if i == 0 else False,
|
|
389
|
+
)
|
|
390
|
+
optimizer.log_to_tensorboard(
|
|
391
|
+
interval=1,
|
|
392
|
+
namespace="post_opt",
|
|
393
|
+
step=(post_opt_step := post_opt_step + 1),
|
|
394
|
+
)
|
|
372
395
|
|
|
373
396
|
disc_global, disc_height_image = optimizer.get_discretized_solution(best=True)
|
|
374
397
|
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import torch
|
|
2
|
-
import torch.nn.functional as F
|
|
3
|
-
|
|
4
|
-
from autoforge.Helper.ImageHelper import srgb_to_lab
|
|
5
|
-
from autoforge.Helper.OptimizerHelper import composite_image_cont
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def loss_fn(
|
|
9
|
-
params: dict,
|
|
10
|
-
target: torch.Tensor,
|
|
11
|
-
tau_height: float,
|
|
12
|
-
tau_global: float,
|
|
13
|
-
h: float,
|
|
14
|
-
max_layers: int,
|
|
15
|
-
material_colors: torch.Tensor,
|
|
16
|
-
material_TDs: torch.Tensor,
|
|
17
|
-
background: torch.Tensor,
|
|
18
|
-
perception_loss_module: torch.nn.Module,
|
|
19
|
-
add_penalty_loss: float = 0.0,
|
|
20
|
-
) -> torch.Tensor:
|
|
21
|
-
"""
|
|
22
|
-
Full forward pass for continuous assignment:
|
|
23
|
-
composite, then compute unified loss on (global_logits).
|
|
24
|
-
"""
|
|
25
|
-
comp = composite_image_cont(
|
|
26
|
-
params["pixel_height_logits"],
|
|
27
|
-
params["global_logits"],
|
|
28
|
-
tau_height,
|
|
29
|
-
tau_global,
|
|
30
|
-
h,
|
|
31
|
-
max_layers,
|
|
32
|
-
material_colors,
|
|
33
|
-
material_TDs,
|
|
34
|
-
background,
|
|
35
|
-
)
|
|
36
|
-
return compute_loss(
|
|
37
|
-
comp=comp,
|
|
38
|
-
target=target,
|
|
39
|
-
pixel_height_logits=params["pixel_height_logits"],
|
|
40
|
-
tau_height=tau_height,
|
|
41
|
-
add_penalty_loss=add_penalty_loss,
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def compute_loss(
|
|
46
|
-
comp: torch.Tensor,
|
|
47
|
-
target: torch.Tensor,
|
|
48
|
-
pixel_height_logits: torch.Tensor = None,
|
|
49
|
-
tau_height: float = 1.0,
|
|
50
|
-
add_penalty_loss: float = 0.0,
|
|
51
|
-
) -> torch.Tensor:
|
|
52
|
-
"""
|
|
53
|
-
Combined MSE + Perceptual + penalty losses with patch-based smoothness.
|
|
54
|
-
"""
|
|
55
|
-
# MSE Loss
|
|
56
|
-
comp_mse = srgb_to_lab(comp)
|
|
57
|
-
target_mse = srgb_to_lab(target)
|
|
58
|
-
mse_loss = F.huber_loss(comp_mse, target_mse)
|
|
59
|
-
|
|
60
|
-
if pixel_height_logits is not None:
|
|
61
|
-
# Existing neighbor-based smoothness loss:
|
|
62
|
-
target_gray = target.mean(dim=2) # shape becomes [H, W]
|
|
63
|
-
weight_x = torch.exp(-torch.abs(target_gray[:, 1:] - target_gray[:, :-1]))
|
|
64
|
-
weight_y = torch.exp(-torch.abs(target_gray[1:, :] - target_gray[:-1, :]))
|
|
65
|
-
weight_x = torch.clamp(weight_x, 0.5, 1.0)
|
|
66
|
-
weight_y = torch.clamp(weight_y, 0.5, 1.0)
|
|
67
|
-
dx = torch.abs(pixel_height_logits[:, 1:] - pixel_height_logits[:, :-1])
|
|
68
|
-
dy = torch.abs(pixel_height_logits[1:, :] - pixel_height_logits[:-1, :])
|
|
69
|
-
loss_dx = torch.mean(F.huber_loss(dx * weight_x, torch.zeros_like(dx)))
|
|
70
|
-
loss_dy = torch.mean(F.huber_loss(dy * weight_y, torch.zeros_like(dy)))
|
|
71
|
-
smoothness_loss = (loss_dx + loss_dy) * (10 * add_penalty_loss)
|
|
72
|
-
|
|
73
|
-
# Additional patch-based smoothness loss (using a 3x3 Laplacian):
|
|
74
|
-
laplacian_kernel = (
|
|
75
|
-
torch.tensor(
|
|
76
|
-
[[0, 1, 0], [1, -4, 1], [0, 1, 0]],
|
|
77
|
-
dtype=pixel_height_logits.dtype,
|
|
78
|
-
device=pixel_height_logits.device,
|
|
79
|
-
)
|
|
80
|
-
.unsqueeze(0)
|
|
81
|
-
.unsqueeze(0)
|
|
82
|
-
)
|
|
83
|
-
height_map = pixel_height_logits.unsqueeze(0).unsqueeze(0)
|
|
84
|
-
laplacian_output = F.conv2d(height_map, laplacian_kernel, padding=1)
|
|
85
|
-
patch_smooth_loss = (
|
|
86
|
-
F.huber_loss(laplacian_output, torch.zeros_like(laplacian_output)) * 10
|
|
87
|
-
)
|
|
88
|
-
total_loss = mse_loss + smoothness_loss + add_penalty_loss * patch_smooth_loss
|
|
89
|
-
else:
|
|
90
|
-
total_loss = mse_loss
|
|
91
|
-
return total_loss
|
|
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
|
{autoforge-1.6.2 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py
RENAMED
|
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
|