autoforge 1.6.3__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.3 → autoforge-1.6.4}/PKG-INFO +3 -1
- {autoforge-1.6.3 → autoforge-1.6.4}/pyproject.toml +4 -2
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/PKG-INFO +3 -1
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/requires.txt +2 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +28 -30
- autoforge-1.6.4/src/autoforge/Loss/LossFunctions.py +109 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Modules/Optimizer.py +77 -43
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/auto_forge.py +34 -11
- autoforge-1.6.3/src/autoforge/Loss/LossFunctions.py +0 -91
- {autoforge-1.6.3 → autoforge-1.6.4}/README.md +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/setup.cfg +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/SOURCES.txt +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/dependency_links.txt +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/entry_points.txt +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/AutoForge.egg-info/top_level.txt +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/backend_database.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/Views/filament_editor.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/app.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Gui/browser_gui/main.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/CAdamW.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/FilamentHelper.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/OldHeightmap.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/ImageHelper.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/OptimizerHelper.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/OutputHelper.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/PruningHelper.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Helper/filamentcolors_library.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Init_comparer/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Init_comparer/plot_results.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Loss/PerceptionLoss.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Loss/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Misc/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Misc/color_fit_all.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/Modules/__init__.py +0 -0
- {autoforge-1.6.3 → autoforge-1.6.4}/src/autoforge/__init__.py +0 -0
- {autoforge-1.6.3 → 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
|
|
|
@@ -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
|
|
{autoforge-1.6.3 → 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):
|
|
@@ -471,7 +469,7 @@ def init_height_map(
|
|
|
471
469
|
def run_init_threads(
|
|
472
470
|
target,
|
|
473
471
|
max_layers,
|
|
474
|
-
h, # unused
|
|
472
|
+
h, # unused but preserved for API compatibility
|
|
475
473
|
background_tuple,
|
|
476
474
|
eps=1e-6,
|
|
477
475
|
random_seed=None,
|
|
@@ -482,33 +480,33 @@ def run_init_threads(
|
|
|
482
480
|
):
|
|
483
481
|
if random_seed is None:
|
|
484
482
|
random_seed = np.random.randint(1e6)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
init_method=init_method,
|
|
498
|
-
cluster_layers=cluster_layers,
|
|
499
|
-
lab_space=lab_space,
|
|
500
|
-
)
|
|
501
|
-
)
|
|
502
|
-
results = [
|
|
503
|
-
f.result()
|
|
504
|
-
for f in tqdm(
|
|
505
|
-
concurrent.futures.as_completed(futures),
|
|
506
|
-
desc="Running Height Map Initialization Threads",
|
|
507
|
-
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,
|
|
508
495
|
)
|
|
496
|
+
for i in range(num_threads)
|
|
509
497
|
]
|
|
510
|
-
# print(f"metric values: {[r[1] for r in results]}")
|
|
511
|
-
# get lowest ordering metric
|
|
512
|
-
best_result = min(results, key=lambda x: x[1])
|
|
513
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])
|
|
514
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
|
|
File without changes
|
{autoforge-1.6.3 → 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
|