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.
Files changed (165) hide show
  1. DeepSDFStruct/SDF.py +1734 -0
  2. DeepSDFStruct/__init__.py +66 -0
  3. DeepSDFStruct/deep_sdf/__init__.py +69 -0
  4. DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
  5. DeepSDFStruct/deep_sdf/data.py +258 -0
  6. DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
  7. DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
  8. DeepSDFStruct/deep_sdf/models.py +197 -0
  9. DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
  10. DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
  11. DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
  12. DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
  13. DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
  14. DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
  15. DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
  16. DeepSDFStruct/deep_sdf/plotting.py +143 -0
  17. DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
  18. DeepSDFStruct/deep_sdf/training.py +840 -0
  19. DeepSDFStruct/deep_sdf/workspace.py +363 -0
  20. DeepSDFStruct/design_of_experiments.py +235 -0
  21. DeepSDFStruct/flexicubes/__init__.py +15 -0
  22. DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
  23. DeepSDFStruct/flexicubes/tables.py +2338 -0
  24. DeepSDFStruct/flexisquares/__init__.py +15 -0
  25. DeepSDFStruct/flexisquares/flexisquares.py +967 -0
  26. DeepSDFStruct/flexisquares/tables.py +63 -0
  27. DeepSDFStruct/lattice_structure.py +254 -0
  28. DeepSDFStruct/local_shapes.py +226 -0
  29. DeepSDFStruct/mesh.py +1184 -0
  30. DeepSDFStruct/optimization.py +357 -0
  31. DeepSDFStruct/parametrization.py +215 -0
  32. DeepSDFStruct/pretrained_models.py +128 -0
  33. DeepSDFStruct/sampling.py +670 -0
  34. DeepSDFStruct/sdf_operations.py +990 -0
  35. DeepSDFStruct/sdf_primitives.py +1416 -0
  36. DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
  37. DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
  38. DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
  39. DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
  40. DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
  41. DeepSDFStruct/torch_spline.py +446 -0
  42. DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
  43. DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
  44. DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
  45. DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
  46. DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
  47. DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
  48. DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
  49. DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
  50. DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
  51. DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
  52. DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
  53. DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
  54. DeepSDFStruct/trained_models/primitives/specs.json +46 -0
  55. DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
  56. DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
  57. DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
  58. DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
  59. DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
  60. DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
  61. DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
  62. DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
  63. DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
  64. DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
  65. DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
  66. DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
  67. DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
  68. DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
  69. DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
  70. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
  71. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
  72. DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
  73. DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
  74. DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
  75. DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
  76. DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
  77. DeepSDFStruct/utils.py +96 -0
  78. DeepSDFStruct/visualization/latent_viewer.py +211 -0
  79. DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
  80. benchmarks/generate_sdf_showcase.py +622 -0
  81. benchmarks/sdf_from_mesh_benchmark.py +52 -0
  82. benchmarks/sdf_from_mesh_comparison.md +33 -0
  83. benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
  84. benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
  85. benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
  86. benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
  87. benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
  88. benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
  89. benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
  90. benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
  91. benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
  92. benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
  93. benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
  94. benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
  95. benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
  96. benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
  97. benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
  98. benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
  99. benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
  100. benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
  101. benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
  102. benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
  103. benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
  104. benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
  105. benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
  106. benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
  107. benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
  108. benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
  109. benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
  110. benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
  111. benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
  112. benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
  113. benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
  114. benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
  115. benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
  116. benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
  117. benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
  118. deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
  119. deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
  120. deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
  121. deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
  122. deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
  123. deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
  124. deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
  125. deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
  126. docs/Makefile +20 -0
  127. docs/api_reference.rst +6 -0
  128. docs/conf.py +60 -0
  129. docs/index.rst +136 -0
  130. docs/make.bat +35 -0
  131. docs/qr_code_github.png +0 -0
  132. docs/qr_code_paper.png +0 -0
  133. docs/readme_images/example_output_01.png +0 -0
  134. docs/readme_images/example_output_02.png +0 -0
  135. docs/readme_images/example_output_03.png +0 -0
  136. docs/readme_images/example_output_04.png +0 -0
  137. tests/data/chairs/1005.obj +282 -0
  138. tests/data/chairs/1024.obj +222 -0
  139. tests/data/chairs/README.md +1 -0
  140. tests/data/circular_balls_test_case.stl +0 -0
  141. tests/data/cone.stl +1794 -0
  142. tests/data/example_disconnectd_mesh.inp +1784 -0
  143. tests/data/example_line_mesh.vtk +0 -0
  144. tests/data/stanford_bunny.stl +0 -0
  145. tests/data/sweep_test_case.stl +0 -0
  146. tests/test_DOE.py +49 -0
  147. tests/test_elongate_sdf.py +195 -0
  148. tests/test_flexisquares.py +187 -0
  149. tests/test_generate_dataset.py +76 -0
  150. tests/test_lattice_evaluation.py +137 -0
  151. tests/test_mesh_export.py +137 -0
  152. tests/test_mesh_functions.py +49 -0
  153. tests/test_networks.py +71 -0
  154. tests/test_pretrained_models.py +40 -0
  155. tests/test_reconstruction.py +101 -0
  156. tests/test_sdf_from_mesh.py +52 -0
  157. tests/test_sdf_functions.py +117 -0
  158. tests/test_sdf_primitives.py +668 -0
  159. tests/test_sdf_trimesh_comparison.py +482 -0
  160. tests/test_splinepy_unitcells.py +64 -0
  161. tests/test_structural_optimization.py +178 -0
  162. tests/test_torch_spline.py +216 -0
  163. tests/test_train_model.py +110 -0
  164. tests/test_training_data_ids.py +41 -0
  165. 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