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,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment Workspace Management
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
This module provides utilities for managing DeepSDF experiment workspaces,
|
|
6
|
+
including directory structures, file naming conventions, and model loading/saving.
|
|
7
|
+
|
|
8
|
+
Constants
|
|
9
|
+
---------
|
|
10
|
+
The module defines standard subdirectory and file names for organizing
|
|
11
|
+
experiment artifacts:
|
|
12
|
+
- Model parameters and checkpoints
|
|
13
|
+
- Optimizer states
|
|
14
|
+
- Latent code vectors
|
|
15
|
+
- Training logs and plots
|
|
16
|
+
- Reconstructions and evaluations
|
|
17
|
+
- Dataset samples and normalization parameters
|
|
18
|
+
|
|
19
|
+
Architecture Registry
|
|
20
|
+
--------------------
|
|
21
|
+
ARCHITECTURES: dict
|
|
22
|
+
Maps architecture names to decoder classes, enabling dynamic model
|
|
23
|
+
instantiation from configuration files.
|
|
24
|
+
|
|
25
|
+
Functions
|
|
26
|
+
---------
|
|
27
|
+
|
|
28
|
+
load_experiment_specifications
|
|
29
|
+
Load experiment configuration from specs.json file.
|
|
30
|
+
|
|
31
|
+
load_trained_model
|
|
32
|
+
Load a trained decoder network from checkpoint.
|
|
33
|
+
|
|
34
|
+
load_latent_vectors
|
|
35
|
+
Load learned latent codes from checkpoint.
|
|
36
|
+
|
|
37
|
+
create_experiment_directory
|
|
38
|
+
Initialize directory structure for a new experiment.
|
|
39
|
+
|
|
40
|
+
The workspace utilities ensure consistent organization across experiments
|
|
41
|
+
and simplify model loading for inference and continued training.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
#!/usr/bin/env python3
|
|
45
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
46
|
+
|
|
47
|
+
import json
|
|
48
|
+
import os
|
|
49
|
+
import pathlib
|
|
50
|
+
import torch
|
|
51
|
+
from typing import TypedDict
|
|
52
|
+
|
|
53
|
+
from .networks.analytic_round_cross import RoundCrossDecoder
|
|
54
|
+
from .networks.deep_sdf_decoder import DeepSDFDecoder
|
|
55
|
+
from .networks.hierarchical_deep_sdf_decoder import HierachicalDeepSDFDecoder
|
|
56
|
+
from .networks.resnet_positional_sdf_decoder import ResNetPositionalDeepSDFDecoder
|
|
57
|
+
from .networks.hierarchical_positional_sdf_decoder import (
|
|
58
|
+
HierachicalPositionalDeepSDFDecoder,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
screenshots_subdir = "Screenshots"
|
|
62
|
+
model_params_subdir = "ModelParameters"
|
|
63
|
+
optimizer_params_subdir = "OptimizerParameters"
|
|
64
|
+
latent_codes_subdir = "LatentCodes"
|
|
65
|
+
latent_code_data_map_filename = "latent_code_data_map.json"
|
|
66
|
+
logs_filename = "Logs.pth"
|
|
67
|
+
logplot_filename = "Logs.png"
|
|
68
|
+
reconstructions_subdir = "Reconstructions"
|
|
69
|
+
reconstruction_meshes_subdir = "Meshes"
|
|
70
|
+
reconstruction_codes_subdir = "Codes"
|
|
71
|
+
specifications_filename = "specs.json"
|
|
72
|
+
data_source_map_filename = ".datasources.json"
|
|
73
|
+
evaluation_subdir = "Evaluation"
|
|
74
|
+
sdf_samples_subdir = "SdfSamples"
|
|
75
|
+
surface_samples_subdir = "SurfaceSamples"
|
|
76
|
+
normalization_param_subdir = "NormalizationParameters"
|
|
77
|
+
training_meshes_subdir = "TrainingMeshes"
|
|
78
|
+
experiment_summary_name = "training_summary.json"
|
|
79
|
+
|
|
80
|
+
# Map architecture name to Decoder class
|
|
81
|
+
ARCHITECTURES = {
|
|
82
|
+
"analytic_round_cross": RoundCrossDecoder,
|
|
83
|
+
"deep_sdf_decoder": DeepSDFDecoder,
|
|
84
|
+
"hierarchical_deep_sdf_decoder": HierachicalDeepSDFDecoder,
|
|
85
|
+
"resnet_positional_deep_sdf_decoder": ResNetPositionalDeepSDFDecoder,
|
|
86
|
+
"hierarchical_positional_deep_sdf_decoder": HierachicalPositionalDeepSDFDecoder,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_experiment_specifications(experiment_directory):
|
|
91
|
+
|
|
92
|
+
filename = os.path.join(experiment_directory, specifications_filename)
|
|
93
|
+
|
|
94
|
+
if not os.path.isfile(filename):
|
|
95
|
+
raise Exception(
|
|
96
|
+
f"The experiment directory ({experiment_directory}) does not include specifications file "
|
|
97
|
+
+ '"specs.json"'
|
|
98
|
+
)
|
|
99
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
100
|
+
return json.load(f)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def load_latent_vectors(experiment_directory, checkpoint, device):
|
|
104
|
+
|
|
105
|
+
filename = os.path.join(
|
|
106
|
+
experiment_directory, latent_codes_subdir, checkpoint + ".pth"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if not os.path.isfile(filename):
|
|
110
|
+
raise Exception(
|
|
111
|
+
f"The experiment directory ({experiment_directory}) does not include a latent code file"
|
|
112
|
+
+ f" for checkpoint '{checkpoint}'"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
data = torch.load(filename, map_location=device, weights_only=True)
|
|
116
|
+
|
|
117
|
+
if isinstance(data["latent_codes"], torch.Tensor):
|
|
118
|
+
|
|
119
|
+
num_vecs = data["latent_codes"].size()[0]
|
|
120
|
+
|
|
121
|
+
lat_vecs = []
|
|
122
|
+
for i in range(num_vecs):
|
|
123
|
+
lat_vecs.append(data["latent_codes"][i])
|
|
124
|
+
|
|
125
|
+
return lat_vecs
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
|
|
129
|
+
num_embeddings, embedding_dim = data["latent_codes"]["weight"].shape
|
|
130
|
+
|
|
131
|
+
lat_vecs = torch.nn.Embedding(num_embeddings, embedding_dim, device=device)
|
|
132
|
+
|
|
133
|
+
lat_vecs.load_state_dict(data["latent_codes"])
|
|
134
|
+
|
|
135
|
+
return lat_vecs.weight.data.detach()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def load_model_parameters(
|
|
139
|
+
experiment_directory, checkpoint, decoder: torch.nn.Module, device
|
|
140
|
+
):
|
|
141
|
+
|
|
142
|
+
filename = os.path.join(
|
|
143
|
+
experiment_directory, model_params_subdir, checkpoint + ".pth"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if not os.path.isfile(filename):
|
|
147
|
+
raise Exception('model state dict "{}" does not exist'.format(filename))
|
|
148
|
+
|
|
149
|
+
data = torch.load(filename, map_location=device, weights_only=True)
|
|
150
|
+
|
|
151
|
+
decoder.load_state_dict(data["model_state_dict"], strict=False)
|
|
152
|
+
|
|
153
|
+
return data["epoch"]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def load_optimizer(
|
|
157
|
+
experiment_directory, checkpoint, optimizer: torch.nn.Module, device
|
|
158
|
+
):
|
|
159
|
+
|
|
160
|
+
filename = os.path.join(
|
|
161
|
+
experiment_directory, optimizer_params_subdir, checkpoint + ".pth"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if not os.path.isfile(filename):
|
|
165
|
+
raise Exception(f'optimizer state dict "{filename}" does not exist')
|
|
166
|
+
|
|
167
|
+
data = torch.load(filename, map_location=device, weights_only=True)
|
|
168
|
+
|
|
169
|
+
optimizer.load_state_dict(data["optimizer_state_dict"])
|
|
170
|
+
|
|
171
|
+
return data["epoch"]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_data_source_map_filename(data_dir):
|
|
175
|
+
return os.path.join(data_dir, data_source_map_filename)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_reconstructed_mesh_filename(
|
|
179
|
+
experiment_dir,
|
|
180
|
+
epoch,
|
|
181
|
+
dataset,
|
|
182
|
+
class_name,
|
|
183
|
+
instance_name,
|
|
184
|
+
create_dir=True,
|
|
185
|
+
filetype="ply",
|
|
186
|
+
):
|
|
187
|
+
fname_raw = os.path.join(
|
|
188
|
+
experiment_dir,
|
|
189
|
+
reconstructions_subdir,
|
|
190
|
+
str(epoch),
|
|
191
|
+
reconstruction_meshes_subdir,
|
|
192
|
+
dataset,
|
|
193
|
+
class_name,
|
|
194
|
+
instance_name + "." + filetype,
|
|
195
|
+
)
|
|
196
|
+
fname = pathlib.Path(fname_raw)
|
|
197
|
+
if not os.path.isdir(fname.parent) and create_dir:
|
|
198
|
+
os.makedirs(fname.parent)
|
|
199
|
+
return fname
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_reconstructed_code_filename(
|
|
203
|
+
experiment_dir, epoch, dataset, class_name, instance_name
|
|
204
|
+
):
|
|
205
|
+
|
|
206
|
+
return os.path.join(
|
|
207
|
+
experiment_dir,
|
|
208
|
+
reconstructions_subdir,
|
|
209
|
+
str(epoch),
|
|
210
|
+
reconstruction_codes_subdir,
|
|
211
|
+
dataset,
|
|
212
|
+
class_name,
|
|
213
|
+
instance_name + ".pth",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_evaluation_dir(experiment_dir, checkpoint, create_if_nonexistent=False):
|
|
218
|
+
|
|
219
|
+
dir = os.path.join(experiment_dir, evaluation_subdir, checkpoint)
|
|
220
|
+
|
|
221
|
+
if create_if_nonexistent and not os.path.isdir(dir):
|
|
222
|
+
os.makedirs(dir)
|
|
223
|
+
|
|
224
|
+
return dir
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_model_params_dir(experiment_dir, create_if_nonexistent=False):
|
|
228
|
+
|
|
229
|
+
dir = os.path.join(experiment_dir, model_params_subdir)
|
|
230
|
+
|
|
231
|
+
if create_if_nonexistent and not os.path.isdir(dir):
|
|
232
|
+
os.makedirs(dir)
|
|
233
|
+
|
|
234
|
+
return dir
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_screenshots_dir(experiment_dir, create_if_nonexistent=True):
|
|
238
|
+
|
|
239
|
+
dir = os.path.join(experiment_dir, screenshots_subdir)
|
|
240
|
+
|
|
241
|
+
if create_if_nonexistent and not os.path.isdir(dir):
|
|
242
|
+
os.makedirs(dir)
|
|
243
|
+
|
|
244
|
+
return dir
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def get_optimizer_params_dir(experiment_dir, create_if_nonexistent=False):
|
|
248
|
+
|
|
249
|
+
dir = os.path.join(experiment_dir, optimizer_params_subdir)
|
|
250
|
+
|
|
251
|
+
if create_if_nonexistent and not os.path.isdir(dir):
|
|
252
|
+
os.makedirs(dir)
|
|
253
|
+
|
|
254
|
+
return dir
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_latent_codes_dir(experiment_dir, create_if_nonexistent=False):
|
|
258
|
+
|
|
259
|
+
dir = os.path.join(experiment_dir, latent_codes_subdir)
|
|
260
|
+
|
|
261
|
+
if create_if_nonexistent and not os.path.isdir(dir):
|
|
262
|
+
os.makedirs(dir)
|
|
263
|
+
|
|
264
|
+
return dir
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_latent_code_data_map_filename(experiment_dir):
|
|
268
|
+
"""Return absolute path for the latent-to-data mapping JSON file."""
|
|
269
|
+
return os.path.join(
|
|
270
|
+
get_latent_codes_dir(experiment_dir, create_if_nonexistent=True),
|
|
271
|
+
latent_code_data_map_filename,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def get_normalization_params_filename(
|
|
276
|
+
data_dir, dataset_name, class_name, instance_name
|
|
277
|
+
):
|
|
278
|
+
return os.path.join(
|
|
279
|
+
data_dir,
|
|
280
|
+
normalization_param_subdir,
|
|
281
|
+
dataset_name,
|
|
282
|
+
class_name,
|
|
283
|
+
instance_name + ".npz",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def init_decoder(experiment_specs, device, data_parallel):
|
|
288
|
+
arch_name = experiment_specs["NetworkArch"]
|
|
289
|
+
if arch_name not in ARCHITECTURES:
|
|
290
|
+
raise ValueError(f"Unknown architecture: {arch_name}")
|
|
291
|
+
|
|
292
|
+
latent_size = experiment_specs["CodeLength"]
|
|
293
|
+
DecoderClass = ARCHITECTURES[arch_name]
|
|
294
|
+
|
|
295
|
+
decoder = DecoderClass(latent_size, **experiment_specs["NetworkSpecs"]).to(device)
|
|
296
|
+
if data_parallel:
|
|
297
|
+
decoder = torch.nn.DataParallel(decoder)
|
|
298
|
+
return decoder
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def load_trained_model(
|
|
302
|
+
experiment_directory: str, checkpoint: str, device=None, data_parallel=False
|
|
303
|
+
):
|
|
304
|
+
specs_filename = os.path.join(experiment_directory, "specs.json")
|
|
305
|
+
with open(specs_filename, "r", encoding="utf-8") as f:
|
|
306
|
+
experiment_specs = json.load(f)
|
|
307
|
+
if device is None:
|
|
308
|
+
device = get_default_device(device)
|
|
309
|
+
|
|
310
|
+
filename = os.path.join(
|
|
311
|
+
experiment_directory, model_params_subdir, checkpoint + ".pth"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if not os.path.isfile(filename):
|
|
315
|
+
raise Exception('model state dict "{}" does not exist'.format(filename))
|
|
316
|
+
|
|
317
|
+
data = torch.load(filename, map_location=device)
|
|
318
|
+
decoder = init_decoder(experiment_specs, device, data_parallel)
|
|
319
|
+
|
|
320
|
+
state_dict = data["model_state_dict"]
|
|
321
|
+
if any(k.startswith("module.") for k in state_dict.keys()):
|
|
322
|
+
stripped_dict = {}
|
|
323
|
+
for k, v in state_dict.items():
|
|
324
|
+
new_key = k.replace("module.", "", 1)
|
|
325
|
+
stripped_dict[new_key] = v
|
|
326
|
+
state_dict = stripped_dict
|
|
327
|
+
|
|
328
|
+
decoder.load_state_dict(state_dict, strict=False)
|
|
329
|
+
decoder = decoder.to(device)
|
|
330
|
+
return decoder
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def get_default_device():
|
|
334
|
+
|
|
335
|
+
if torch.cuda.is_available():
|
|
336
|
+
device = torch.device("cuda")
|
|
337
|
+
else:
|
|
338
|
+
device = torch.device("cpu")
|
|
339
|
+
return device
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def print_model_specifications(experiment_directory: str):
|
|
343
|
+
specs = load_experiment_specifications(experiment_directory)
|
|
344
|
+
print("Model Specifications:")
|
|
345
|
+
for key in specs:
|
|
346
|
+
print(f" {key}: {specs[key]}")
|
|
347
|
+
print("\n")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class ExperimentSummary(TypedDict):
|
|
351
|
+
loss: float
|
|
352
|
+
num_epochs: int
|
|
353
|
+
timestamp: str
|
|
354
|
+
host_name: str
|
|
355
|
+
device: str
|
|
356
|
+
training_duration: str
|
|
357
|
+
data_dir: str
|
|
358
|
+
version: str
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def save_experiment_summary(experiment_directory: str, summary: ExperimentSummary):
|
|
362
|
+
with open(os.path.join(experiment_directory, experiment_summary_name), "w") as f:
|
|
363
|
+
json.dump(summary, f, indent=4)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Design of Experiments (DOE) for DeepSDF Training
|
|
3
|
+
================================================
|
|
4
|
+
|
|
5
|
+
This module provides tools for conducting systematic design of experiments
|
|
6
|
+
when training DeepSDF neural network models. It integrates with MLflow for
|
|
7
|
+
experiment tracking and supports automated hyperparameter sweeps.
|
|
8
|
+
|
|
9
|
+
Key Features
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
ExperimentSpecifications
|
|
13
|
+
A flexible dictionary-like class for managing experiment configurations
|
|
14
|
+
with support for:
|
|
15
|
+
- Loading specifications from JSON files
|
|
16
|
+
- Recursive updates for nested parameter structures
|
|
17
|
+
- Deep copying to prevent unintended modifications
|
|
18
|
+
- Flattening nested configurations for logging
|
|
19
|
+
- Saving configurations for reproducibility
|
|
20
|
+
|
|
21
|
+
MLflow Integration
|
|
22
|
+
Automatic tracking of:
|
|
23
|
+
- Training metrics (loss curves, validation scores)
|
|
24
|
+
- Model checkpoints
|
|
25
|
+
- Hyperparameters and configurations
|
|
26
|
+
- Dataset information
|
|
27
|
+
- Experiment metadata
|
|
28
|
+
|
|
29
|
+
The module simplifies the process of running large-scale hyperparameter
|
|
30
|
+
searches and ablation studies for DeepSDF model training, with built-in
|
|
31
|
+
versioning and reproducibility features.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
Run an experiment with custom specifications::
|
|
36
|
+
|
|
37
|
+
from DeepSDFStruct.design_of_experiments import ExperimentSpecifications
|
|
38
|
+
|
|
39
|
+
# Load base configuration
|
|
40
|
+
specs = ExperimentSpecifications('config.json')
|
|
41
|
+
|
|
42
|
+
# Update hyperparameters
|
|
43
|
+
specs.update({
|
|
44
|
+
'learning_rate': 0.001,
|
|
45
|
+
'batch_size': 32,
|
|
46
|
+
'num_epochs': 100
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
# Run training with tracking
|
|
50
|
+
# train_with_specs(specs)
|
|
51
|
+
|
|
52
|
+
Create and modify experiment configurations::
|
|
53
|
+
|
|
54
|
+
specs = ExperimentSpecifications({
|
|
55
|
+
'model': {
|
|
56
|
+
'layers': [512, 512, 512],
|
|
57
|
+
'activation': 'relu'
|
|
58
|
+
},
|
|
59
|
+
'training': {
|
|
60
|
+
'lr': 0.0005
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Flatten for logging
|
|
65
|
+
flat_params = specs.flatten()
|
|
66
|
+
print(flat_params) # {'model.layers': [...], 'model.activation': 'relu', ...}
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
import os
|
|
70
|
+
import pathlib
|
|
71
|
+
import json
|
|
72
|
+
import mlflow
|
|
73
|
+
from urllib.parse import urlparse
|
|
74
|
+
from DeepSDFStruct.deep_sdf.training import train_deep_sdf
|
|
75
|
+
import copy
|
|
76
|
+
from typing import Dict, Any, Union
|
|
77
|
+
|
|
78
|
+
package_path = pathlib.Path(__file__).parent
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ExperimentSpecifications(dict):
|
|
82
|
+
"""
|
|
83
|
+
A dictionary-like class to hold and update experiment specifications.
|
|
84
|
+
Supports recursive updates for nested dictionaries.
|
|
85
|
+
Can be initialized from a dictionary or loaded from a file (JSON/YAML).
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, specs: Union[Dict[str, Any], str, None] = None):
|
|
89
|
+
"""
|
|
90
|
+
Initialize the ExperimentSpecifications.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
specs: A dictionary of specifications, a file path (JSON/YAML),
|
|
94
|
+
or None for an empty specification set.
|
|
95
|
+
"""
|
|
96
|
+
if isinstance(specs, str):
|
|
97
|
+
loaded_specs = self._load_from_file(specs)
|
|
98
|
+
super().__init__(copy.deepcopy(loaded_specs))
|
|
99
|
+
else:
|
|
100
|
+
super().__init__(copy.deepcopy(specs) if specs else {})
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _load_from_file(filename: str) -> Dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Load specifications from a JSON or YAML file.
|
|
106
|
+
"""
|
|
107
|
+
with open(filename, "r") as f:
|
|
108
|
+
if filename.endswith(".json"):
|
|
109
|
+
return json.load(f)
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError("Unsupported file format. Use .json or .yaml/.yml")
|
|
112
|
+
|
|
113
|
+
def save(self, filename: str) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Save current specifications to a JSON or YAML file.
|
|
116
|
+
"""
|
|
117
|
+
with open(filename, "w") as f:
|
|
118
|
+
if filename.endswith(".json"):
|
|
119
|
+
json.dump(self, f, indent=4)
|
|
120
|
+
|
|
121
|
+
def update(self, updates: Dict[str, Any]) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Recursively update the experiment specifications with new values.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def _recursive_update(d, u):
|
|
127
|
+
for k, v in u.items():
|
|
128
|
+
if isinstance(v, dict) and isinstance(d.get(k), dict):
|
|
129
|
+
_recursive_update(d[k], v)
|
|
130
|
+
else:
|
|
131
|
+
d[k] = v
|
|
132
|
+
|
|
133
|
+
_recursive_update(self, updates)
|
|
134
|
+
|
|
135
|
+
def copy(self):
|
|
136
|
+
"""Return a deep copy of the specifications."""
|
|
137
|
+
return ExperimentSpecifications(copy.deepcopy(self))
|
|
138
|
+
|
|
139
|
+
def flatten(self, parent_key: str = "", sep: str = ".") -> Dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
Flatten nested dictionary (including lists of dicts) into a single-level dict
|
|
142
|
+
with dot-separated keys.
|
|
143
|
+
"""
|
|
144
|
+
items = {}
|
|
145
|
+
for k, v in self.items():
|
|
146
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
|
147
|
+
|
|
148
|
+
if isinstance(v, dict):
|
|
149
|
+
items.update(
|
|
150
|
+
ExperimentSpecifications(v).flatten(parent_key=new_key, sep=sep)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
elif isinstance(v, list):
|
|
154
|
+
# Check if the list contains dictionaries
|
|
155
|
+
if all(isinstance(i, dict) for i in v):
|
|
156
|
+
for idx, elem in enumerate(v):
|
|
157
|
+
items.update(
|
|
158
|
+
ExperimentSpecifications(elem).flatten(
|
|
159
|
+
parent_key=f"{new_key}.{idx}", sep=sep
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
# Just convert the list/tuple to a string for MLflow
|
|
164
|
+
items[new_key] = str(v)
|
|
165
|
+
|
|
166
|
+
else:
|
|
167
|
+
items[new_key] = v
|
|
168
|
+
|
|
169
|
+
return items
|
|
170
|
+
|
|
171
|
+
def __repr__(self):
|
|
172
|
+
return f"ExperimentSpecifications({dict(self)})"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def create_experiment(exp_dir, specs):
|
|
176
|
+
"""
|
|
177
|
+
Create a new experiment directory and specs.json file, copying defaults and applying overrides.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
exp_dir (str): Path to new experiment directory.
|
|
181
|
+
specs (dict): Dictionary containing the experiment specifications.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
os.makedirs(exp_dir, exist_ok=True)
|
|
185
|
+
|
|
186
|
+
# Write new specs.json
|
|
187
|
+
specs_path = os.path.join(exp_dir, "specs.json")
|
|
188
|
+
with open(specs_path, "w") as f:
|
|
189
|
+
json.dump(specs, f, indent=4)
|
|
190
|
+
|
|
191
|
+
return specs_path
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def run_experiment(
|
|
195
|
+
exp_name,
|
|
196
|
+
data_dir,
|
|
197
|
+
specs: ExperimentSpecifications,
|
|
198
|
+
batch_split=1,
|
|
199
|
+
run_name=None,
|
|
200
|
+
device="cpu",
|
|
201
|
+
tracking_uri="sqlite:///mlruns.db",
|
|
202
|
+
):
|
|
203
|
+
"""
|
|
204
|
+
Creates an experiment with overrides, trains the model, and logs everything to MLflow.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
exp_name (str): Name of the experiment folder.
|
|
208
|
+
data_dir (str): Path to the dataset.
|
|
209
|
+
specs (dict): Experiment specifications
|
|
210
|
+
device (str): Training device ('cpu' or 'cuda').
|
|
211
|
+
mlflow_experiment_name (str): Name of the MLflow experiment.
|
|
212
|
+
register_model (bool): If True, register the final model in MLflow Model Registry.
|
|
213
|
+
"""
|
|
214
|
+
mlflow.set_tracking_uri(tracking_uri)
|
|
215
|
+
# Start MLflow run
|
|
216
|
+
mlflow.set_experiment(exp_name)
|
|
217
|
+
with mlflow.start_run(run_name=run_name):
|
|
218
|
+
parsed = urlparse(mlflow.get_artifact_uri())
|
|
219
|
+
if parsed.path == "":
|
|
220
|
+
raise NotImplementedError("Remote Tracking URI not implemented yet.")
|
|
221
|
+
else:
|
|
222
|
+
exp_dir = parsed.path
|
|
223
|
+
create_experiment(exp_dir, specs=specs)
|
|
224
|
+
specs_path = os.path.join(exp_dir, "specs.json")
|
|
225
|
+
mlflow.log_artifact(specs_path)
|
|
226
|
+
|
|
227
|
+
mlflow.log_params(specs.flatten())
|
|
228
|
+
|
|
229
|
+
summary = train_deep_sdf(
|
|
230
|
+
exp_dir, data_dir, device=device, batch_split=batch_split
|
|
231
|
+
)
|
|
232
|
+
mlflow.set_tags(dict(summary))
|
|
233
|
+
|
|
234
|
+
mlflow.log_metric("train_loss", summary["loss"])
|
|
235
|
+
return summary["loss"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlexiCubes - Differentiable 3D Mesh Extraction
|
|
3
|
+
==============================================
|
|
4
|
+
|
|
5
|
+
This module implements FlexiCubes, a differentiable 3D variant of the
|
|
6
|
+
Dual Marching Cubes algorithm for extracting meshes from scalar fields.
|
|
7
|
+
|
|
8
|
+
FlexiCubes enables gradient-based optimization of mesh representations
|
|
9
|
+
by making the mesh extraction process fully differentiable. This is
|
|
10
|
+
essential for inverse problems and optimization tasks where the geometry
|
|
11
|
+
must be optimized to satisfy certain constraints or objectives.
|
|
12
|
+
|
|
13
|
+
The module uses precomputed lookup tables to handle all 256 possible
|
|
14
|
+
Marching Cubes configurations efficiently.
|
|
15
|
+
"""
|