autoforge 1.4.2__tar.gz → 1.5.0__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.4.2 → autoforge-1.5.0}/PKG-INFO +1 -1
- {autoforge-1.4.2 → autoforge-1.5.0}/pyproject.toml +1 -1
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/PKG-INFO +1 -1
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/SOURCES.txt +1 -0
- autoforge-1.5.0/src/autoforge/Helper/CAdamW.py +113 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/OptimizerHelper.py +149 -36
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/OutputHelper.py +19 -11
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/PruningHelper.py +5 -9
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Loss/LossFunctions.py +2 -3
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Modules/Optimizer.py +33 -22
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/auto_forge.py +5 -3
- {autoforge-1.4.2 → autoforge-1.5.0}/README.md +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/setup.cfg +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/dependency_links.txt +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/entry_points.txt +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/requires.txt +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/AutoForge.egg-info/top_level.txt +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/Views/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/Views/backend_database.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/Views/filament_editor.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/app.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Gui/main.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/FilamentHelper.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/ImageHelper.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Helper/filamentcolors_library.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Loss/PerceptionLoss.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Loss/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Misc/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Misc/color_fit_all.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/Modules/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/__init__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/__main__.py +0 -0
- {autoforge-1.4.2 → autoforge-1.5.0}/src/autoforge/old_jax_files.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "AutoForge"
|
|
7
|
-
version="1.
|
|
7
|
+
version="1.5.0"
|
|
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"
|
|
@@ -16,6 +16,7 @@ src/autoforge/Gui/main.py
|
|
|
16
16
|
src/autoforge/Gui/Views/__init__.py
|
|
17
17
|
src/autoforge/Gui/Views/backend_database.py
|
|
18
18
|
src/autoforge/Gui/Views/filament_editor.py
|
|
19
|
+
src/autoforge/Helper/CAdamW.py
|
|
19
20
|
src/autoforge/Helper/FilamentHelper.py
|
|
20
21
|
src/autoforge/Helper/ImageHelper.py
|
|
21
22
|
src/autoforge/Helper/OptimizerHelper.py
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Taken from https://github.com/kyleliang919/C-Optim/blob/main/c_adamw.py
|
|
2
|
+
# copy dependencies from transformers/optimization.py
|
|
3
|
+
import math
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Callable, Iterable, Tuple
|
|
6
|
+
|
|
7
|
+
import torch
|
|
8
|
+
from torch import nn
|
|
9
|
+
from torch.optim import Optimizer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CAdamW(Optimizer):
|
|
13
|
+
"""
|
|
14
|
+
Implements Adam algorithm with weight decay fix as introduced in [Decoupled Weight Decay
|
|
15
|
+
Regularization](https://arxiv.org/abs/1711.05101).
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
params (`Iterable[nn.parameter.Parameter]`):
|
|
19
|
+
Iterable of parameters to optimize or dictionaries defining parameter groups.
|
|
20
|
+
lr (`float`, *optional*, defaults to 0.001):
|
|
21
|
+
The learning rate to use.
|
|
22
|
+
betas (`Tuple[float,float]`, *optional*, defaults to `(0.9, 0.999)`):
|
|
23
|
+
Adam's betas parameters (b1, b2).
|
|
24
|
+
eps (`float`, *optional*, defaults to 1e-06):
|
|
25
|
+
Adam's epsilon for numerical stability.
|
|
26
|
+
weight_decay (`float`, *optional*, defaults to 0.0):
|
|
27
|
+
Decoupled weight decay to apply.
|
|
28
|
+
correct_bias (`bool`, *optional*, defaults to `True`):
|
|
29
|
+
Whether or not to correct bias in Adam (for instance, in Bert TF repository they use `False`).
|
|
30
|
+
no_deprecation_warning (`bool`, *optional*, defaults to `False`):
|
|
31
|
+
A flag used to disable the deprecation warning (set to `True` to disable the warning).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
params: Iterable[nn.parameter.Parameter],
|
|
37
|
+
lr: float = 1e-3,
|
|
38
|
+
betas: Tuple[float, float] = (0.9, 0.999),
|
|
39
|
+
eps: float = 1e-6,
|
|
40
|
+
weight_decay: float = 0.0,
|
|
41
|
+
correct_bias: bool = True,
|
|
42
|
+
):
|
|
43
|
+
|
|
44
|
+
if lr < 0.0:
|
|
45
|
+
raise ValueError(f"Invalid learning rate: {lr} - should be >= 0.0")
|
|
46
|
+
if not 0.0 <= betas[0] < 1.0:
|
|
47
|
+
raise ValueError(f"Invalid beta parameter: {betas[0]} - should be in [0.0, 1.0)")
|
|
48
|
+
if not 0.0 <= betas[1] < 1.0:
|
|
49
|
+
raise ValueError(f"Invalid beta parameter: {betas[1]} - should be in [0.0, 1.0)")
|
|
50
|
+
if not 0.0 <= eps:
|
|
51
|
+
raise ValueError(f"Invalid epsilon value: {eps} - should be >= 0.0")
|
|
52
|
+
defaults = {"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay, "correct_bias": correct_bias}
|
|
53
|
+
super().__init__(params, defaults)
|
|
54
|
+
self.init_lr = lr
|
|
55
|
+
|
|
56
|
+
@torch.no_grad()
|
|
57
|
+
def step(self, closure: Callable = None):
|
|
58
|
+
"""
|
|
59
|
+
Performs a single optimization step.
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
closure (`Callable`, *optional*): A closure that reevaluates the model and returns the loss.
|
|
63
|
+
"""
|
|
64
|
+
loss = None
|
|
65
|
+
if closure is not None:
|
|
66
|
+
loss = closure()
|
|
67
|
+
|
|
68
|
+
for group in self.param_groups:
|
|
69
|
+
for i, p in enumerate(group["params"]):
|
|
70
|
+
if p.grad is None:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
grad = p.grad
|
|
74
|
+
state = self.state[p]
|
|
75
|
+
|
|
76
|
+
if "step" not in state:
|
|
77
|
+
state["step"] = 0
|
|
78
|
+
|
|
79
|
+
# State initialization
|
|
80
|
+
if "exp_avg" not in state:
|
|
81
|
+
# Exponential moving average of gradient values
|
|
82
|
+
state["exp_avg"] = torch.zeros_like(grad)
|
|
83
|
+
# Exponential moving average of squared gradient values
|
|
84
|
+
state["exp_avg_sq"] = torch.zeros_like(grad)
|
|
85
|
+
|
|
86
|
+
exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
|
|
87
|
+
beta1, beta2 = group["betas"]
|
|
88
|
+
|
|
89
|
+
state["step"] += 1
|
|
90
|
+
|
|
91
|
+
# apply weight decay
|
|
92
|
+
if group["weight_decay"] > 0.0:
|
|
93
|
+
p.add_(p, alpha=(-group["lr"] * group["weight_decay"]))
|
|
94
|
+
|
|
95
|
+
# Decay the first and second moment running average coefficient
|
|
96
|
+
# In-place operations to update the averages at the same time
|
|
97
|
+
exp_avg.mul_(beta1).add_(grad, alpha=(1.0 - beta1))
|
|
98
|
+
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
|
99
|
+
denom = exp_avg_sq.sqrt().add_(group["eps"])
|
|
100
|
+
|
|
101
|
+
step_size = group["lr"]
|
|
102
|
+
if group["correct_bias"]: # No bias correction for Bert
|
|
103
|
+
bias_correction1 = 1.0 - beta1 ** state["step"]
|
|
104
|
+
bias_correction2 = 1.0 - beta2 ** state["step"]
|
|
105
|
+
step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
|
|
106
|
+
|
|
107
|
+
# compute norm gradient
|
|
108
|
+
mask = (exp_avg * grad > 0).to(grad.dtype)
|
|
109
|
+
# mask = mask * (mask.numel() / (mask.sum() + 1)) ## original implementation, leaving it here for record
|
|
110
|
+
mask.div_(mask.mean().clamp_(min=1e-3)) # https://huggingface.co/rwightman/timm-optim-caution found this implementation is more favoarable in many cases
|
|
111
|
+
norm_grad = (exp_avg * mask) / denom
|
|
112
|
+
p.add_(norm_grad, alpha=-step_size)
|
|
113
|
+
return loss
|
|
@@ -76,7 +76,7 @@ def deterministic_gumbel_softmax(
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
@torch.jit.script
|
|
79
|
-
def
|
|
79
|
+
def composite_image_cont(
|
|
80
80
|
pixel_height_logits: torch.Tensor,
|
|
81
81
|
global_logits: torch.Tensor,
|
|
82
82
|
tau_height: float,
|
|
@@ -86,32 +86,27 @@ def composite_image(
|
|
|
86
86
|
material_colors: torch.Tensor,
|
|
87
87
|
material_TDs: torch.Tensor,
|
|
88
88
|
background: torch.Tensor,
|
|
89
|
-
mode: str = "continuous",
|
|
90
|
-
rng_seed: int = -1, # <--- new optional argument
|
|
91
89
|
) -> torch.Tensor:
|
|
92
90
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
If rng_seed >= 0 and mode=="discrete", we will fix the random seed
|
|
96
|
-
for each layer so that we get the same random Gumbel noise for reproducibility.
|
|
91
|
+
Continuous compositing over all pixels.
|
|
92
|
+
Uses Gumbel softmax with either hard or soft sampling depending on tau_global.
|
|
97
93
|
"""
|
|
98
94
|
pixel_height = (max_layers * h) * torch.sigmoid(pixel_height_logits)
|
|
99
95
|
continuous_layers = pixel_height / h
|
|
100
96
|
|
|
101
|
-
# same as before
|
|
102
97
|
adaptive_layers = adaptive_round(
|
|
103
98
|
continuous_layers, tau_height, high_tau=0.1, low_tau=0.01, temp=0.1
|
|
104
99
|
)
|
|
105
100
|
discrete_layers_temp = torch.round(continuous_layers)
|
|
106
101
|
discrete_layers = (
|
|
107
102
|
discrete_layers_temp + (adaptive_layers - discrete_layers_temp).detach()
|
|
108
|
-
)
|
|
109
|
-
discrete_layers = discrete_layers.to(torch.int32)
|
|
103
|
+
).to(torch.int32)
|
|
110
104
|
|
|
111
105
|
H, W = pixel_height.shape
|
|
112
106
|
comp = torch.zeros(H, W, 3, dtype=torch.float32, device=pixel_height.device)
|
|
113
107
|
remaining = torch.ones(H, W, dtype=torch.float32, device=pixel_height.device)
|
|
114
108
|
|
|
109
|
+
# opacity function parameters
|
|
115
110
|
o = -1.2416557e-02
|
|
116
111
|
A = 9.6407950e-01
|
|
117
112
|
k = 3.4103447e01
|
|
@@ -119,39 +114,20 @@ def composite_image(
|
|
|
119
114
|
|
|
120
115
|
for i in range(max_layers):
|
|
121
116
|
layer_idx = max_layers - 1 - i
|
|
122
|
-
p_print = (discrete_layers > layer_idx).float()
|
|
123
|
-
eff_thick = p_print * h
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
if mode == "discrete":
|
|
128
|
-
if rng_seed >= 0:
|
|
129
|
-
# Use a context manager so we don’t pollute the global seed
|
|
130
|
-
p_i = deterministic_gumbel_softmax(
|
|
131
|
-
global_logits[layer_idx],
|
|
132
|
-
tau_global,
|
|
133
|
-
hard=True,
|
|
134
|
-
rng_seed=rng_seed + layer_idx,
|
|
135
|
-
)
|
|
136
|
-
else:
|
|
137
|
-
# same as before, but will be random every call
|
|
138
|
-
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=True)
|
|
139
|
-
|
|
140
|
-
elif mode == "continuous":
|
|
141
|
-
if tau_global < 1e-3:
|
|
142
|
-
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=True)
|
|
143
|
-
else:
|
|
144
|
-
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=False)
|
|
145
|
-
else:
|
|
146
|
-
# e.g. "pruning" or any other fallback
|
|
117
|
+
p_print = (discrete_layers > layer_idx).float() # [H,W]
|
|
118
|
+
eff_thick = p_print * h # effective thickness per pixel
|
|
119
|
+
|
|
120
|
+
# continuous mode: use hard gumbel if tau_global is very low
|
|
121
|
+
if tau_global < 1e-3:
|
|
147
122
|
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=True)
|
|
123
|
+
else:
|
|
124
|
+
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=False)
|
|
148
125
|
|
|
149
126
|
color_i = torch.matmul(p_i, material_colors)
|
|
150
127
|
TD_i = torch.matmul(p_i, material_TDs)
|
|
151
128
|
TD_i = torch.clamp(TD_i, 1e-8, 1e8)
|
|
152
129
|
|
|
153
130
|
opac = o + (A * torch.log1p(k * (eff_thick / TD_i)) + b * (eff_thick / TD_i))
|
|
154
|
-
# opac = torch.where(TD_i / 10 <= h, torch.tensor(1.0), opac)
|
|
155
131
|
opac = torch.clamp(opac, 0.0, 1.0)
|
|
156
132
|
|
|
157
133
|
comp = comp + ((remaining * opac).unsqueeze(-1) * color_i)
|
|
@@ -161,6 +137,143 @@ def composite_image(
|
|
|
161
137
|
return comp * 255.0
|
|
162
138
|
|
|
163
139
|
|
|
140
|
+
@torch.jit.script
|
|
141
|
+
def composite_image_disc(
|
|
142
|
+
pixel_height_logits: torch.Tensor,
|
|
143
|
+
global_logits: torch.Tensor,
|
|
144
|
+
tau_height: float,
|
|
145
|
+
tau_global: float,
|
|
146
|
+
h: float,
|
|
147
|
+
max_layers: int,
|
|
148
|
+
material_colors: torch.Tensor,
|
|
149
|
+
material_TDs: torch.Tensor,
|
|
150
|
+
background: torch.Tensor,
|
|
151
|
+
rng_seed: int = -1,
|
|
152
|
+
) -> torch.Tensor:
|
|
153
|
+
"""
|
|
154
|
+
Discrete compositing over all pixels.
|
|
155
|
+
For each layer (traversed from top to bottom), we compute a one-hot material selection.
|
|
156
|
+
For pixels where the layer is printed (as determined by discrete_layers),
|
|
157
|
+
contiguous layers with the same material are merged (i.e. their effective thickness is accumulated)
|
|
158
|
+
and then composited using the opacity function. The accumulated segment is applied when
|
|
159
|
+
the material changes or if the layer is not printed.
|
|
160
|
+
"""
|
|
161
|
+
# Compute pixel height and determine discrete layers.
|
|
162
|
+
pixel_height = (max_layers * h) * torch.sigmoid(pixel_height_logits)
|
|
163
|
+
continuous_layers = pixel_height / h
|
|
164
|
+
|
|
165
|
+
adaptive_layers = adaptive_round(
|
|
166
|
+
continuous_layers, tau_height, high_tau=0.1, low_tau=0.01, temp=0.1
|
|
167
|
+
)
|
|
168
|
+
discrete_layers_temp = torch.round(continuous_layers)
|
|
169
|
+
discrete_layers = (
|
|
170
|
+
discrete_layers_temp + (adaptive_layers - discrete_layers_temp).detach()
|
|
171
|
+
).to(torch.int32)
|
|
172
|
+
|
|
173
|
+
H, W = pixel_height.shape
|
|
174
|
+
comp = torch.zeros(H, W, 3, dtype=torch.float32, device=pixel_height.device)
|
|
175
|
+
remaining = torch.ones(H, W, dtype=torch.float32, device=pixel_height.device)
|
|
176
|
+
|
|
177
|
+
# Current material and accumulated thickness per pixel.
|
|
178
|
+
# cur_mat is initialized to -1 meaning "no material".
|
|
179
|
+
cur_mat = -torch.ones((H, W), dtype=torch.int32, device=pixel_height.device)
|
|
180
|
+
acc_thick = torch.zeros((H, W), dtype=torch.float32, device=pixel_height.device)
|
|
181
|
+
|
|
182
|
+
# Opacity function parameters.
|
|
183
|
+
o = -1.2416557e-02
|
|
184
|
+
A = 9.6407950e-01
|
|
185
|
+
k = 3.4103447e01
|
|
186
|
+
b = -4.1554203e00
|
|
187
|
+
|
|
188
|
+
for i in range(max_layers):
|
|
189
|
+
layer_idx = max_layers - 1 - i
|
|
190
|
+
p_print = discrete_layers > layer_idx # Boolean mask [H,W]
|
|
191
|
+
eff_thick = (
|
|
192
|
+
p_print.to(torch.float32) * h
|
|
193
|
+
) # effective thickness for printed pixels
|
|
194
|
+
|
|
195
|
+
# Determine material for this layer using discrete (one-hot) gumbel softmax.
|
|
196
|
+
if rng_seed >= 0:
|
|
197
|
+
p_i = deterministic_gumbel_softmax(
|
|
198
|
+
global_logits[layer_idx], tau_global, True, rng_seed + layer_idx
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
p_i = F.gumbel_softmax(global_logits[layer_idx], tau_global, hard=True)
|
|
202
|
+
# Cast new_mat to int32 so it matches cur_mat.
|
|
203
|
+
new_mat = torch.argmax(p_i, 0).to(torch.int32)
|
|
204
|
+
|
|
205
|
+
# 1. For pixels where no current material is set, start a new segment.
|
|
206
|
+
mask_new = p_print & (cur_mat == -1)
|
|
207
|
+
if mask_new.any():
|
|
208
|
+
cur_mat[mask_new] = new_mat
|
|
209
|
+
acc_thick[mask_new] = eff_thick[mask_new]
|
|
210
|
+
|
|
211
|
+
# 2. For pixels where the current segment has the same material, accumulate thickness.
|
|
212
|
+
mask_same = p_print & (cur_mat == new_mat)
|
|
213
|
+
if mask_same.any():
|
|
214
|
+
acc_thick[mask_same] = acc_thick[mask_same] + eff_thick[mask_same]
|
|
215
|
+
|
|
216
|
+
# 3. For pixels where the current segment has a different material,
|
|
217
|
+
# composite the accumulated segment and start a new one.
|
|
218
|
+
mask_diff = p_print & (cur_mat != -1) & (cur_mat != new_mat)
|
|
219
|
+
if mask_diff.any():
|
|
220
|
+
# Convert indices to int64 for index_select.
|
|
221
|
+
indices_diff = cur_mat[mask_diff].to(torch.int64)
|
|
222
|
+
td_vals = material_TDs.index_select(0, indices_diff)
|
|
223
|
+
col_vals = material_colors.index_select(0, indices_diff)
|
|
224
|
+
opac_vals = o + (
|
|
225
|
+
A * torch.log1p(k * (acc_thick[mask_diff] / td_vals))
|
|
226
|
+
+ b * (acc_thick[mask_diff] / td_vals)
|
|
227
|
+
)
|
|
228
|
+
opac_vals = torch.clamp(opac_vals, 0.0, 1.0)
|
|
229
|
+
comp[mask_diff] = comp[mask_diff] + (
|
|
230
|
+
(remaining[mask_diff] * opac_vals).unsqueeze(-1) * col_vals
|
|
231
|
+
)
|
|
232
|
+
remaining[mask_diff] = remaining[mask_diff] * (1 - opac_vals)
|
|
233
|
+
cur_mat[mask_diff] = new_mat
|
|
234
|
+
acc_thick[mask_diff] = eff_thick[mask_diff]
|
|
235
|
+
|
|
236
|
+
# 4. For pixels where this layer is not printed but we have a pending segment,
|
|
237
|
+
# composite that segment and reset.
|
|
238
|
+
mask_not_printed = (~p_print) & (cur_mat != -1)
|
|
239
|
+
if mask_not_printed.any():
|
|
240
|
+
indices_np = cur_mat[mask_not_printed].to(torch.int64)
|
|
241
|
+
td_vals = material_TDs.index_select(0, indices_np)
|
|
242
|
+
col_vals = material_colors.index_select(0, indices_np)
|
|
243
|
+
opac_vals = o + (
|
|
244
|
+
A * torch.log1p(k * (acc_thick[mask_not_printed] / td_vals))
|
|
245
|
+
+ b * (acc_thick[mask_not_printed] / td_vals)
|
|
246
|
+
)
|
|
247
|
+
opac_vals = torch.clamp(opac_vals, 0.0, 1.0)
|
|
248
|
+
comp[mask_not_printed] = comp[mask_not_printed] + (
|
|
249
|
+
(remaining[mask_not_printed] * opac_vals).unsqueeze(-1) * col_vals
|
|
250
|
+
)
|
|
251
|
+
remaining[mask_not_printed] = remaining[mask_not_printed] * (1 - opac_vals)
|
|
252
|
+
cur_mat[mask_not_printed] = -1
|
|
253
|
+
acc_thick[mask_not_printed] = 0.0
|
|
254
|
+
|
|
255
|
+
# After the loop, composite any remaining accumulated segment.
|
|
256
|
+
mask_remain = cur_mat != -1
|
|
257
|
+
if mask_remain.any():
|
|
258
|
+
indices_rem = cur_mat[mask_remain].to(torch.int64)
|
|
259
|
+
td_vals = material_TDs.index_select(0, indices_rem)
|
|
260
|
+
col_vals = material_colors.index_select(0, indices_rem)
|
|
261
|
+
opac_vals = o + (
|
|
262
|
+
A * torch.log1p(k * (acc_thick[mask_remain] / td_vals))
|
|
263
|
+
+ b * (acc_thick[mask_remain] / td_vals)
|
|
264
|
+
)
|
|
265
|
+
opac_vals = torch.clamp(opac_vals, 0.0, 1.0)
|
|
266
|
+
comp[mask_remain] = comp[mask_remain] + (
|
|
267
|
+
(remaining[mask_remain] * opac_vals).unsqueeze(-1) * col_vals
|
|
268
|
+
)
|
|
269
|
+
remaining[mask_remain] = remaining[mask_remain] * (1 - opac_vals)
|
|
270
|
+
cur_mat[mask_remain] = -1
|
|
271
|
+
acc_thick[mask_remain] = 0.0
|
|
272
|
+
|
|
273
|
+
comp = comp + remaining.unsqueeze(-1) * background
|
|
274
|
+
return comp * 255.0
|
|
275
|
+
|
|
276
|
+
|
|
164
277
|
@torch.jit.script
|
|
165
278
|
def adaptive_round(
|
|
166
279
|
x: torch.Tensor, tau: float, high_tau: float, low_tau: float, temp: float
|
|
@@ -33,7 +33,7 @@ def extract_filament_swaps(disc_global, disc_height_image, background_layers):
|
|
|
33
33
|
current = int(disc_global[i])
|
|
34
34
|
# If this is the first layer or the material changes from the previous layer…
|
|
35
35
|
if current != prev:
|
|
36
|
-
slider =
|
|
36
|
+
slider = i + background_layers
|
|
37
37
|
slider_values.append(slider)
|
|
38
38
|
filament_indices.append(prev)
|
|
39
39
|
prev = current
|
|
@@ -118,7 +118,7 @@ def generate_project_file(
|
|
|
118
118
|
},
|
|
119
119
|
)
|
|
120
120
|
# add black to slider value
|
|
121
|
-
slider_values.insert(0, (args.background_height // args.layer_height)
|
|
121
|
+
slider_values.insert(0, (args.background_height // args.layer_height))
|
|
122
122
|
|
|
123
123
|
# reverse order of filament set
|
|
124
124
|
filament_set = filament_set[::-1]
|
|
@@ -186,11 +186,9 @@ def generate_project_file(
|
|
|
186
186
|
json.dump(project_data, f, indent=4)
|
|
187
187
|
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def generate_stl(height_map, filename, background_height, maximum_x_y_size, alpha_mask=None):
|
|
189
|
+
def generate_stl(
|
|
190
|
+
height_map, filename, background_height, maximum_x_y_size, alpha_mask=None
|
|
191
|
+
):
|
|
194
192
|
"""
|
|
195
193
|
Generate a binary STL file from a height map with an optional alpha mask.
|
|
196
194
|
If alpha_mask is provided, vertices where alpha < 128 are omitted.
|
|
@@ -213,7 +211,7 @@ def generate_stl(height_map, filename, background_height, maximum_x_y_size, alph
|
|
|
213
211
|
if alpha_mask is None:
|
|
214
212
|
valid_mask = np.ones((H, W), dtype=bool)
|
|
215
213
|
else:
|
|
216
|
-
valid_mask =
|
|
214
|
+
valid_mask = alpha_mask >= 128
|
|
217
215
|
|
|
218
216
|
# --- Create vertices for the top surface ---
|
|
219
217
|
# We compute a grid of vertices (even for masked-out regions) but later only use those that are valid.
|
|
@@ -250,7 +248,12 @@ def generate_stl(height_map, filename, background_height, maximum_x_y_size, alph
|
|
|
250
248
|
top_triangles_indices = [] # Each element is a tuple of 3 (i,j) indices.
|
|
251
249
|
for i in range(H - 1):
|
|
252
250
|
for j in range(W - 1):
|
|
253
|
-
if
|
|
251
|
+
if (
|
|
252
|
+
valid_mask[i, j]
|
|
253
|
+
and valid_mask[i, j + 1]
|
|
254
|
+
and valid_mask[i + 1, j + 1]
|
|
255
|
+
and valid_mask[i + 1, j]
|
|
256
|
+
):
|
|
254
257
|
# Define the four corners of the quad.
|
|
255
258
|
v0 = top_vertices[i, j]
|
|
256
259
|
v1 = top_vertices[i, j + 1]
|
|
@@ -306,7 +309,12 @@ def generate_stl(height_map, filename, background_height, maximum_x_y_size, alph
|
|
|
306
309
|
# The ordering is reversed (relative to the top face) so that the normals face downward.
|
|
307
310
|
for i in range(H - 1):
|
|
308
311
|
for j in range(W - 1):
|
|
309
|
-
if
|
|
312
|
+
if (
|
|
313
|
+
valid_mask[i, j]
|
|
314
|
+
and valid_mask[i, j + 1]
|
|
315
|
+
and valid_mask[i + 1, j + 1]
|
|
316
|
+
and valid_mask[i + 1, j]
|
|
317
|
+
):
|
|
310
318
|
v0 = bottom_vertices[i, j]
|
|
311
319
|
v1 = bottom_vertices[i, j + 1]
|
|
312
320
|
v2 = bottom_vertices[i + 1, j + 1]
|
|
@@ -383,7 +391,7 @@ def generate_swap_instructions(
|
|
|
383
391
|
instructions.append("Start with your background color")
|
|
384
392
|
for i in range(0, L):
|
|
385
393
|
if i == 0 or int(discrete_global[i]) != int(discrete_global[i - 1]):
|
|
386
|
-
ie = i
|
|
394
|
+
ie = i + 1
|
|
387
395
|
instructions.append(
|
|
388
396
|
f"At layer #{ie + background_layers} ({(ie * h) + background_height:.2f}mm) swap to {material_names[int(discrete_global[i])]}"
|
|
389
397
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import torch
|
|
2
2
|
from tqdm import tqdm
|
|
3
3
|
|
|
4
|
-
from autoforge.Helper.OptimizerHelper import
|
|
4
|
+
from autoforge.Helper.OptimizerHelper import discretize_solution, composite_image_disc
|
|
5
5
|
from autoforge.Loss.LossFunctions import compute_loss
|
|
6
6
|
|
|
7
7
|
|
|
@@ -55,7 +55,7 @@ def prune_num_colors(
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
with torch.no_grad():
|
|
58
|
-
out_im =
|
|
58
|
+
out_im = composite_image_disc(
|
|
59
59
|
pixel_height_logits,
|
|
60
60
|
logits_for_disc,
|
|
61
61
|
tau_for_comp,
|
|
@@ -65,7 +65,6 @@ def prune_num_colors(
|
|
|
65
65
|
material_colors,
|
|
66
66
|
material_TDs,
|
|
67
67
|
background,
|
|
68
|
-
mode="discrete",
|
|
69
68
|
rng_seed=rng_seed,
|
|
70
69
|
)
|
|
71
70
|
|
|
@@ -148,7 +147,7 @@ def prune_num_swaps(
|
|
|
148
147
|
)
|
|
149
148
|
|
|
150
149
|
with torch.no_grad():
|
|
151
|
-
out_im =
|
|
150
|
+
out_im = composite_image_disc(
|
|
152
151
|
pixel_height_logits,
|
|
153
152
|
logits_for_disc,
|
|
154
153
|
tau_for_comp,
|
|
@@ -158,7 +157,6 @@ def prune_num_swaps(
|
|
|
158
157
|
material_colors,
|
|
159
158
|
material_TDs,
|
|
160
159
|
background,
|
|
161
|
-
mode="discrete",
|
|
162
160
|
rng_seed=rng_seed,
|
|
163
161
|
)
|
|
164
162
|
return compute_loss(
|
|
@@ -413,7 +411,7 @@ def prune_redundant_layers(
|
|
|
413
411
|
current_max_layers = current_params["global_logits"].shape[0]
|
|
414
412
|
|
|
415
413
|
# Compute baseline composite and loss.
|
|
416
|
-
comp =
|
|
414
|
+
comp = composite_image_disc(
|
|
417
415
|
current_params["pixel_height_logits"],
|
|
418
416
|
current_params["global_logits"],
|
|
419
417
|
final_tau,
|
|
@@ -423,7 +421,6 @@ def prune_redundant_layers(
|
|
|
423
421
|
material_colors,
|
|
424
422
|
material_TDs,
|
|
425
423
|
background,
|
|
426
|
-
mode="discrete",
|
|
427
424
|
rng_seed=rng_seed,
|
|
428
425
|
)
|
|
429
426
|
disc_global, _ = discretize_solution(
|
|
@@ -458,7 +455,7 @@ def prune_redundant_layers(
|
|
|
458
455
|
candidate_params, candidate_max_layers = remove_layer_from_solution(
|
|
459
456
|
current_params, layer, final_tau, h, current_max_layers, rng_seed
|
|
460
457
|
)
|
|
461
|
-
candidate_comp =
|
|
458
|
+
candidate_comp = composite_image_disc(
|
|
462
459
|
candidate_params["pixel_height_logits"],
|
|
463
460
|
candidate_params["global_logits"],
|
|
464
461
|
final_tau,
|
|
@@ -468,7 +465,6 @@ def prune_redundant_layers(
|
|
|
468
465
|
material_colors,
|
|
469
466
|
material_TDs,
|
|
470
467
|
background,
|
|
471
|
-
mode="discrete",
|
|
472
468
|
rng_seed=rng_seed,
|
|
473
469
|
)
|
|
474
470
|
candidate_disc_global, _ = discretize_solution(
|
|
@@ -2,7 +2,7 @@ import torch
|
|
|
2
2
|
import torch.nn.functional as F
|
|
3
3
|
|
|
4
4
|
from autoforge.Helper.ImageHelper import srgb_to_lab, increase_saturation
|
|
5
|
-
from autoforge.Helper.OptimizerHelper import
|
|
5
|
+
from autoforge.Helper.OptimizerHelper import composite_image_cont
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def loss_fn(
|
|
@@ -22,7 +22,7 @@ def loss_fn(
|
|
|
22
22
|
Full forward pass for continuous assignment:
|
|
23
23
|
composite, then compute unified loss on (global_logits).
|
|
24
24
|
"""
|
|
25
|
-
comp =
|
|
25
|
+
comp = composite_image_cont(
|
|
26
26
|
params["pixel_height_logits"],
|
|
27
27
|
params["global_logits"],
|
|
28
28
|
tau_height,
|
|
@@ -32,7 +32,6 @@ def loss_fn(
|
|
|
32
32
|
material_colors,
|
|
33
33
|
material_TDs,
|
|
34
34
|
background,
|
|
35
|
-
mode="continuous",
|
|
36
35
|
)
|
|
37
36
|
return compute_loss(
|
|
38
37
|
material_assignment=params["global_logits"],
|
|
@@ -5,11 +5,15 @@ import random
|
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
6
|
import numpy as np
|
|
7
7
|
import torch
|
|
8
|
-
import torch.optim as optim
|
|
9
8
|
from tqdm import tqdm
|
|
10
9
|
from torch.utils.tensorboard import SummaryWriter
|
|
11
10
|
|
|
12
|
-
from autoforge.Helper.
|
|
11
|
+
from autoforge.Helper.CAdamW import CAdamW
|
|
12
|
+
from autoforge.Helper.OptimizerHelper import (
|
|
13
|
+
discretize_solution,
|
|
14
|
+
composite_image_cont,
|
|
15
|
+
composite_image_disc,
|
|
16
|
+
)
|
|
13
17
|
from autoforge.Helper.PruningHelper import prune_colors_and_swaps, find_color_bands
|
|
14
18
|
from autoforge.Loss.LossFunctions import loss_fn, compute_loss
|
|
15
19
|
|
|
@@ -64,7 +68,7 @@ class FilamentOptimizer:
|
|
|
64
68
|
# Initialize TensorBoard writer
|
|
65
69
|
if args.tensorboard:
|
|
66
70
|
if args.run_name:
|
|
67
|
-
self.writer = SummaryWriter(log_dir=f
|
|
71
|
+
self.writer = SummaryWriter(log_dir=f"runs/{args.run_name}")
|
|
68
72
|
else:
|
|
69
73
|
self.writer = SummaryWriter()
|
|
70
74
|
else:
|
|
@@ -99,9 +103,10 @@ class FilamentOptimizer:
|
|
|
99
103
|
)
|
|
100
104
|
|
|
101
105
|
# Initialize optimizer
|
|
102
|
-
self.optimizer =
|
|
106
|
+
self.optimizer = CAdamW(
|
|
103
107
|
[self.params["pixel_height_logits"], self.params["global_logits"]],
|
|
104
108
|
lr=self.learning_rate,
|
|
109
|
+
weight_decay=1e-2,
|
|
105
110
|
)
|
|
106
111
|
|
|
107
112
|
# Setup best discrete solution tracking
|
|
@@ -193,7 +198,9 @@ class FilamentOptimizer:
|
|
|
193
198
|
|
|
194
199
|
return loss
|
|
195
200
|
|
|
196
|
-
def log_to_tensorboard(
|
|
201
|
+
def log_to_tensorboard(
|
|
202
|
+
self, interval: int = 100, namespace: str = "", step: int = None
|
|
203
|
+
):
|
|
197
204
|
"""
|
|
198
205
|
Log metrics and images to TensorBoard.
|
|
199
206
|
|
|
@@ -211,22 +218,26 @@ class FilamentOptimizer:
|
|
|
211
218
|
steps = step if step is not None else self.num_steps_done
|
|
212
219
|
|
|
213
220
|
# Log metrics
|
|
214
|
-
self.writer.add_scalar(
|
|
215
|
-
|
|
221
|
+
self.writer.add_scalar(
|
|
222
|
+
f"Loss/{prefix}best_discrete", self.best_discrete_loss, steps
|
|
223
|
+
)
|
|
224
|
+
self.writer.add_scalar(f"Loss/{prefix}best_swaps", self.best_swaps, steps)
|
|
216
225
|
|
|
217
226
|
tau_height, tau_global = self._get_tau()
|
|
218
227
|
|
|
219
228
|
# Metrics that are only relevant for the main optimization loop
|
|
220
229
|
if not prefix:
|
|
221
|
-
self.writer.add_scalar(
|
|
222
|
-
self.writer.add_scalar(
|
|
223
|
-
self.writer.add_scalar(
|
|
224
|
-
|
|
230
|
+
self.writer.add_scalar("Params/tau_height", tau_height, steps)
|
|
231
|
+
self.writer.add_scalar("Params/tau_global", tau_global, steps)
|
|
232
|
+
self.writer.add_scalar(
|
|
233
|
+
"Params/lr", self.optimizer.param_groups[0]["lr"], steps
|
|
234
|
+
)
|
|
235
|
+
self.writer.add_scalar("Loss/train", self.loss, steps)
|
|
225
236
|
|
|
226
237
|
# Log images periodically
|
|
227
238
|
if (steps + 1) % interval == 0:
|
|
228
239
|
with torch.no_grad():
|
|
229
|
-
comp_img =
|
|
240
|
+
comp_img = composite_image_cont(
|
|
230
241
|
self.params["pixel_height_logits"],
|
|
231
242
|
self.params["global_logits"],
|
|
232
243
|
tau_height,
|
|
@@ -235,9 +246,13 @@ class FilamentOptimizer:
|
|
|
235
246
|
self.max_layers,
|
|
236
247
|
self.material_colors,
|
|
237
248
|
self.material_TDs,
|
|
238
|
-
self.background
|
|
249
|
+
self.background,
|
|
250
|
+
)
|
|
251
|
+
self.writer.add_images(
|
|
252
|
+
f"Current Output/{prefix}composite",
|
|
253
|
+
comp_img.permute(2, 0, 1).unsqueeze(0) / 255.0,
|
|
254
|
+
steps,
|
|
239
255
|
)
|
|
240
|
-
self.writer.add_images(f'Current Output/{prefix}composite', comp_img.permute(2, 0, 1).unsqueeze(0) / 255.0, steps)
|
|
241
256
|
|
|
242
257
|
def visualize(self, interval: int = 25):
|
|
243
258
|
"""
|
|
@@ -255,7 +270,7 @@ class FilamentOptimizer:
|
|
|
255
270
|
|
|
256
271
|
with torch.no_grad():
|
|
257
272
|
tau_h, tau_g = self._get_tau()
|
|
258
|
-
comp =
|
|
273
|
+
comp = composite_image_cont(
|
|
259
274
|
self.params["pixel_height_logits"],
|
|
260
275
|
self.params["global_logits"],
|
|
261
276
|
tau_h,
|
|
@@ -265,14 +280,13 @@ class FilamentOptimizer:
|
|
|
265
280
|
self.material_colors,
|
|
266
281
|
self.material_TDs,
|
|
267
282
|
self.background,
|
|
268
|
-
mode="continuous",
|
|
269
283
|
)
|
|
270
284
|
comp_np = np.clip(comp.cpu().detach().numpy(), 0, 255).astype(np.uint8)
|
|
271
285
|
self.current_comp_ax.set_data(comp_np)
|
|
272
286
|
|
|
273
287
|
if self.best_params is not None:
|
|
274
288
|
with torch.no_grad():
|
|
275
|
-
best_comp =
|
|
289
|
+
best_comp = composite_image_disc(
|
|
276
290
|
self.best_params["pixel_height_logits"],
|
|
277
291
|
self.best_params["global_logits"],
|
|
278
292
|
self.final_tau,
|
|
@@ -282,7 +296,6 @@ class FilamentOptimizer:
|
|
|
282
296
|
self.material_colors,
|
|
283
297
|
self.material_TDs,
|
|
284
298
|
self.background,
|
|
285
|
-
mode="discrete",
|
|
286
299
|
rng_seed=self.best_seed,
|
|
287
300
|
)
|
|
288
301
|
best_comp_np = np.clip(best_comp.cpu().detach().numpy(), 0, 255).astype(
|
|
@@ -434,7 +447,7 @@ class FilamentOptimizer:
|
|
|
434
447
|
|
|
435
448
|
# 2) Compute discrete-mode composite
|
|
436
449
|
with torch.no_grad():
|
|
437
|
-
comp_disc =
|
|
450
|
+
comp_disc = composite_image_disc(
|
|
438
451
|
self.params["pixel_height_logits"],
|
|
439
452
|
self.params["global_logits"],
|
|
440
453
|
self.final_tau,
|
|
@@ -444,7 +457,6 @@ class FilamentOptimizer:
|
|
|
444
457
|
self.material_colors,
|
|
445
458
|
self.material_TDs,
|
|
446
459
|
self.background,
|
|
447
|
-
mode="discrete", # or "discrete" if you prefer fully discrete
|
|
448
460
|
rng_seed=seed,
|
|
449
461
|
)
|
|
450
462
|
|
|
@@ -487,7 +499,7 @@ class FilamentOptimizer:
|
|
|
487
499
|
disc_global, disc_height_image = discretize_solution(
|
|
488
500
|
self.best_params, self.final_tau, self.h, self.max_layers, rng_seed=seed
|
|
489
501
|
)
|
|
490
|
-
comp_disc =
|
|
502
|
+
comp_disc = composite_image_disc(
|
|
491
503
|
self.best_params["pixel_height_logits"],
|
|
492
504
|
self.best_params["global_logits"],
|
|
493
505
|
self.final_tau,
|
|
@@ -497,7 +509,6 @@ class FilamentOptimizer:
|
|
|
497
509
|
self.material_colors,
|
|
498
510
|
self.material_TDs,
|
|
499
511
|
self.background,
|
|
500
|
-
mode="discrete",
|
|
501
512
|
rng_seed=seed,
|
|
502
513
|
)
|
|
503
514
|
current_disc_loss = compute_loss(
|
|
@@ -10,9 +10,9 @@ from autoforge.Helper.FilamentHelper import hex_to_rgb, load_materials
|
|
|
10
10
|
from autoforge.Helper.ImageHelper import resize_image, resize_image_exact
|
|
11
11
|
from autoforge.Helper.OptimizerHelper import (
|
|
12
12
|
init_height_map,
|
|
13
|
-
composite_image,
|
|
14
13
|
discretize_solution,
|
|
15
14
|
init_height_map_depth_color_adjusted,
|
|
15
|
+
composite_image_disc,
|
|
16
16
|
)
|
|
17
17
|
from autoforge.Helper.OutputHelper import (
|
|
18
18
|
generate_stl,
|
|
@@ -207,6 +207,9 @@ def main():
|
|
|
207
207
|
"Background must be multiple of layer_height"
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
+
assert os.path.exists(args.input_image), "Input image not found"
|
|
211
|
+
assert os.path.exists(args.csv_file), "CSV file not found"
|
|
212
|
+
|
|
210
213
|
random_seed = args.random_seed
|
|
211
214
|
if random_seed == 0:
|
|
212
215
|
random_seed = int(time.time())
|
|
@@ -387,7 +390,7 @@ def main():
|
|
|
387
390
|
|
|
388
391
|
print("Done. Saving outputs...")
|
|
389
392
|
# Save Image
|
|
390
|
-
comp_disc =
|
|
393
|
+
comp_disc = composite_image_disc(
|
|
391
394
|
params["pixel_height_logits"],
|
|
392
395
|
params["global_logits"],
|
|
393
396
|
args.final_tau,
|
|
@@ -397,7 +400,6 @@ def main():
|
|
|
397
400
|
material_colors,
|
|
398
401
|
material_TDs,
|
|
399
402
|
background,
|
|
400
|
-
mode="discrete", # or "discrete" if you prefer fully discrete
|
|
401
403
|
rng_seed=optimizer.best_seed,
|
|
402
404
|
)
|
|
403
405
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|