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,840 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeepSDF Model Training
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
This module implements the complete training pipeline for DeepSDF neural
|
|
6
|
+
networks. It provides loss functions, learning rate schedules, training
|
|
7
|
+
loops, and checkpoint management.
|
|
8
|
+
|
|
9
|
+
Key Features
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Loss Functions
|
|
13
|
+
- ClampedL1Loss: L1 loss with value clamping for stability
|
|
14
|
+
- Support for custom loss functions
|
|
15
|
+
|
|
16
|
+
Learning Rate Schedules
|
|
17
|
+
- ConstantLearningRateSchedule: Fixed learning rate
|
|
18
|
+
- StepLearningRateSchedule: Step decay schedule
|
|
19
|
+
- WarmupLearningRateSchedule: Warmup followed by decay
|
|
20
|
+
|
|
21
|
+
Training Loop
|
|
22
|
+
- Multi-epoch training with validation
|
|
23
|
+
- Automatic checkpointing and model saving
|
|
24
|
+
- Loss tracking and visualization
|
|
25
|
+
- Support for distributed training
|
|
26
|
+
- Resume from checkpoint capability
|
|
27
|
+
|
|
28
|
+
Experiment Management
|
|
29
|
+
- MLflow integration for experiment tracking
|
|
30
|
+
- Automatic logging of hyperparameters
|
|
31
|
+
- Training curve visualization
|
|
32
|
+
- Model versioning
|
|
33
|
+
|
|
34
|
+
The training process follows the DeepSDF paper methodology with extensions
|
|
35
|
+
for lattice structures and microstructured materials.
|
|
36
|
+
|
|
37
|
+
Examples
|
|
38
|
+
--------
|
|
39
|
+
Train a DeepSDF model::
|
|
40
|
+
|
|
41
|
+
from DeepSDFStruct.deep_sdf.training import train_deep_sdf
|
|
42
|
+
|
|
43
|
+
specs = {
|
|
44
|
+
'NetworkSpecs': {...},
|
|
45
|
+
'TrainSpecs': {
|
|
46
|
+
'NumEpochs': 2000,
|
|
47
|
+
'LearningRateSchedule': {...}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
train_deep_sdf(experiment_dir, specs)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
#!/usr/bin/env python3
|
|
55
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
56
|
+
|
|
57
|
+
import torch
|
|
58
|
+
import torch.utils.data as data_utils
|
|
59
|
+
import signal
|
|
60
|
+
import sys
|
|
61
|
+
import os
|
|
62
|
+
import logging
|
|
63
|
+
import math
|
|
64
|
+
import json
|
|
65
|
+
import time
|
|
66
|
+
import datetime
|
|
67
|
+
import random
|
|
68
|
+
import pathlib
|
|
69
|
+
import socket
|
|
70
|
+
import tqdm
|
|
71
|
+
|
|
72
|
+
import DeepSDFStruct.deep_sdf
|
|
73
|
+
import DeepSDFStruct.deep_sdf.workspace as ws
|
|
74
|
+
import DeepSDFStruct.deep_sdf.data
|
|
75
|
+
from DeepSDFStruct.deep_sdf.models import DeepSDFModel
|
|
76
|
+
from DeepSDFStruct.deep_sdf.plotting import plot_logs
|
|
77
|
+
from DeepSDFStruct.SDF import SDFfromDeepSDF
|
|
78
|
+
from DeepSDFStruct.mesh import create_3D_mesh, export_surface_mesh
|
|
79
|
+
from DeepSDFStruct.deep_sdf.nn_utils import get_loss_function
|
|
80
|
+
from importlib.metadata import version
|
|
81
|
+
import numpy as np
|
|
82
|
+
|
|
83
|
+
logger = logging.getLogger(DeepSDFStruct.__name__)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class LearningRateSchedule:
|
|
87
|
+
def get_learning_rate(self, epoch):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ConstantLearningRateSchedule(LearningRateSchedule):
|
|
92
|
+
def __init__(self, value):
|
|
93
|
+
self.value = value
|
|
94
|
+
|
|
95
|
+
def get_learning_rate(self, epoch):
|
|
96
|
+
return self.value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class StepLearningRateSchedule(LearningRateSchedule):
|
|
100
|
+
def __init__(self, initial, interval, factor):
|
|
101
|
+
self.initial = initial
|
|
102
|
+
self.interval = interval
|
|
103
|
+
self.factor = factor
|
|
104
|
+
|
|
105
|
+
def get_learning_rate(self, epoch):
|
|
106
|
+
|
|
107
|
+
return self.initial * (self.factor ** (epoch // self.interval))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class WarmupLearningRateSchedule(LearningRateSchedule):
|
|
111
|
+
def __init__(self, initial, warmed_up, length):
|
|
112
|
+
self.initial = initial
|
|
113
|
+
self.warmed_up = warmed_up
|
|
114
|
+
self.length = length
|
|
115
|
+
|
|
116
|
+
def get_learning_rate(self, epoch):
|
|
117
|
+
if epoch > self.length:
|
|
118
|
+
return self.warmed_up
|
|
119
|
+
return self.initial + (self.warmed_up - self.initial) * epoch / self.length
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_learning_rate_schedules(specs):
|
|
123
|
+
|
|
124
|
+
schedule_specs = specs["LearningRateSchedule"]
|
|
125
|
+
|
|
126
|
+
schedules = []
|
|
127
|
+
|
|
128
|
+
for schedule_specs in schedule_specs:
|
|
129
|
+
|
|
130
|
+
if schedule_specs["Type"] == "Step":
|
|
131
|
+
schedules.append(
|
|
132
|
+
StepLearningRateSchedule(
|
|
133
|
+
schedule_specs["Initial"],
|
|
134
|
+
schedule_specs["Interval"],
|
|
135
|
+
schedule_specs["Factor"],
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
elif schedule_specs["Type"] == "Warmup":
|
|
139
|
+
schedules.append(
|
|
140
|
+
WarmupLearningRateSchedule(
|
|
141
|
+
schedule_specs["Initial"],
|
|
142
|
+
schedule_specs["Final"],
|
|
143
|
+
schedule_specs["Length"],
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
elif schedule_specs["Type"] == "Constant":
|
|
147
|
+
schedules.append(ConstantLearningRateSchedule(schedule_specs["Value"]))
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
raise Exception(
|
|
151
|
+
'no known learning rate schedule of type "{}"'.format(
|
|
152
|
+
schedule_specs["Type"]
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return schedules
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def save_model(experiment_directory, filename, decoder, epoch):
|
|
160
|
+
|
|
161
|
+
model_params_dir = ws.get_model_params_dir(experiment_directory, True)
|
|
162
|
+
|
|
163
|
+
torch.save(
|
|
164
|
+
{"epoch": epoch, "model_state_dict": decoder.state_dict()},
|
|
165
|
+
os.path.join(model_params_dir, filename),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def save_optimizer(experiment_directory, filename, optimizer, epoch):
|
|
170
|
+
|
|
171
|
+
optimizer_params_dir = ws.get_optimizer_params_dir(experiment_directory, True)
|
|
172
|
+
|
|
173
|
+
torch.save(
|
|
174
|
+
{"epoch": epoch, "optimizer_state_dict": optimizer.state_dict()},
|
|
175
|
+
os.path.join(optimizer_params_dir, filename),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def save_latent_vectors(experiment_directory, filename, latent_vec, epoch):
|
|
180
|
+
|
|
181
|
+
latent_codes_dir = ws.get_latent_codes_dir(experiment_directory, True)
|
|
182
|
+
|
|
183
|
+
all_latents = latent_vec.state_dict()
|
|
184
|
+
|
|
185
|
+
torch.save(
|
|
186
|
+
{"epoch": epoch, "latent_codes": all_latents},
|
|
187
|
+
os.path.join(latent_codes_dir, filename),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def save_latent_code_data_map(experiment_directory, data_source, npz_filenames):
|
|
192
|
+
"""Save mapping between latent indices and source training `.npz` files.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
experiment_directory : str
|
|
197
|
+
Path to the experiment directory where training artifacts are stored.
|
|
198
|
+
data_source : str
|
|
199
|
+
Root directory of the dataset used for training.
|
|
200
|
+
npz_filenames : list[str]
|
|
201
|
+
Relative `.npz` paths in dataset split order. The order corresponds
|
|
202
|
+
directly to latent embedding indices.
|
|
203
|
+
"""
|
|
204
|
+
latent_code_data_map = {
|
|
205
|
+
"data_source": data_source,
|
|
206
|
+
"sdf_samples_subdir": ws.sdf_samples_subdir,
|
|
207
|
+
"latent_codes": [
|
|
208
|
+
{
|
|
209
|
+
"latent_index": latent_idx,
|
|
210
|
+
"relative_npz_filename": npz_filename,
|
|
211
|
+
"npz_filename": os.path.join(
|
|
212
|
+
data_source, ws.sdf_samples_subdir, npz_filename
|
|
213
|
+
),
|
|
214
|
+
}
|
|
215
|
+
for latent_idx, npz_filename in enumerate(npz_filenames)
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
filename = ws.get_latent_code_data_map_filename(experiment_directory)
|
|
219
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
220
|
+
json.dump(latent_code_data_map, f, indent=4)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def save_logs(
|
|
224
|
+
experiment_directory,
|
|
225
|
+
loss_log,
|
|
226
|
+
lr_log,
|
|
227
|
+
timing_log,
|
|
228
|
+
lat_mag_log,
|
|
229
|
+
param_mag_log,
|
|
230
|
+
epoch,
|
|
231
|
+
):
|
|
232
|
+
|
|
233
|
+
torch.save(
|
|
234
|
+
{
|
|
235
|
+
"epoch": epoch,
|
|
236
|
+
"loss": loss_log,
|
|
237
|
+
"learning_rate": lr_log,
|
|
238
|
+
"timing": timing_log,
|
|
239
|
+
"latent_magnitude": lat_mag_log,
|
|
240
|
+
"param_magnitude": param_mag_log,
|
|
241
|
+
},
|
|
242
|
+
os.path.join(experiment_directory, ws.logs_filename),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def load_logs(experiment_directory):
|
|
247
|
+
|
|
248
|
+
full_filename = os.path.join(experiment_directory, ws.logs_filename)
|
|
249
|
+
|
|
250
|
+
if not os.path.isfile(full_filename):
|
|
251
|
+
raise Exception('log file "{}" does not exist'.format(full_filename))
|
|
252
|
+
|
|
253
|
+
data = torch.load(full_filename)
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
data["loss"],
|
|
257
|
+
data["learning_rate"],
|
|
258
|
+
data["timing"],
|
|
259
|
+
data["latent_magnitude"],
|
|
260
|
+
data["param_magnitude"],
|
|
261
|
+
data["epoch"],
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def clip_logs(loss_log, lr_log, timing_log, lat_mag_log, param_mag_log, epoch):
|
|
266
|
+
|
|
267
|
+
iters_per_epoch = len(loss_log) // len(lr_log)
|
|
268
|
+
|
|
269
|
+
loss_log = loss_log[: (iters_per_epoch * epoch)]
|
|
270
|
+
lr_log = lr_log[:epoch]
|
|
271
|
+
timing_log = timing_log[:epoch]
|
|
272
|
+
lat_mag_log = lat_mag_log[:epoch]
|
|
273
|
+
for n in param_mag_log:
|
|
274
|
+
param_mag_log[n] = param_mag_log[n][:epoch]
|
|
275
|
+
|
|
276
|
+
return (loss_log, lr_log, timing_log, lat_mag_log, param_mag_log)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_spec_with_default(specs, key, default):
|
|
280
|
+
try:
|
|
281
|
+
return specs[key]
|
|
282
|
+
except KeyError:
|
|
283
|
+
return default
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def get_mean_latent_vector_magnitude(latent_vectors):
|
|
287
|
+
return torch.mean(torch.norm(latent_vectors.weight.data.detach(), dim=1))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def append_parameter_magnitudes(param_mag_log, model):
|
|
291
|
+
for name, param in model.named_parameters():
|
|
292
|
+
if len(name) > 7 and name[:7] == "module.":
|
|
293
|
+
name = name[7:]
|
|
294
|
+
if name not in param_mag_log.keys():
|
|
295
|
+
param_mag_log[name] = []
|
|
296
|
+
param_mag_log[name].append(param.data.norm().item())
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def train_deep_sdf(
|
|
300
|
+
experiment_directory, data_source, continue_from=None, batch_split=1, device=None
|
|
301
|
+
):
|
|
302
|
+
if device is None:
|
|
303
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
304
|
+
|
|
305
|
+
if device == "cuda":
|
|
306
|
+
device_name = torch.cuda.get_device_name()
|
|
307
|
+
elif device == "cpu":
|
|
308
|
+
device_name = "cpu"
|
|
309
|
+
else:
|
|
310
|
+
raise RuntimeError("Device must be either cpu or cuda")
|
|
311
|
+
|
|
312
|
+
specs = ws.load_experiment_specifications(experiment_directory)
|
|
313
|
+
logger.info(f"Reading experiment configuration from {experiment_directory}")
|
|
314
|
+
logger.info("Experiment description: \n" + specs["Description"])
|
|
315
|
+
|
|
316
|
+
# reconstruction_split_file = specs["ReconstructionSplit"]
|
|
317
|
+
# if os.path.isfile(reconstruction_split_file):
|
|
318
|
+
# with open(reconstruction_split_file, "r") as f:
|
|
319
|
+
# reconstruction_split = json.load(f)
|
|
320
|
+
# else:
|
|
321
|
+
# reconstruction_split = None
|
|
322
|
+
|
|
323
|
+
logger.debug(specs["NetworkSpecs"])
|
|
324
|
+
|
|
325
|
+
latent_size = specs["CodeLength"]
|
|
326
|
+
|
|
327
|
+
checkpoints = list(
|
|
328
|
+
range(
|
|
329
|
+
specs["SnapshotFrequency"],
|
|
330
|
+
specs["NumEpochs"] + 1,
|
|
331
|
+
specs["SnapshotFrequency"],
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
for checkpoint in specs["AdditionalSnapshots"]:
|
|
336
|
+
checkpoints.append(checkpoint)
|
|
337
|
+
checkpoints.sort()
|
|
338
|
+
|
|
339
|
+
lr_schedules = get_learning_rate_schedules(specs)
|
|
340
|
+
|
|
341
|
+
grad_clip = get_spec_with_default(specs, "GradientClipNorm", None)
|
|
342
|
+
if grad_clip is not None:
|
|
343
|
+
logger.debug("clipping gradients to max norm {}".format(grad_clip))
|
|
344
|
+
|
|
345
|
+
def save_latest(epoch):
|
|
346
|
+
|
|
347
|
+
save_model(experiment_directory, "latest.pth", decoder, epoch)
|
|
348
|
+
save_optimizer(experiment_directory, "latest.pth", optimizer_all, epoch)
|
|
349
|
+
save_latent_vectors(experiment_directory, "latest.pth", lat_vecs, epoch)
|
|
350
|
+
|
|
351
|
+
def save_checkpoints(epoch):
|
|
352
|
+
|
|
353
|
+
save_model(experiment_directory, str(epoch) + ".pth", decoder, epoch)
|
|
354
|
+
save_optimizer(experiment_directory, str(epoch) + ".pth", optimizer_all, epoch)
|
|
355
|
+
save_latent_vectors(experiment_directory, str(epoch) + ".pth", lat_vecs, epoch)
|
|
356
|
+
save_logs(
|
|
357
|
+
experiment_directory,
|
|
358
|
+
loss_log,
|
|
359
|
+
lr_log,
|
|
360
|
+
timing_log,
|
|
361
|
+
lat_mag_log,
|
|
362
|
+
param_mag_log,
|
|
363
|
+
epoch,
|
|
364
|
+
)
|
|
365
|
+
plot_logs(
|
|
366
|
+
experiment_directory,
|
|
367
|
+
show_lr=True,
|
|
368
|
+
filename=os.path.join(experiment_directory, ws.logplot_filename),
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def signal_handler(sig, frame):
|
|
372
|
+
logger.info("Stopping early...")
|
|
373
|
+
sys.exit(0)
|
|
374
|
+
|
|
375
|
+
def adjust_learning_rate(lr_schedules, optimizer, epoch):
|
|
376
|
+
|
|
377
|
+
for i, param_group in enumerate(optimizer.param_groups):
|
|
378
|
+
param_group["lr"] = lr_schedules[i].get_learning_rate(epoch)
|
|
379
|
+
|
|
380
|
+
def empirical_stat(latent_vecs, indices):
|
|
381
|
+
lat_mat = torch.zeros(
|
|
382
|
+
0, device=latent_vecs[0].device, dtype=latent_vecs[0].dtype
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
for ind in indices:
|
|
386
|
+
lat_mat = torch.cat([lat_mat, latent_vecs[ind]], 0)
|
|
387
|
+
mean = torch.mean(lat_mat, 0)
|
|
388
|
+
var = torch.var(lat_mat, 0)
|
|
389
|
+
return mean, var
|
|
390
|
+
|
|
391
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
392
|
+
|
|
393
|
+
num_samp_per_scene = specs["SamplesPerScene"]
|
|
394
|
+
scene_per_batch = specs["ScenesPerBatch"]
|
|
395
|
+
clamp_dist = specs["ClampingDistance"]
|
|
396
|
+
minT = -clamp_dist
|
|
397
|
+
maxT = clamp_dist
|
|
398
|
+
enforce_minmax = True
|
|
399
|
+
|
|
400
|
+
code_reg_lambda = get_spec_with_default(specs, "CodeRegularizationLambda", 1e-4)
|
|
401
|
+
|
|
402
|
+
code_bound = get_spec_with_default(specs, "CodeBound", None)
|
|
403
|
+
|
|
404
|
+
if torch.cuda.device_count() > 1:
|
|
405
|
+
data_parallel = True
|
|
406
|
+
else:
|
|
407
|
+
data_parallel = False
|
|
408
|
+
|
|
409
|
+
decoder = ws.init_decoder(specs, device, data_parallel)
|
|
410
|
+
|
|
411
|
+
geom_dimension = decoder.geom_dimension
|
|
412
|
+
host_name = socket.gethostname()
|
|
413
|
+
logger.info(f"training on {host_name} with {device_name}")
|
|
414
|
+
|
|
415
|
+
seed = get_spec_with_default(specs, "seed", 42)
|
|
416
|
+
logger.info(f"Setting random seed to {seed}")
|
|
417
|
+
random.seed(seed)
|
|
418
|
+
np.random.seed(seed)
|
|
419
|
+
torch.manual_seed(seed)
|
|
420
|
+
torch.cuda.manual_seed(seed)
|
|
421
|
+
torch.cuda.manual_seed_all(seed) # if you are using multi-GPU
|
|
422
|
+
torch.backends.cudnn.deterministic = True
|
|
423
|
+
torch.backends.cudnn.benchmark = False
|
|
424
|
+
|
|
425
|
+
num_epochs = specs["NumEpochs"]
|
|
426
|
+
log_frequency = get_spec_with_default(specs, "LogFrequency", 10)
|
|
427
|
+
|
|
428
|
+
train_split_file = pathlib.Path(data_source) / specs["TrainSplit"]
|
|
429
|
+
|
|
430
|
+
with open(train_split_file, "r") as f:
|
|
431
|
+
train_split = json.load(f)
|
|
432
|
+
|
|
433
|
+
sdf_dataset = DeepSDFStruct.deep_sdf.data.SDFSamples(
|
|
434
|
+
data_source,
|
|
435
|
+
train_split,
|
|
436
|
+
num_samp_per_scene,
|
|
437
|
+
load_ram=True,
|
|
438
|
+
geom_dimension=geom_dimension,
|
|
439
|
+
)
|
|
440
|
+
save_latent_code_data_map(experiment_directory, data_source, sdf_dataset.npyfiles)
|
|
441
|
+
|
|
442
|
+
num_data_loader_threads = get_spec_with_default(specs, "DataLoaderThreads", 1)
|
|
443
|
+
logger.debug("loading data with {} threads".format(num_data_loader_threads))
|
|
444
|
+
|
|
445
|
+
# if the batch size is larger than the dataset, we cannot use drop last,
|
|
446
|
+
# otherwise the dataloader does not load any data
|
|
447
|
+
if scene_per_batch > len(sdf_dataset):
|
|
448
|
+
drop_last = False
|
|
449
|
+
else:
|
|
450
|
+
drop_last = True
|
|
451
|
+
|
|
452
|
+
sdf_loader = data_utils.DataLoader(
|
|
453
|
+
sdf_dataset,
|
|
454
|
+
batch_size=scene_per_batch,
|
|
455
|
+
shuffle=True,
|
|
456
|
+
num_workers=num_data_loader_threads,
|
|
457
|
+
drop_last=drop_last,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
logger.debug("torch num_threads: {}".format(torch.get_num_threads()))
|
|
461
|
+
|
|
462
|
+
num_scenes = len(sdf_dataset)
|
|
463
|
+
|
|
464
|
+
logger.info("There are {} scenes".format(num_scenes))
|
|
465
|
+
|
|
466
|
+
logger.debug(decoder)
|
|
467
|
+
if isinstance(latent_size, list):
|
|
468
|
+
latent_size_embedding = torch.tensor(latent_size).sum()
|
|
469
|
+
else:
|
|
470
|
+
latent_size_embedding = latent_size
|
|
471
|
+
lat_vecs = torch.nn.Embedding(
|
|
472
|
+
num_scenes, latent_size_embedding, max_norm=code_bound, device=device
|
|
473
|
+
)
|
|
474
|
+
torch.nn.init.normal_(
|
|
475
|
+
lat_vecs.weight.data,
|
|
476
|
+
0.0,
|
|
477
|
+
get_spec_with_default(specs, "CodeInitStdDev", 1.0)
|
|
478
|
+
/ math.sqrt(latent_size_embedding),
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
logger.debug(
|
|
482
|
+
"initialized with mean magnitude {}".format(
|
|
483
|
+
get_mean_latent_vector_magnitude(lat_vecs)
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
loss_fun_spec = get_spec_with_default(specs, "LossFunction", "clampedL1")
|
|
487
|
+
loss_fun = get_loss_function(loss_fun_spec)
|
|
488
|
+
add_homogeniation = get_spec_with_default(specs, "AddHomogenization", False)
|
|
489
|
+
if add_homogeniation:
|
|
490
|
+
logger.info("adding homogenization loss")
|
|
491
|
+
|
|
492
|
+
optimizer_all = torch.optim.Adam(
|
|
493
|
+
[
|
|
494
|
+
{
|
|
495
|
+
"params": decoder.parameters(),
|
|
496
|
+
"lr": lr_schedules[0].get_learning_rate(0),
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"params": lat_vecs.parameters(),
|
|
500
|
+
"lr": lr_schedules[1].get_learning_rate(0),
|
|
501
|
+
},
|
|
502
|
+
]
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
loss_log = []
|
|
506
|
+
lr_log = []
|
|
507
|
+
lat_mag_log = []
|
|
508
|
+
timing_log = []
|
|
509
|
+
param_mag_log = {}
|
|
510
|
+
|
|
511
|
+
start_epoch = 1
|
|
512
|
+
|
|
513
|
+
if continue_from is not None:
|
|
514
|
+
|
|
515
|
+
logger.info('continuing from "{}"'.format(continue_from))
|
|
516
|
+
|
|
517
|
+
_ = ws.load_latent_vectors(experiment_directory, continue_from, device=device)
|
|
518
|
+
|
|
519
|
+
model_epoch = ws.load_model_parameters(
|
|
520
|
+
experiment_directory, continue_from, decoder, device=device
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
_ = ws.load_optimizer(
|
|
524
|
+
experiment_directory, continue_from, optimizer_all, device=device
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
loss_log, lr_log, timing_log, lat_mag_log, param_mag_log, log_epoch = load_logs(
|
|
528
|
+
experiment_directory
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
if not log_epoch == model_epoch:
|
|
532
|
+
loss_log, lr_log, timing_log, lat_mag_log, param_mag_log = clip_logs(
|
|
533
|
+
loss_log, lr_log, timing_log, lat_mag_log, param_mag_log, model_epoch
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
start_epoch = model_epoch + 1
|
|
537
|
+
|
|
538
|
+
logger.debug("loaded")
|
|
539
|
+
|
|
540
|
+
logger.info("starting from epoch {}".format(start_epoch))
|
|
541
|
+
|
|
542
|
+
logger.info(
|
|
543
|
+
"Number of decoder parameters: {}".format(
|
|
544
|
+
sum(p.data.nelement() for p in decoder.parameters())
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
logger.info(
|
|
548
|
+
"Number of shape code parameters: {} (# codes {}, code dim {})".format(
|
|
549
|
+
lat_vecs.num_embeddings * lat_vecs.embedding_dim,
|
|
550
|
+
lat_vecs.num_embeddings,
|
|
551
|
+
lat_vecs.embedding_dim,
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
start_train = time.time()
|
|
555
|
+
pbar = tqdm.trange(start_epoch, num_epochs + 1, desc="Training", smoothing=0)
|
|
556
|
+
for epoch in pbar:
|
|
557
|
+
epoch_error = 0.0
|
|
558
|
+
start = time.time()
|
|
559
|
+
|
|
560
|
+
decoder.train()
|
|
561
|
+
|
|
562
|
+
adjust_learning_rate(lr_schedules, optimizer_all, epoch)
|
|
563
|
+
|
|
564
|
+
for sdf_data, properties, indices in sdf_loader:
|
|
565
|
+
# Process the input data
|
|
566
|
+
sdf_data = sdf_data.reshape(-1, geom_dimension + 1).to(device)
|
|
567
|
+
properties_expanded = properties.reshape(-1, properties.shape[-1]).to(
|
|
568
|
+
device
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
indices = indices.to(device)
|
|
572
|
+
|
|
573
|
+
num_sdf_samples = sdf_data.shape[0]
|
|
574
|
+
|
|
575
|
+
sdf_data.requires_grad = False
|
|
576
|
+
|
|
577
|
+
xyz = sdf_data[:, 0:geom_dimension]
|
|
578
|
+
sdf_gt = sdf_data[:, geom_dimension].unsqueeze(1)
|
|
579
|
+
|
|
580
|
+
if enforce_minmax:
|
|
581
|
+
sdf_gt = torch.clamp(sdf_gt, minT, maxT)
|
|
582
|
+
|
|
583
|
+
xyz = torch.chunk(xyz, batch_split)
|
|
584
|
+
indices = torch.chunk(
|
|
585
|
+
indices.unsqueeze(-1).repeat(1, num_samp_per_scene).view(-1),
|
|
586
|
+
batch_split,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
sdf_gt = torch.chunk(sdf_gt, batch_split)
|
|
590
|
+
properties_chunked = torch.chunk(properties_expanded, batch_split)
|
|
591
|
+
|
|
592
|
+
batch_loss = 0.0
|
|
593
|
+
batch_reg_loss = 0.0
|
|
594
|
+
batch_hom_loss = 0.0
|
|
595
|
+
optimizer_all.zero_grad()
|
|
596
|
+
|
|
597
|
+
for i in range(batch_split):
|
|
598
|
+
batch_lat_vecs = lat_vecs(indices[i])
|
|
599
|
+
batch_properties = properties_chunked[i]
|
|
600
|
+
|
|
601
|
+
input = torch.cat([batch_lat_vecs, xyz[i]], dim=1)
|
|
602
|
+
|
|
603
|
+
# NN optimization
|
|
604
|
+
pred_sdf = decoder(input)
|
|
605
|
+
|
|
606
|
+
if enforce_minmax:
|
|
607
|
+
pred_sdf = torch.clamp(pred_sdf, minT, maxT)
|
|
608
|
+
|
|
609
|
+
chunk_loss = loss_fun(pred_sdf, sdf_gt[i].to(device))
|
|
610
|
+
|
|
611
|
+
if add_homogeniation:
|
|
612
|
+
pred_properties = decoder.predict_C_homogenized(batch_lat_vecs)
|
|
613
|
+
|
|
614
|
+
hom_loss = torch.nn.functional.mse_loss(
|
|
615
|
+
pred_properties, batch_properties, reduction="mean"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
chunk_loss += hom_loss.to(device)
|
|
619
|
+
batch_hom_loss += hom_loss.item()
|
|
620
|
+
|
|
621
|
+
# standard l2 latent vector loss
|
|
622
|
+
reg_loss = (
|
|
623
|
+
code_reg_lambda
|
|
624
|
+
* min(1, epoch / 100)
|
|
625
|
+
* torch.mean(torch.norm(batch_lat_vecs, dim=1))
|
|
626
|
+
)
|
|
627
|
+
chunk_loss = chunk_loss + reg_loss.to(device)
|
|
628
|
+
batch_reg_loss = batch_reg_loss + reg_loss.to(device)
|
|
629
|
+
|
|
630
|
+
chunk_loss.backward()
|
|
631
|
+
|
|
632
|
+
batch_loss += chunk_loss.item()
|
|
633
|
+
|
|
634
|
+
logger.debug("loss = {}".format(batch_loss))
|
|
635
|
+
|
|
636
|
+
loss_log.append(batch_loss)
|
|
637
|
+
grad_clip = 1.0
|
|
638
|
+
if grad_clip is not None:
|
|
639
|
+
|
|
640
|
+
torch.nn.utils.clip_grad_norm_(decoder.parameters(), grad_clip)
|
|
641
|
+
|
|
642
|
+
optimizer_all.step()
|
|
643
|
+
epoch_error += batch_loss
|
|
644
|
+
|
|
645
|
+
error = epoch_error / len(sdf_loader)
|
|
646
|
+
|
|
647
|
+
end = time.time()
|
|
648
|
+
# logger.info("epoch {}...".format(epoch))
|
|
649
|
+
tot_time = time.time() - start_train
|
|
650
|
+
avg_time_per_epoch = tot_time / (epoch)
|
|
651
|
+
estimated_remaining_time = avg_time_per_epoch * (num_epochs - (epoch))
|
|
652
|
+
time_string = str(datetime.timedelta(seconds=round(estimated_remaining_time)))
|
|
653
|
+
|
|
654
|
+
if epoch == num_epochs:
|
|
655
|
+
total_time = str(datetime.timedelta(seconds=round(tot_time)))
|
|
656
|
+
logging.info(
|
|
657
|
+
f"Finished {epoch} ({epoch}/{num_epochs}) [{epoch/num_epochs*100:.2f}%] after {total_time}"
|
|
658
|
+
)
|
|
659
|
+
else:
|
|
660
|
+
if add_homogeniation:
|
|
661
|
+
pbar.set_postfix(
|
|
662
|
+
{
|
|
663
|
+
"Loss": f"{batch_loss:.4f}",
|
|
664
|
+
"Reg": f"{batch_reg_loss:.4f}",
|
|
665
|
+
"Hom": f"{batch_hom_loss:.4f}",
|
|
666
|
+
}
|
|
667
|
+
)
|
|
668
|
+
else:
|
|
669
|
+
pbar.set_postfix(
|
|
670
|
+
{"Loss": f"{batch_loss:.4f}", "Reg": f"{batch_reg_loss:.4f}"}
|
|
671
|
+
)
|
|
672
|
+
seconds_elapsed = end - start
|
|
673
|
+
timing_log.append(seconds_elapsed)
|
|
674
|
+
|
|
675
|
+
lr_log.append([schedule.get_learning_rate(epoch) for schedule in lr_schedules])
|
|
676
|
+
|
|
677
|
+
lat_mag_log.append(get_mean_latent_vector_magnitude(lat_vecs))
|
|
678
|
+
|
|
679
|
+
append_parameter_magnitudes(param_mag_log, decoder)
|
|
680
|
+
|
|
681
|
+
if epoch in checkpoints:
|
|
682
|
+
save_checkpoints(epoch)
|
|
683
|
+
|
|
684
|
+
if epoch % log_frequency == 0:
|
|
685
|
+
save_latest(epoch)
|
|
686
|
+
save_logs(
|
|
687
|
+
experiment_directory,
|
|
688
|
+
loss_log,
|
|
689
|
+
lr_log,
|
|
690
|
+
timing_log,
|
|
691
|
+
lat_mag_log,
|
|
692
|
+
param_mag_log,
|
|
693
|
+
epoch,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
summary = ws.ExperimentSummary(
|
|
697
|
+
loss=error,
|
|
698
|
+
num_epochs=epoch,
|
|
699
|
+
timestamp=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
700
|
+
host_name=host_name,
|
|
701
|
+
device=str(device),
|
|
702
|
+
training_duration=total_time,
|
|
703
|
+
data_dir=str(data_source),
|
|
704
|
+
version=version("DeepSDFStruct"),
|
|
705
|
+
)
|
|
706
|
+
ws.save_experiment_summary(experiment_directory, summary)
|
|
707
|
+
return summary
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def reconstruct_meshs_from_latent(
|
|
711
|
+
experiment_directory,
|
|
712
|
+
checkpoint="latest",
|
|
713
|
+
max_batch=32,
|
|
714
|
+
filetype="ply",
|
|
715
|
+
device="cpu",
|
|
716
|
+
):
|
|
717
|
+
|
|
718
|
+
decoder = ws.load_trained_model(experiment_directory, checkpoint, device=device)
|
|
719
|
+
latent_vectors = ws.load_latent_vectors(
|
|
720
|
+
experiment_directory, checkpoint, device=device
|
|
721
|
+
)
|
|
722
|
+
decoder.eval()
|
|
723
|
+
deep_sdf_model = DeepSDFModel(decoder, latent_vectors, device=device)
|
|
724
|
+
sdf_from_DeepSDF = SDFfromDeepSDF(deep_sdf_model)
|
|
725
|
+
|
|
726
|
+
for i, latent_in in enumerate(latent_vectors):
|
|
727
|
+
epoch = checkpoint
|
|
728
|
+
dataset = "latent_recon"
|
|
729
|
+
class_name = "all"
|
|
730
|
+
instance_name = f"{i}"
|
|
731
|
+
fname = ws.get_reconstructed_mesh_filename(
|
|
732
|
+
experiment_directory,
|
|
733
|
+
epoch,
|
|
734
|
+
dataset,
|
|
735
|
+
class_name,
|
|
736
|
+
instance_name,
|
|
737
|
+
filetype=filetype,
|
|
738
|
+
)
|
|
739
|
+
if os.path.isfile(fname):
|
|
740
|
+
print(f"Skipping {fname}")
|
|
741
|
+
continue
|
|
742
|
+
print(f"Reconstructing {fname} ({i}/{len(latent_vectors)})")
|
|
743
|
+
sdf_from_DeepSDF.set_latent_vec(latent_in)
|
|
744
|
+
surf_mesh, _ = create_3D_mesh(sdf_from_DeepSDF, 30, mesh_type="surface")
|
|
745
|
+
export_surface_mesh(fname, surf_mesh)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def create_interpolated_meshes_from_latent(
|
|
749
|
+
experiment_directory: str | os.PathLike[str],
|
|
750
|
+
indices: list[int],
|
|
751
|
+
steps: int,
|
|
752
|
+
checkpoint: str = "latest",
|
|
753
|
+
max_batch: int = 32,
|
|
754
|
+
filetype: str = "ply",
|
|
755
|
+
device="cpu",
|
|
756
|
+
) -> None:
|
|
757
|
+
"""
|
|
758
|
+
Interpolate between latent vectors and export reconstructed meshes.
|
|
759
|
+
|
|
760
|
+
This function loads a trained DeepSDF model and its latent vectors, then
|
|
761
|
+
interpolates between consecutive latent codes specified in `indices`. At
|
|
762
|
+
each interpolation step, a 3D surface mesh is reconstructed and exported
|
|
763
|
+
to disk in the requested format.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
experiment_directory (str | PathLike): Path to the experiment
|
|
767
|
+
directory containing checkpoints and latent vectors.
|
|
768
|
+
checkpoint (str, optional): Which checkpoint to load. Defaults to
|
|
769
|
+
"latest".
|
|
770
|
+
max_batch (int, optional): Maximum batch size for inference.
|
|
771
|
+
Defaults to 32.
|
|
772
|
+
filetype (str, optional): File extension for exported meshes
|
|
773
|
+
(e.g., "ply", "obj"). Defaults to "ply".
|
|
774
|
+
indices (list[int], optional): Sequence of latent vector indices
|
|
775
|
+
between which interpolation should be performed.
|
|
776
|
+
Defaults to [1, 2, 3, 4, 5, 6, 7, 8].
|
|
777
|
+
steps (int, optional): Number of interpolation steps (including
|
|
778
|
+
endpoints). Defaults to 11.
|
|
779
|
+
|
|
780
|
+
Example:
|
|
781
|
+
>>> create_interpolated_meshes_from_latent(
|
|
782
|
+
... experiment_directory="experiments/run1",
|
|
783
|
+
... [1, 2, 3, 4, 5, 6, 7, 8],
|
|
784
|
+
... 11,
|
|
785
|
+
... checkpoint="latest",
|
|
786
|
+
... max_batch=32,
|
|
787
|
+
... filetype="ply",
|
|
788
|
+
... )
|
|
789
|
+
"""
|
|
790
|
+
decoder = ws.load_trained_model(experiment_directory, checkpoint, device=device)
|
|
791
|
+
latent_vectors = ws.load_latent_vectors(
|
|
792
|
+
experiment_directory, checkpoint, device=device
|
|
793
|
+
)
|
|
794
|
+
decoder.eval()
|
|
795
|
+
deep_sdf_model = DeepSDFModel(decoder, latent_vectors, device=device)
|
|
796
|
+
sdf_from_DeepSDF = SDFfromDeepSDF(deep_sdf_model)
|
|
797
|
+
# interpolate between two latents
|
|
798
|
+
start = time.time()
|
|
799
|
+
num_samples = (len(indices) - 1) * steps
|
|
800
|
+
i_sample = 1
|
|
801
|
+
for i_latent, lat in enumerate(indices[:-1]):
|
|
802
|
+
index1 = indices[i_latent]
|
|
803
|
+
index2 = indices[i_latent + 1]
|
|
804
|
+
latent1 = latent_vectors[index1]
|
|
805
|
+
latent2 = latent_vectors[index2]
|
|
806
|
+
|
|
807
|
+
for i in range(steps):
|
|
808
|
+
latent_in = latent1 + (latent2 - latent1) * i / (steps - 1)
|
|
809
|
+
epoch = checkpoint
|
|
810
|
+
dataset = "latent_recon"
|
|
811
|
+
class_name = "interpolation"
|
|
812
|
+
instance_name = f"interpolate_{index1}_{index2}_{i}"
|
|
813
|
+
fname = ws.get_reconstructed_mesh_filename(
|
|
814
|
+
experiment_directory,
|
|
815
|
+
epoch,
|
|
816
|
+
dataset,
|
|
817
|
+
class_name,
|
|
818
|
+
instance_name,
|
|
819
|
+
filetype=filetype,
|
|
820
|
+
)
|
|
821
|
+
if os.path.isfile(fname):
|
|
822
|
+
print(f"Skipping {fname}")
|
|
823
|
+
continue
|
|
824
|
+
print(f"Reconstructing {fname} ({i}/{len(latent_vectors)})")
|
|
825
|
+
sdf_from_DeepSDF.set_latent_vec(latent_in)
|
|
826
|
+
surf_mesh, _ = create_3D_mesh(sdf_from_DeepSDF, 30, mesh_type="surface")
|
|
827
|
+
export_surface_mesh(fname, surf_mesh)
|
|
828
|
+
|
|
829
|
+
# end = time.time()
|
|
830
|
+
# logger.info("epoch {}...".format(epoch))
|
|
831
|
+
tot_time = time.time() - start
|
|
832
|
+
avg_time_per_sample = tot_time / (i_sample)
|
|
833
|
+
estimated_remaining_time = avg_time_per_sample * (num_samples - (i_sample))
|
|
834
|
+
time_string = str(
|
|
835
|
+
datetime.timedelta(seconds=round(estimated_remaining_time))
|
|
836
|
+
)
|
|
837
|
+
print(
|
|
838
|
+
f"Finished {i_sample} ({i_sample}/{num_samples}) [{i_sample/num_samples*100:.2f}%] in {time_string} ({avg_time_per_sample:.2f}s/epoch)"
|
|
839
|
+
)
|
|
840
|
+
i_sample += 1
|