DeepSDFStruct 1.7.2__py3-none-any.whl
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.
- DeepSDFStruct/SDF.py +1734 -0
- DeepSDFStruct/__init__.py +66 -0
- DeepSDFStruct/deep_sdf/__init__.py +69 -0
- DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
- DeepSDFStruct/deep_sdf/data.py +258 -0
- DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
- DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
- DeepSDFStruct/deep_sdf/models.py +197 -0
- DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
- DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
- DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
- DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
- DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
- DeepSDFStruct/deep_sdf/plotting.py +143 -0
- DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
- DeepSDFStruct/deep_sdf/training.py +840 -0
- DeepSDFStruct/deep_sdf/workspace.py +363 -0
- DeepSDFStruct/design_of_experiments.py +235 -0
- DeepSDFStruct/flexicubes/__init__.py +15 -0
- DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
- DeepSDFStruct/flexicubes/tables.py +2338 -0
- DeepSDFStruct/flexisquares/__init__.py +15 -0
- DeepSDFStruct/flexisquares/flexisquares.py +967 -0
- DeepSDFStruct/flexisquares/tables.py +63 -0
- DeepSDFStruct/lattice_structure.py +254 -0
- DeepSDFStruct/local_shapes.py +226 -0
- DeepSDFStruct/mesh.py +1184 -0
- DeepSDFStruct/optimization.py +357 -0
- DeepSDFStruct/parametrization.py +215 -0
- DeepSDFStruct/pretrained_models.py +128 -0
- DeepSDFStruct/sampling.py +670 -0
- DeepSDFStruct/sdf_operations.py +990 -0
- DeepSDFStruct/sdf_primitives.py +1416 -0
- DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
- DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
- DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
- DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
- DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
- DeepSDFStruct/torch_spline.py +446 -0
- DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
- DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/specs.json +46 -0
- DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
- DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
- DeepSDFStruct/utils.py +96 -0
- DeepSDFStruct/visualization/latent_viewer.py +211 -0
- DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
- benchmarks/generate_sdf_showcase.py +622 -0
- benchmarks/sdf_from_mesh_benchmark.py +52 -0
- benchmarks/sdf_from_mesh_comparison.md +33 -0
- benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
- deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
- deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
- deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
- deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
- deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
- deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
- deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
- deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
- docs/Makefile +20 -0
- docs/api_reference.rst +6 -0
- docs/conf.py +60 -0
- docs/index.rst +136 -0
- docs/make.bat +35 -0
- docs/qr_code_github.png +0 -0
- docs/qr_code_paper.png +0 -0
- docs/readme_images/example_output_01.png +0 -0
- docs/readme_images/example_output_02.png +0 -0
- docs/readme_images/example_output_03.png +0 -0
- docs/readme_images/example_output_04.png +0 -0
- tests/data/chairs/1005.obj +282 -0
- tests/data/chairs/1024.obj +222 -0
- tests/data/chairs/README.md +1 -0
- tests/data/circular_balls_test_case.stl +0 -0
- tests/data/cone.stl +1794 -0
- tests/data/example_disconnectd_mesh.inp +1784 -0
- tests/data/example_line_mesh.vtk +0 -0
- tests/data/stanford_bunny.stl +0 -0
- tests/data/sweep_test_case.stl +0 -0
- tests/test_DOE.py +49 -0
- tests/test_elongate_sdf.py +195 -0
- tests/test_flexisquares.py +187 -0
- tests/test_generate_dataset.py +76 -0
- tests/test_lattice_evaluation.py +137 -0
- tests/test_mesh_export.py +137 -0
- tests/test_mesh_functions.py +49 -0
- tests/test_networks.py +71 -0
- tests/test_pretrained_models.py +40 -0
- tests/test_reconstruction.py +101 -0
- tests/test_sdf_from_mesh.py +52 -0
- tests/test_sdf_functions.py +117 -0
- tests/test_sdf_primitives.py +668 -0
- tests/test_sdf_trimesh_comparison.py +482 -0
- tests/test_splinepy_unitcells.py +64 -0
- tests/test_structural_optimization.py +178 -0
- tests/test_torch_spline.py +216 -0
- tests/test_train_model.py +110 -0
- tests/test_training_data_ids.py +41 -0
- tests/tmp_outputs/temp_file.txt +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
|
|
4
|
+
activations = {
|
|
5
|
+
"relu": nn.ReLU(),
|
|
6
|
+
"tanh": nn.Tanh(),
|
|
7
|
+
"gelu": nn.GELU(),
|
|
8
|
+
"silu": nn.SiLU(),
|
|
9
|
+
"leaky_relu": nn.LeakyReLU(),
|
|
10
|
+
"elu": nn.ELU(),
|
|
11
|
+
"selu": nn.SELU(),
|
|
12
|
+
"sigmoid": nn.Sigmoid(),
|
|
13
|
+
"softplus": nn.Softplus(),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClampedL1Loss(torch.nn.Module):
|
|
18
|
+
def __init__(self, clamp_val=0.1):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.clamp_val = clamp_val
|
|
21
|
+
self.loss = torch.nn.L1Loss()
|
|
22
|
+
|
|
23
|
+
def forward(self, input, target):
|
|
24
|
+
input_clamped = input.clamp(-self.clamp_val, self.clamp_val)
|
|
25
|
+
target_clamped = target.clamp(-self.clamp_val, self.clamp_val)
|
|
26
|
+
return self.loss(input_clamped, target_clamped)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LeakyClampedL1Loss(torch.nn.Module):
|
|
30
|
+
"""L1 loss that clamps values within [-clamp_val, clamp_val], but applies a
|
|
31
|
+
leaky linear continuation outside that range instead of hard clamping.
|
|
32
|
+
|
|
33
|
+
This behaves like a `clamp` in the center and a `LeakyReLU`-style slope
|
|
34
|
+
outside. Both input and target are transformed the same way before L1.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, clamp_val=0.1, leak=0.01):
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.clamp_val = clamp_val
|
|
40
|
+
self.leak = leak
|
|
41
|
+
self.loss = torch.nn.L1Loss()
|
|
42
|
+
|
|
43
|
+
def _leaky_clamp(self, x: torch.Tensor) -> torch.Tensor:
|
|
44
|
+
cv = self.clamp_val
|
|
45
|
+
lk = self.leak
|
|
46
|
+
# If x > c -> c + l*(x-c); if x < -c -> -c + l*(x + c); else x
|
|
47
|
+
pos = torch.where(x > cv, cv + lk * (x - cv), x)
|
|
48
|
+
out = torch.where(pos < -cv, -cv + lk * (pos + cv), pos)
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
def forward(self, input, target):
|
|
52
|
+
input_trans = self._leaky_clamp(input)
|
|
53
|
+
target_trans = self._leaky_clamp(target)
|
|
54
|
+
return self.loss(input_trans, target_trans)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_loss_function(loss_fun_spec: str):
|
|
58
|
+
"""Return a loss function instance for the given spec string."""
|
|
59
|
+
if loss_fun_spec == "clampedL1":
|
|
60
|
+
return ClampedL1Loss()
|
|
61
|
+
elif loss_fun_spec == "leakyClampedL1":
|
|
62
|
+
return LeakyClampedL1Loss()
|
|
63
|
+
elif loss_fun_spec == "L1":
|
|
64
|
+
return torch.nn.L1Loss()
|
|
65
|
+
elif loss_fun_spec == "MSE":
|
|
66
|
+
return torch.nn.MSELoss()
|
|
67
|
+
elif loss_fun_spec == "huber":
|
|
68
|
+
return torch.nn.HuberLoss()
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(f"Unsupported loss function: {loss_fun_spec}")
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Training Visualization and Logging
|
|
3
|
+
==================================
|
|
4
|
+
|
|
5
|
+
This module provides utilities for visualizing DeepSDF training progress
|
|
6
|
+
and generating plots of training metrics.
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
plot_logs
|
|
12
|
+
Plot training loss curves with smoothing and optional learning rate overlay.
|
|
13
|
+
Useful for monitoring training progress and diagnosing convergence issues.
|
|
14
|
+
|
|
15
|
+
plot_reconstruction_loss
|
|
16
|
+
Visualize loss during shape reconstruction, showing how well the model
|
|
17
|
+
fits to target geometries.
|
|
18
|
+
|
|
19
|
+
extract_paths
|
|
20
|
+
Helper for extracting file paths from nested data structures.
|
|
21
|
+
|
|
22
|
+
running_mean
|
|
23
|
+
Compute running average for smoothing noisy loss curves.
|
|
24
|
+
|
|
25
|
+
The module integrates with the workspace utilities to load training logs
|
|
26
|
+
and generate publication-quality plots for analysis and presentation.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import os
|
|
31
|
+
import logging
|
|
32
|
+
import torch
|
|
33
|
+
import matplotlib.pyplot as plt
|
|
34
|
+
|
|
35
|
+
import DeepSDFStruct.deep_sdf.workspace as ws
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def extract_paths(data, current_path=""):
|
|
41
|
+
paths = []
|
|
42
|
+
|
|
43
|
+
if isinstance(data, dict):
|
|
44
|
+
for key, value in data.items():
|
|
45
|
+
new_path = f"{current_path}/{key}" if current_path else key
|
|
46
|
+
paths.extend(extract_paths(value, new_path))
|
|
47
|
+
|
|
48
|
+
elif isinstance(data, list):
|
|
49
|
+
for item in data:
|
|
50
|
+
paths.extend(extract_paths(item, current_path))
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
paths.append(f"{current_path}/{data}")
|
|
54
|
+
|
|
55
|
+
return paths
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def running_mean(x, N):
|
|
59
|
+
cumsum = np.cumsum(np.insert(x, 0, 0))
|
|
60
|
+
return (cumsum[N:] - cumsum[:-N]) / float(N)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def plot_logs(experiment_directory, show_lr=False, ax=None, filename=None):
|
|
64
|
+
|
|
65
|
+
logs = torch.load(os.path.join(experiment_directory, ws.logs_filename))
|
|
66
|
+
|
|
67
|
+
num_iters = len(logs["loss"])
|
|
68
|
+
iters_per_epoch = num_iters / logs["epoch"]
|
|
69
|
+
|
|
70
|
+
smoothed_loss_41 = running_mean(logs["loss"], 41)
|
|
71
|
+
|
|
72
|
+
show_plt = False
|
|
73
|
+
|
|
74
|
+
if show_lr:
|
|
75
|
+
if ax is None:
|
|
76
|
+
fig, ax = plt.subplots(2, 1)
|
|
77
|
+
fig.tight_layout()
|
|
78
|
+
show_plt = True
|
|
79
|
+
else:
|
|
80
|
+
if ax is None:
|
|
81
|
+
fig, ax = plt.subplots()
|
|
82
|
+
show_plt = True
|
|
83
|
+
ax = [ax]
|
|
84
|
+
|
|
85
|
+
ax[0].plot(
|
|
86
|
+
np.arange(num_iters) / iters_per_epoch,
|
|
87
|
+
logs["loss"],
|
|
88
|
+
"#82c6eb",
|
|
89
|
+
np.arange(20, num_iters - 20) / iters_per_epoch,
|
|
90
|
+
smoothed_loss_41,
|
|
91
|
+
"#2a9edd",
|
|
92
|
+
)
|
|
93
|
+
ax[0].set_yscale("log")
|
|
94
|
+
|
|
95
|
+
ax[0].set(xlabel="Epoch", ylabel="Loss")
|
|
96
|
+
ax[0].legend(["Loss", "Loss (Running Mean)", "Loss (Running Mean 41)"])
|
|
97
|
+
|
|
98
|
+
if show_lr:
|
|
99
|
+
combined_lrs = np.array(logs["learning_rate"])
|
|
100
|
+
ax[1].plot(
|
|
101
|
+
np.arange(combined_lrs.shape[0]),
|
|
102
|
+
combined_lrs[:, 0],
|
|
103
|
+
np.arange(combined_lrs.shape[0]),
|
|
104
|
+
combined_lrs[:, 1],
|
|
105
|
+
)
|
|
106
|
+
ax[1].set(xlabel="Epoch", ylabel="Learning Rate")
|
|
107
|
+
ax[1].legend(["Decoder", "Latent Vector"])
|
|
108
|
+
|
|
109
|
+
for axis in ax:
|
|
110
|
+
axis.grid()
|
|
111
|
+
if filename is not None:
|
|
112
|
+
plt.savefig(filename, bbox_inches="tight")
|
|
113
|
+
plt.close()
|
|
114
|
+
elif show_plt:
|
|
115
|
+
plt.show()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def plot_reconstruction_loss(loss_history, iters_per_epoch, filename=None):
|
|
119
|
+
|
|
120
|
+
losses = np.array(loss_history)
|
|
121
|
+
num_iters = len(losses)
|
|
122
|
+
|
|
123
|
+
latest_epoch = num_iters / iters_per_epoch
|
|
124
|
+
logger.info("latest epoch is {}".format(latest_epoch))
|
|
125
|
+
logger.info("{} iters per epoch".format(iters_per_epoch))
|
|
126
|
+
|
|
127
|
+
smoothed_loss_41 = running_mean(losses, 41)
|
|
128
|
+
|
|
129
|
+
fig, ax = plt.subplots()
|
|
130
|
+
|
|
131
|
+
ax.plot(
|
|
132
|
+
np.arange(num_iters) / iters_per_epoch,
|
|
133
|
+
losses,
|
|
134
|
+
"#82c6eb",
|
|
135
|
+
np.arange(20, num_iters - 20) / iters_per_epoch,
|
|
136
|
+
smoothed_loss_41,
|
|
137
|
+
"#2a9edd",
|
|
138
|
+
)
|
|
139
|
+
ax.set_yscale("log")
|
|
140
|
+
ax.set(xlabel="Epoch", ylabel="Loss")
|
|
141
|
+
ax.legend(["Loss", "Loss (Running Mean 41)"])
|
|
142
|
+
ax.grid()
|
|
143
|
+
plt.savefig(filename, bbox_inches="tight")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shape Reconstruction and Latent Code Optimization
|
|
3
|
+
=================================================
|
|
4
|
+
|
|
5
|
+
This module provides methods for reconstructing shapes from SDF samples
|
|
6
|
+
by optimizing latent codes. This is a key capability of DeepSDF models,
|
|
7
|
+
enabling fitting of new geometries not seen during training.
|
|
8
|
+
|
|
9
|
+
Key Functions
|
|
10
|
+
-------------
|
|
11
|
+
|
|
12
|
+
reconstruct_from_samples
|
|
13
|
+
Optimize a latent code to match given SDF samples. Uses gradient
|
|
14
|
+
descent to find the latent representation that best reproduces
|
|
15
|
+
the target geometry.
|
|
16
|
+
|
|
17
|
+
The reconstruction process:
|
|
18
|
+
1. Start with initial latent code (often zeros or random)
|
|
19
|
+
2. Forward pass through decoder to get SDF predictions
|
|
20
|
+
3. Compute loss between predictions and target samples
|
|
21
|
+
4. Backpropagate and update latent code
|
|
22
|
+
5. Repeat until convergence
|
|
23
|
+
|
|
24
|
+
This enables:
|
|
25
|
+
- Fitting trained models to new geometries
|
|
26
|
+
- Shape interpolation by blending latent codes
|
|
27
|
+
- Shape editing by modifying latent codes
|
|
28
|
+
- Compression by storing only latent codes
|
|
29
|
+
|
|
30
|
+
Examples
|
|
31
|
+
--------
|
|
32
|
+
Reconstruct a shape from samples::
|
|
33
|
+
|
|
34
|
+
from DeepSDFStruct.deep_sdf.reconstruction import reconstruct_from_samples
|
|
35
|
+
from DeepSDFStruct.sampling import sample_sdf_near_surface
|
|
36
|
+
|
|
37
|
+
# Sample target geometry
|
|
38
|
+
target_samples = sample_sdf_near_surface(target_sdf, n_samples=100000)
|
|
39
|
+
|
|
40
|
+
# Reconstruct using trained model
|
|
41
|
+
reconstructed_sdf = reconstruct_from_samples(
|
|
42
|
+
model,
|
|
43
|
+
target_samples,
|
|
44
|
+
num_iterations=1000,
|
|
45
|
+
lr=5e-4
|
|
46
|
+
)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from DeepSDFStruct.SDF import SDFBase
|
|
50
|
+
from DeepSDFStruct.deep_sdf.nn_utils import ClampedL1Loss
|
|
51
|
+
from torch.utils.data import TensorDataset, DataLoader
|
|
52
|
+
from DeepSDFStruct.sampling import SampledSDF
|
|
53
|
+
from DeepSDFStruct.deep_sdf.plotting import plot_reconstruction_loss
|
|
54
|
+
from DeepSDFStruct.torch_spline import TorchSpline, TorchScaling
|
|
55
|
+
from tqdm import trange
|
|
56
|
+
import torch
|
|
57
|
+
import torch.nn as nn
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reconstruct_from_samples(
|
|
61
|
+
sdf: SDFBase,
|
|
62
|
+
sdfSample: SampledSDF,
|
|
63
|
+
num_iterations=1000,
|
|
64
|
+
lr=5e-4,
|
|
65
|
+
loss_fn="ClampedL1",
|
|
66
|
+
batch_size=512,
|
|
67
|
+
drop_last=True,
|
|
68
|
+
use_tanh_on_gt=False,
|
|
69
|
+
loss_plot_path=None,
|
|
70
|
+
optimizer_name="adam",
|
|
71
|
+
deformation_function=None | TorchSpline | TorchScaling,
|
|
72
|
+
):
|
|
73
|
+
if optimizer_name == "adam":
|
|
74
|
+
optimizer = torch.optim.Adam(sdf.parameters(), lr=lr)
|
|
75
|
+
elif optimizer_name == "lbfgs":
|
|
76
|
+
optimizer = torch.optim.LBFGS(
|
|
77
|
+
sdf.parameters(),
|
|
78
|
+
lr=lr,
|
|
79
|
+
max_iter=20, # inner Newton iterations
|
|
80
|
+
history_size=100, # curvature memory
|
|
81
|
+
line_search_fn="strong_wolfe",
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
raise NotImplementedError(f"Optimizer {optimizer_name} not available.")
|
|
85
|
+
|
|
86
|
+
verts_min = sdfSample.samples.min(axis=0)
|
|
87
|
+
verts_max = sdfSample.samples.max(axis=0)
|
|
88
|
+
|
|
89
|
+
print("Min/Max in PHYSICAL space:\n")
|
|
90
|
+
for name, mn, mx in zip(["x", "y", "z"], verts_min.values, verts_max.values):
|
|
91
|
+
print(f"{name}: min={mn:.6f}, max={mx:.6f}")
|
|
92
|
+
|
|
93
|
+
if deformation_function is not None:
|
|
94
|
+
queries_parameter_space = deformation_function.inverse_target_points(
|
|
95
|
+
sdfSample.samples
|
|
96
|
+
).detach()
|
|
97
|
+
else:
|
|
98
|
+
queries_parameter_space = sdfSample.samples.detach()
|
|
99
|
+
|
|
100
|
+
queries_min = queries_parameter_space.min(dim=0).values
|
|
101
|
+
queries_max = queries_parameter_space.max(dim=0).values
|
|
102
|
+
|
|
103
|
+
print("\nMin/Max in QUERY space:\n")
|
|
104
|
+
for name, mn, mx in zip(
|
|
105
|
+
["x", "y", "z"], queries_min.tolist(), queries_max.tolist()
|
|
106
|
+
):
|
|
107
|
+
print(f"{name}: min={mn:.6f}, max={mx:.6f}")
|
|
108
|
+
|
|
109
|
+
gt_dist = sdfSample.distances
|
|
110
|
+
if use_tanh_on_gt:
|
|
111
|
+
gt_dist = torch.tanh(gt_dist)
|
|
112
|
+
|
|
113
|
+
pbar = trange(num_iterations, desc="Reconstructing SDF from mesh", leave=True)
|
|
114
|
+
|
|
115
|
+
if loss_fn == "L1":
|
|
116
|
+
Loss = torch.nn.L1Loss()
|
|
117
|
+
elif loss_fn == "ClampedL1":
|
|
118
|
+
Loss = ClampedL1Loss(clamp_val=0.1)
|
|
119
|
+
elif loss_fn == "MSE":
|
|
120
|
+
Loss = torch.nn.MSELoss()
|
|
121
|
+
else:
|
|
122
|
+
raise NotImplementedError(f"Loss function {loss_fn} not available.")
|
|
123
|
+
|
|
124
|
+
dataset = TensorDataset(queries_parameter_space, gt_dist)
|
|
125
|
+
if drop_last and (batch_size > len(dataset)):
|
|
126
|
+
print(
|
|
127
|
+
"Warning: drop_last was set to true, "
|
|
128
|
+
f"but batch size ({batch_size}) is larger "
|
|
129
|
+
f"than the size of the dataset ({len(dataset)})."
|
|
130
|
+
" setting drop_last=False"
|
|
131
|
+
)
|
|
132
|
+
drop_last = False
|
|
133
|
+
dataloader = DataLoader(
|
|
134
|
+
dataset, batch_size=batch_size, shuffle=True, drop_last=drop_last
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
loss_history = []
|
|
138
|
+
for e in pbar:
|
|
139
|
+
for querie_batch, gt_batch in dataloader:
|
|
140
|
+
|
|
141
|
+
def closure() -> torch.Tensor:
|
|
142
|
+
optimizer.zero_grad()
|
|
143
|
+
pred_dist = sdf(querie_batch)
|
|
144
|
+
loss = Loss(pred_dist, gt_batch)
|
|
145
|
+
loss.backward()
|
|
146
|
+
return loss
|
|
147
|
+
|
|
148
|
+
if optimizer_name == "adam":
|
|
149
|
+
loss = closure()
|
|
150
|
+
optimizer.step()
|
|
151
|
+
elif optimizer_name == "lbfgs":
|
|
152
|
+
loss = optimizer.step(closure)
|
|
153
|
+
loss_num = loss.detach().item()
|
|
154
|
+
pbar.set_postfix({"loss": f"{loss_num:.5f}"})
|
|
155
|
+
loss_history.append(loss_num)
|
|
156
|
+
|
|
157
|
+
if loss_plot_path is not None:
|
|
158
|
+
plot_reconstruction_loss(
|
|
159
|
+
loss_history, iters_per_epoch=len(dataloader), filename=loss_plot_path
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
params = list(sdf.parameters())
|
|
163
|
+
print(params)
|
|
164
|
+
|
|
165
|
+
return params
|