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,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analytic Round Cross Decoder Network
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
This module implements a specialized decoder architecture for round cross
|
|
6
|
+
lattice structures with analytical parametrization. The network incorporates
|
|
7
|
+
geometric priors specific to cross-shaped lattice unit cells.
|
|
8
|
+
|
|
9
|
+
The architecture is similar to the standard DeepSDF decoder but may include
|
|
10
|
+
specialized layers or constraints to better represent the round cross geometry
|
|
11
|
+
family, which is common in mechanical metamaterials.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
#!/usr/bin/env python3
|
|
15
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
16
|
+
|
|
17
|
+
import torch.nn as nn
|
|
18
|
+
import torch
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RoundCrossDecoder(nn.Module):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
latent_size,
|
|
25
|
+
dims,
|
|
26
|
+
geom_dimension,
|
|
27
|
+
dropout=None,
|
|
28
|
+
dropout_prob=0.0,
|
|
29
|
+
norm_layers=(),
|
|
30
|
+
latent_in=(),
|
|
31
|
+
weight_norm=False,
|
|
32
|
+
xyz_in_all=None,
|
|
33
|
+
use_tanh=False,
|
|
34
|
+
latent_dropout=False,
|
|
35
|
+
):
|
|
36
|
+
super(RoundCrossDecoder, self).__init__()
|
|
37
|
+
|
|
38
|
+
def make_sequence():
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
dims = [latent_size + geom_dimension] + dims + [1]
|
|
42
|
+
|
|
43
|
+
self.num_layers = len(dims)
|
|
44
|
+
self.geom_dimension = geom_dimension
|
|
45
|
+
self.norm_layers = norm_layers
|
|
46
|
+
self.latent_in = latent_in
|
|
47
|
+
self.latent_dropout = latent_dropout
|
|
48
|
+
if self.latent_dropout:
|
|
49
|
+
self.lat_dp = nn.Dropout(0.2)
|
|
50
|
+
|
|
51
|
+
self.xyz_in_all = xyz_in_all
|
|
52
|
+
self.weight_norm = weight_norm
|
|
53
|
+
|
|
54
|
+
for layer in range(0, self.num_layers - 1):
|
|
55
|
+
if layer + 1 in latent_in:
|
|
56
|
+
out_dim = dims[layer + 1] - dims[0]
|
|
57
|
+
else:
|
|
58
|
+
out_dim = dims[layer + 1]
|
|
59
|
+
if self.xyz_in_all and layer != self.num_layers - 2:
|
|
60
|
+
out_dim -= geom_dimension
|
|
61
|
+
|
|
62
|
+
if weight_norm and layer in self.norm_layers:
|
|
63
|
+
setattr(
|
|
64
|
+
self,
|
|
65
|
+
"lin" + str(layer),
|
|
66
|
+
nn.utils.parametrizations.weight_norm(
|
|
67
|
+
nn.Linear(dims[layer], out_dim)
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
setattr(self, "lin" + str(layer), nn.Linear(dims[layer], out_dim))
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
(not weight_norm)
|
|
75
|
+
and self.norm_layers is not None
|
|
76
|
+
and layer in self.norm_layers
|
|
77
|
+
):
|
|
78
|
+
setattr(self, "bn" + str(layer), nn.LayerNorm(out_dim))
|
|
79
|
+
|
|
80
|
+
self.use_tanh = use_tanh
|
|
81
|
+
if use_tanh:
|
|
82
|
+
self.tanh = nn.Tanh()
|
|
83
|
+
self.relu = nn.ReLU()
|
|
84
|
+
|
|
85
|
+
self.dropout_prob = dropout_prob
|
|
86
|
+
self.dropout = dropout
|
|
87
|
+
self.th = nn.Tanh()
|
|
88
|
+
|
|
89
|
+
# input: N x (L+3), or N x (L+geom_dimension)
|
|
90
|
+
def forward(self, input):
|
|
91
|
+
xyz = input[:, -self.geom_dimension :]
|
|
92
|
+
r = input[:, 0]
|
|
93
|
+
output = torch.linalg.norm(xyz, dim=1, ord=torch.inf)
|
|
94
|
+
|
|
95
|
+
# add x cylinder
|
|
96
|
+
cylinder = torch.sqrt(xyz[:, 1] ** 2 + xyz[:, 2] ** 2) - r
|
|
97
|
+
output = torch.minimum(output, cylinder)
|
|
98
|
+
# add y cylinder
|
|
99
|
+
cylinder = torch.sqrt(xyz[:, 0] ** 2 + xyz[:, 2] ** 2) - r
|
|
100
|
+
output = torch.minimum(output, cylinder)
|
|
101
|
+
# add z cylinder
|
|
102
|
+
cylinder = torch.sqrt(xyz[:, 0] ** 2 + xyz[:, 1] ** 2) - r
|
|
103
|
+
output = torch.minimum(output, cylinder)
|
|
104
|
+
|
|
105
|
+
return output.reshape(-1, 1)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard DeepSDF Decoder Network
|
|
3
|
+
================================
|
|
4
|
+
|
|
5
|
+
This module implements the standard DeepSDF decoder architecture from
|
|
6
|
+
Park et al. (2019). The network takes a latent code and 3D coordinates
|
|
7
|
+
as input and outputs a signed distance value.
|
|
8
|
+
|
|
9
|
+
Architecture
|
|
10
|
+
------------
|
|
11
|
+
The decoder is a multi-layer perceptron (MLP) with:
|
|
12
|
+
- Concatenation of latent code and spatial coordinates at input
|
|
13
|
+
- Optional skip connections for latent code injection
|
|
14
|
+
- Optional coordinate injection at multiple layers
|
|
15
|
+
- Batch normalization or weight normalization
|
|
16
|
+
- ReLU activations (or tanh for final layer)
|
|
17
|
+
- Dropout for regularization
|
|
18
|
+
|
|
19
|
+
The network learns to map from a latent space to implicit SDF representations,
|
|
20
|
+
enabling compact encoding of complex 3D geometries.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
#!/usr/bin/env python3
|
|
24
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
25
|
+
|
|
26
|
+
import torch.nn as nn
|
|
27
|
+
import torch
|
|
28
|
+
import torch.nn.functional as F
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DeepSDFDecoder(nn.Module):
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
latent_size,
|
|
35
|
+
dims,
|
|
36
|
+
geom_dimension,
|
|
37
|
+
dropout=None,
|
|
38
|
+
dropout_prob=0.0,
|
|
39
|
+
norm_layers=(),
|
|
40
|
+
latent_in=(),
|
|
41
|
+
weight_norm=False,
|
|
42
|
+
xyz_in_all=None,
|
|
43
|
+
use_tanh=False,
|
|
44
|
+
latent_dropout=False,
|
|
45
|
+
homogen_predictor_dims=None,
|
|
46
|
+
):
|
|
47
|
+
super(DeepSDFDecoder, self).__init__()
|
|
48
|
+
|
|
49
|
+
def make_sequence():
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
dims = [latent_size + geom_dimension] + dims + [1]
|
|
53
|
+
|
|
54
|
+
self.num_layers = len(dims)
|
|
55
|
+
self.geom_dimension = geom_dimension
|
|
56
|
+
self.norm_layers = norm_layers
|
|
57
|
+
self.latent_in = latent_in
|
|
58
|
+
self.latent_dropout = latent_dropout
|
|
59
|
+
if self.latent_dropout:
|
|
60
|
+
self.lat_dp = nn.Dropout(0.2)
|
|
61
|
+
|
|
62
|
+
self.xyz_in_all = xyz_in_all
|
|
63
|
+
self.weight_norm = weight_norm
|
|
64
|
+
|
|
65
|
+
for layer in range(0, self.num_layers - 1):
|
|
66
|
+
if layer + 1 in latent_in:
|
|
67
|
+
out_dim = dims[layer + 1] - dims[0]
|
|
68
|
+
else:
|
|
69
|
+
out_dim = dims[layer + 1]
|
|
70
|
+
if self.xyz_in_all and layer != self.num_layers - 2:
|
|
71
|
+
out_dim -= geom_dimension
|
|
72
|
+
|
|
73
|
+
if weight_norm and layer in self.norm_layers:
|
|
74
|
+
setattr(
|
|
75
|
+
self,
|
|
76
|
+
"lin" + str(layer),
|
|
77
|
+
nn.utils.parametrizations.weight_norm(
|
|
78
|
+
nn.Linear(dims[layer], out_dim)
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
setattr(self, "lin" + str(layer), nn.Linear(dims[layer], out_dim))
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
(not weight_norm)
|
|
86
|
+
and self.norm_layers is not None
|
|
87
|
+
and layer in self.norm_layers
|
|
88
|
+
):
|
|
89
|
+
setattr(self, "bn" + str(layer), nn.LayerNorm(out_dim))
|
|
90
|
+
|
|
91
|
+
self.use_tanh = use_tanh
|
|
92
|
+
if use_tanh:
|
|
93
|
+
self.tanh = nn.Tanh()
|
|
94
|
+
self.relu = nn.ReLU()
|
|
95
|
+
|
|
96
|
+
self.dropout_prob = dropout_prob
|
|
97
|
+
self.dropout = dropout
|
|
98
|
+
self.th = nn.Tanh()
|
|
99
|
+
if homogen_predictor_dims is not None:
|
|
100
|
+
hp_dims = [latent_size] + homogen_predictor_dims
|
|
101
|
+
homogen_layers = []
|
|
102
|
+
|
|
103
|
+
for i in range(len(hp_dims) - 1):
|
|
104
|
+
homogen_layers.append(nn.Linear(hp_dims[i], hp_dims[i + 1]))
|
|
105
|
+
|
|
106
|
+
# No activation after final layer
|
|
107
|
+
if i < len(hp_dims) - 2:
|
|
108
|
+
homogen_layers.append(nn.ReLU())
|
|
109
|
+
|
|
110
|
+
self.homogen_network = nn.Sequential(*homogen_layers)
|
|
111
|
+
else:
|
|
112
|
+
self.homogen_network = None
|
|
113
|
+
|
|
114
|
+
# input: N x (L+3), or N x (L+geom_dimension)
|
|
115
|
+
def forward(self, input):
|
|
116
|
+
xyz = input[:, -self.geom_dimension :]
|
|
117
|
+
|
|
118
|
+
if input.shape[1] > self.geom_dimension and self.latent_dropout:
|
|
119
|
+
latent_vecs = input[:, : -self.geom_dimension]
|
|
120
|
+
latent_vecs = F.dropout(latent_vecs, p=0.2, training=self.training)
|
|
121
|
+
x = torch.cat([latent_vecs, xyz], 1)
|
|
122
|
+
else:
|
|
123
|
+
x = input
|
|
124
|
+
|
|
125
|
+
for layer in range(0, self.num_layers - 1):
|
|
126
|
+
lin = getattr(self, "lin" + str(layer))
|
|
127
|
+
if layer in self.latent_in:
|
|
128
|
+
x = torch.cat([x, input], 1)
|
|
129
|
+
elif layer != 0 and self.xyz_in_all:
|
|
130
|
+
x = torch.cat([x, xyz], 1)
|
|
131
|
+
x = lin(x)
|
|
132
|
+
# last layer Tanh
|
|
133
|
+
if layer == self.num_layers - 2 and self.use_tanh:
|
|
134
|
+
x = self.tanh(x)
|
|
135
|
+
if layer < self.num_layers - 2:
|
|
136
|
+
if (
|
|
137
|
+
self.norm_layers is not None
|
|
138
|
+
and layer in self.norm_layers
|
|
139
|
+
and not self.weight_norm
|
|
140
|
+
):
|
|
141
|
+
bn = getattr(self, "bn" + str(layer))
|
|
142
|
+
x = bn(x)
|
|
143
|
+
x = self.relu(x)
|
|
144
|
+
if self.dropout is not None and layer in self.dropout:
|
|
145
|
+
x = F.dropout(x, p=self.dropout_prob, training=self.training)
|
|
146
|
+
|
|
147
|
+
return x
|
|
148
|
+
|
|
149
|
+
def predict_C_homogenized(self, latent_vec):
|
|
150
|
+
if self.homogen_network is not None:
|
|
151
|
+
return self.homogen_network(latent_vec)
|
|
152
|
+
else:
|
|
153
|
+
raise RuntimeError(
|
|
154
|
+
"Cannot predict homogenized elasticity tensor, because homogenization network is not defined"
|
|
155
|
+
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hierarchical DeepSDF Decoder Network
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
This module implements a hierarchical decoder architecture that injects
|
|
6
|
+
latent codes at multiple layers rather than only at the input. This enables
|
|
7
|
+
multi-scale geometric control and better representation of complex features.
|
|
8
|
+
|
|
9
|
+
Architecture
|
|
10
|
+
------------
|
|
11
|
+
Unlike the standard DeepSDF decoder, this architecture:
|
|
12
|
+
- Accepts multiple latent vectors of potentially different sizes
|
|
13
|
+
- Injects each latent at a specified layer depth
|
|
14
|
+
- Enables hierarchical control from coarse to fine details
|
|
15
|
+
- Supports various activation functions (ReLU, GELU, SiLU, etc.)
|
|
16
|
+
|
|
17
|
+
The hierarchical structure is particularly useful for:
|
|
18
|
+
- Multi-resolution geometric features
|
|
19
|
+
- Compositional shape generation
|
|
20
|
+
- Transfer learning across scales
|
|
21
|
+
- Fine-tuning specific detail levels
|
|
22
|
+
|
|
23
|
+
This architecture is inspired by progressive training and multi-scale
|
|
24
|
+
representations in generative models.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
#!/usr/bin/env python3
|
|
28
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
29
|
+
|
|
30
|
+
import torch.nn as nn
|
|
31
|
+
import torch
|
|
32
|
+
import torch.nn.functional as F
|
|
33
|
+
from DeepSDFStruct.deep_sdf.nn_utils import activations
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HierachicalDeepSDFDecoder(nn.Module):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
latent_size,
|
|
40
|
+
dims,
|
|
41
|
+
geom_dimension,
|
|
42
|
+
dropout=None,
|
|
43
|
+
dropout_prob=0.0,
|
|
44
|
+
latent_in=[],
|
|
45
|
+
weight_norm=False,
|
|
46
|
+
norm_layers=[],
|
|
47
|
+
xyz_in_all=None,
|
|
48
|
+
activation_fun="relu",
|
|
49
|
+
latent_dropout=False,
|
|
50
|
+
):
|
|
51
|
+
super(HierachicalDeepSDFDecoder, self).__init__()
|
|
52
|
+
|
|
53
|
+
self.layer_dims = [geom_dimension] + dims + [1]
|
|
54
|
+
assert len(latent_size) == len(
|
|
55
|
+
latent_in
|
|
56
|
+
), "latent shape must match latent in shape"
|
|
57
|
+
# add latent dimensions to inputs
|
|
58
|
+
self.latent_dims = {}
|
|
59
|
+
for lat_size, index in zip(latent_size, latent_in):
|
|
60
|
+
self.layer_dims[index] = self.layer_dims[index] + lat_size
|
|
61
|
+
self.latent_dims[index] = lat_size
|
|
62
|
+
|
|
63
|
+
self.num_layers = len(dims)
|
|
64
|
+
self.geom_dimension = geom_dimension
|
|
65
|
+
self.latent_size = latent_size
|
|
66
|
+
self.latent_in = latent_in
|
|
67
|
+
self.latent_dropout = latent_dropout
|
|
68
|
+
if self.latent_dropout:
|
|
69
|
+
self.lat_dp = nn.Dropout(0.2)
|
|
70
|
+
|
|
71
|
+
self.xyz_in_all = xyz_in_all
|
|
72
|
+
self.weight_norm = weight_norm
|
|
73
|
+
self.norm_layers = norm_layers
|
|
74
|
+
|
|
75
|
+
for layer in range(0, len(self.layer_dims) - 1):
|
|
76
|
+
if layer + 1 in latent_in:
|
|
77
|
+
out_dim = self.layer_dims[layer + 1] - self.latent_dims[layer + 1]
|
|
78
|
+
else:
|
|
79
|
+
out_dim = self.layer_dims[layer + 1]
|
|
80
|
+
|
|
81
|
+
if weight_norm and layer in self.norm_layers:
|
|
82
|
+
setattr(
|
|
83
|
+
self,
|
|
84
|
+
"lin" + str(layer),
|
|
85
|
+
nn.utils.parametrizations.weight_norm(
|
|
86
|
+
nn.Linear(self.layer_dims[layer], out_dim)
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
setattr(
|
|
91
|
+
self, "lin" + str(layer), nn.Linear(self.layer_dims[layer], out_dim)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if activation_fun not in activations:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Unsupported activation function '{activation_fun}'. "
|
|
97
|
+
f"Available options are: {list(activations.keys())}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.activation = activations[activation_fun]
|
|
101
|
+
|
|
102
|
+
self.dropout_prob = dropout_prob
|
|
103
|
+
self.dropout = dropout
|
|
104
|
+
self.th = nn.Tanh()
|
|
105
|
+
|
|
106
|
+
# input: N x (L+3), or N x (L+geom_dimension)
|
|
107
|
+
def forward(self, input):
|
|
108
|
+
xyz = input[:, -self.geom_dimension :]
|
|
109
|
+
latent_vecs = input[:, : -self.geom_dimension]
|
|
110
|
+
latent_chunks = torch.split(latent_vecs, self.latent_size, dim=1)
|
|
111
|
+
|
|
112
|
+
if self.latent_dropout:
|
|
113
|
+
latent_vecs = F.dropout(latent_vecs, p=0.2, training=self.training)
|
|
114
|
+
x = xyz
|
|
115
|
+
|
|
116
|
+
for layer in range(0, len(self.layer_dims) - 1):
|
|
117
|
+
lin = getattr(self, "lin" + str(layer))
|
|
118
|
+
if layer in self.latent_in:
|
|
119
|
+
latent_idx = self.latent_in.index(layer)
|
|
120
|
+
latent_chunk = latent_chunks[latent_idx]
|
|
121
|
+
x = torch.cat([x, latent_chunk], 1)
|
|
122
|
+
# print(f"Latent chunk in layer {layer}: {latent_chunk}")
|
|
123
|
+
x = lin(x)
|
|
124
|
+
|
|
125
|
+
if layer < self.num_layers - 2:
|
|
126
|
+
if (
|
|
127
|
+
self.norm_layers is not None
|
|
128
|
+
and layer in self.norm_layers
|
|
129
|
+
and not self.weight_norm
|
|
130
|
+
):
|
|
131
|
+
bn = getattr(self, "bn" + str(layer))
|
|
132
|
+
x = bn(x)
|
|
133
|
+
if self.dropout is not None and layer in self.dropout:
|
|
134
|
+
x = F.dropout(x, p=self.dropout_prob, training=self.training)
|
|
135
|
+
|
|
136
|
+
if layer <= (self.num_layers - 2):
|
|
137
|
+
x = self.activation(x)
|
|
138
|
+
|
|
139
|
+
return x
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import logging
|
|
4
|
+
import torch.nn.functional as F
|
|
5
|
+
from DeepSDFStruct.deep_sdf.nn_utils import activations
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("DeepSDFStruct")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HierachicalPositionalDeepSDFDecoder(nn.Module):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
latent_size,
|
|
14
|
+
dims,
|
|
15
|
+
geom_dimension,
|
|
16
|
+
dropout=None,
|
|
17
|
+
dropout_prob=0.0,
|
|
18
|
+
latent_in=[],
|
|
19
|
+
weight_norm=False,
|
|
20
|
+
norm_layers=[],
|
|
21
|
+
xyz_in_all=None,
|
|
22
|
+
positional_encoding=False,
|
|
23
|
+
pe_levels=6,
|
|
24
|
+
split_latent=False,
|
|
25
|
+
activation_fun="relu",
|
|
26
|
+
latent_dropout=False,
|
|
27
|
+
use_tanh=None,
|
|
28
|
+
):
|
|
29
|
+
super(HierachicalPositionalDeepSDFDecoder, self).__init__()
|
|
30
|
+
if use_tanh is not None:
|
|
31
|
+
logger.warning("Use tanh is deprecated, use the activation_fun argument")
|
|
32
|
+
|
|
33
|
+
# -----------------------------------------------------
|
|
34
|
+
# Positional Encoding
|
|
35
|
+
# -----------------------------------------------------
|
|
36
|
+
self.use_pe = positional_encoding
|
|
37
|
+
self.pe_levels = pe_levels
|
|
38
|
+
|
|
39
|
+
if positional_encoding:
|
|
40
|
+
# PE increases dimensionality from D -> D * (2*L + 1)
|
|
41
|
+
self.xyz_dim = geom_dimension * (2 * pe_levels + 1)
|
|
42
|
+
else:
|
|
43
|
+
self.xyz_dim = geom_dimension
|
|
44
|
+
|
|
45
|
+
self.geom_dimension = geom_dimension
|
|
46
|
+
|
|
47
|
+
# -----------------------------------------------------
|
|
48
|
+
# Base MLP structure
|
|
49
|
+
# layer_dims[i] = input dimension of layer i
|
|
50
|
+
# -----------------------------------------------------
|
|
51
|
+
self.layer_dims = [
|
|
52
|
+
self.xyz_dim
|
|
53
|
+
] + dims # final layer out_dim is handled separately
|
|
54
|
+
self.num_layers = len(dims)
|
|
55
|
+
|
|
56
|
+
# -----------------------------------------------------
|
|
57
|
+
# Latent injection logic
|
|
58
|
+
# -----------------------------------------------------
|
|
59
|
+
self.split_latent = split_latent
|
|
60
|
+
self.latent_in = latent_in
|
|
61
|
+
self.latent_size = latent_size
|
|
62
|
+
self.latent_dropout = latent_dropout
|
|
63
|
+
|
|
64
|
+
if self.split_latent:
|
|
65
|
+
assert (
|
|
66
|
+
latent_size % len(latent_in) == 0
|
|
67
|
+
), "latent_size must be divisible by number of latent injection layers"
|
|
68
|
+
chunk_size = latent_size // len(latent_in)
|
|
69
|
+
latent_chunk_sizes = [chunk_size] * len(latent_in)
|
|
70
|
+
else:
|
|
71
|
+
latent_chunk_sizes = [latent_size] * len(latent_in)
|
|
72
|
+
|
|
73
|
+
self.latent_dims = {} # map layer → latent_chunk_dim
|
|
74
|
+
for size, layer_idx in zip(latent_chunk_sizes, latent_in):
|
|
75
|
+
self.layer_dims[layer_idx] += size
|
|
76
|
+
self.latent_dims[layer_idx] = size
|
|
77
|
+
|
|
78
|
+
# -----------------------------------------------------
|
|
79
|
+
# XYZ skip connection logic (xyz_in_all)
|
|
80
|
+
# xyz is added BEFORE the linear layer, after activation,
|
|
81
|
+
# but only for layers > 0
|
|
82
|
+
# -----------------------------------------------------
|
|
83
|
+
self.xyz_in_all = xyz_in_all
|
|
84
|
+
if xyz_in_all:
|
|
85
|
+
for layer in range(1, self.num_layers):
|
|
86
|
+
self.layer_dims[layer] += self.xyz_dim
|
|
87
|
+
|
|
88
|
+
# -----------------------------------------------------
|
|
89
|
+
# Linear layer construction
|
|
90
|
+
# -----------------------------------------------------
|
|
91
|
+
self.weight_norm = weight_norm
|
|
92
|
+
self.norm_layers = norm_layers
|
|
93
|
+
|
|
94
|
+
for layer in range(self.num_layers):
|
|
95
|
+
in_dim = self.layer_dims[layer]
|
|
96
|
+
out_dim = dims[layer]
|
|
97
|
+
|
|
98
|
+
linear = nn.Linear(in_dim, out_dim)
|
|
99
|
+
if weight_norm and layer in norm_layers:
|
|
100
|
+
linear = nn.utils.parametrizations.weight_norm(linear)
|
|
101
|
+
|
|
102
|
+
setattr(self, f"lin{layer}", linear)
|
|
103
|
+
|
|
104
|
+
# Output layer (final layer: size dims[-1] → 1)
|
|
105
|
+
self.output_layer = nn.Linear(dims[-1], 1)
|
|
106
|
+
|
|
107
|
+
# -----------------------------------------------------
|
|
108
|
+
# Normalization + Dropout
|
|
109
|
+
# -----------------------------------------------------
|
|
110
|
+
self.dropout_prob = dropout_prob
|
|
111
|
+
self.dropout = dropout
|
|
112
|
+
if dropout is not None:
|
|
113
|
+
self.dropout_layers = set(dropout)
|
|
114
|
+
else:
|
|
115
|
+
self.dropout_layers = set()
|
|
116
|
+
|
|
117
|
+
# BatchNorm for chosen layers
|
|
118
|
+
for layer in norm_layers:
|
|
119
|
+
setattr(self, f"bn{layer}", nn.BatchNorm1d(dims[layer]))
|
|
120
|
+
|
|
121
|
+
# Latent dropout
|
|
122
|
+
if latent_dropout:
|
|
123
|
+
self.lat_dp = nn.Dropout(0.2)
|
|
124
|
+
|
|
125
|
+
# -----------------------------------------------------
|
|
126
|
+
# Activation
|
|
127
|
+
# -----------------------------------------------------
|
|
128
|
+
if activation_fun not in activations:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Unsupported activation function {activation_fun}. "
|
|
131
|
+
f"Choose from: {list(activations.keys())}"
|
|
132
|
+
)
|
|
133
|
+
self.activation = activations[activation_fun]
|
|
134
|
+
|
|
135
|
+
# ----------- Positional Encoding -----------
|
|
136
|
+
def positional_encoding(self, xyz):
|
|
137
|
+
"""
|
|
138
|
+
Applies sinusoidal positional encoding to each of the xyz dims.
|
|
139
|
+
Output shape: [B, 3*(2*L+1)]
|
|
140
|
+
"""
|
|
141
|
+
enc = [xyz]
|
|
142
|
+
for i in range(self.pe_levels):
|
|
143
|
+
freq = 2**i
|
|
144
|
+
enc.append(torch.sin(freq * xyz))
|
|
145
|
+
enc.append(torch.cos(freq * xyz))
|
|
146
|
+
return torch.cat(enc, dim=-1)
|
|
147
|
+
|
|
148
|
+
# input: N x (L+3), or N x (L+geom_dimension)
|
|
149
|
+
def forward(self, input: torch.Tensor):
|
|
150
|
+
xyz = input[:, -self.geom_dimension :]
|
|
151
|
+
latent_vecs = input[:, : -self.geom_dimension]
|
|
152
|
+
if self.latent_dropout:
|
|
153
|
+
latent_vecs = F.dropout(latent_vecs, p=0.2, training=self.training)
|
|
154
|
+
if self.split_latent:
|
|
155
|
+
latent_chunks = torch.split(
|
|
156
|
+
latent_vecs, int(self.latent_size / len(self.latent_in)), dim=1
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
latent_chunks = [latent_vecs for _ in self.latent_in]
|
|
160
|
+
|
|
161
|
+
if self.use_pe:
|
|
162
|
+
xyz = self.positional_encoding(xyz)
|
|
163
|
+
x = xyz
|
|
164
|
+
|
|
165
|
+
for layer in range(0, len(self.layer_dims) - 1):
|
|
166
|
+
lin = getattr(self, "lin" + str(layer))
|
|
167
|
+
if layer in self.latent_in:
|
|
168
|
+
latent_idx = self.latent_in.index(layer)
|
|
169
|
+
latent_chunk = latent_chunks[latent_idx]
|
|
170
|
+
x = torch.cat([x, latent_chunk], 1)
|
|
171
|
+
|
|
172
|
+
if layer != 0 and self.xyz_in_all:
|
|
173
|
+
x = torch.cat([x, xyz])
|
|
174
|
+
x = lin(x)
|
|
175
|
+
|
|
176
|
+
if layer < self.num_layers - 2:
|
|
177
|
+
if (
|
|
178
|
+
self.norm_layers is not None
|
|
179
|
+
and layer in self.norm_layers
|
|
180
|
+
and not self.weight_norm
|
|
181
|
+
):
|
|
182
|
+
bn = getattr(self, "bn" + str(layer))
|
|
183
|
+
x = bn(x)
|
|
184
|
+
if self.dropout is not None and layer in self.dropout:
|
|
185
|
+
x = F.dropout(x, p=self.dropout_prob, training=self.training)
|
|
186
|
+
|
|
187
|
+
if layer <= (self.num_layers - 2):
|
|
188
|
+
x = self.activation(x)
|
|
189
|
+
|
|
190
|
+
return self.output_layer(x)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("DeepSDFStruct")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResNetPositionalDeepSDFDecoder(nn.Module):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
latent_size,
|
|
12
|
+
dims,
|
|
13
|
+
dropout=None,
|
|
14
|
+
dropout_prob=None,
|
|
15
|
+
norm_layers=None,
|
|
16
|
+
latent_in=None,
|
|
17
|
+
xyz_in_all=False,
|
|
18
|
+
latent_dropout=False,
|
|
19
|
+
weight_norm=True,
|
|
20
|
+
use_tanh=None,
|
|
21
|
+
geom_dimension=3,
|
|
22
|
+
activation_fun="tanh",
|
|
23
|
+
positional_encoding=False,
|
|
24
|
+
pe_levels=6, # number of frequency bands if PE enabled
|
|
25
|
+
resnet=False,
|
|
26
|
+
resnet_every=2, # insert residual connection every N layers
|
|
27
|
+
):
|
|
28
|
+
super().__init__()
|
|
29
|
+
if use_tanh is not None:
|
|
30
|
+
logger.warning("Use tanh is deprecated, use the activation_fun argument")
|
|
31
|
+
self.geom_dimension = geom_dimension
|
|
32
|
+
self.use_pe = positional_encoding
|
|
33
|
+
self.pe_levels = pe_levels
|
|
34
|
+
self.use_resnet = resnet
|
|
35
|
+
self.resnet_every = resnet_every
|
|
36
|
+
|
|
37
|
+
# Optional positional encoding increases xyz dimension
|
|
38
|
+
input_geom_dim = geom_dimension
|
|
39
|
+
if positional_encoding:
|
|
40
|
+
input_geom_dim = geom_dimension * (2 * pe_levels + 1)
|
|
41
|
+
|
|
42
|
+
# MLP layer dims
|
|
43
|
+
dims = [latent_size + input_geom_dim] + dims + [1]
|
|
44
|
+
self.num_layers = len(dims) - 1
|
|
45
|
+
|
|
46
|
+
if activation_fun == "tanh":
|
|
47
|
+
self.act = nn.Tanh()
|
|
48
|
+
elif activation_fun == "relu":
|
|
49
|
+
self.act = nn.ReLU()
|
|
50
|
+
elif activation_fun == "leaky_relu":
|
|
51
|
+
self.act = nn.LeakyReLU(0.2)
|
|
52
|
+
elif activation_fun == "silu":
|
|
53
|
+
self.act = nn.SiLU()
|
|
54
|
+
elif activation_fun == "sigmoid":
|
|
55
|
+
self.act = nn.Sigmoid()
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(f"Unknown activation function: {activation_fun}")
|
|
58
|
+
|
|
59
|
+
# Construct layers
|
|
60
|
+
self.layers = nn.ModuleList()
|
|
61
|
+
for i in range(self.num_layers):
|
|
62
|
+
self.layers.append(nn.Linear(dims[i], dims[i + 1]))
|
|
63
|
+
|
|
64
|
+
# ----------- Positional Encoding -----------
|
|
65
|
+
def positional_encoding(self, xyz):
|
|
66
|
+
"""
|
|
67
|
+
Applies sinusoidal positional encoding to each of the xyz dims.
|
|
68
|
+
Output shape: [B, 3*(2*L+1)]
|
|
69
|
+
"""
|
|
70
|
+
enc = [xyz]
|
|
71
|
+
for i in range(self.pe_levels):
|
|
72
|
+
freq = 2**i
|
|
73
|
+
enc.append(torch.sin(freq * xyz))
|
|
74
|
+
enc.append(torch.cos(freq * xyz))
|
|
75
|
+
return torch.cat(enc, dim=-1)
|
|
76
|
+
|
|
77
|
+
# ---------------- Forward ------------------
|
|
78
|
+
def forward(self, input):
|
|
79
|
+
"""
|
|
80
|
+
input: [B, latent + xyz]
|
|
81
|
+
"""
|
|
82
|
+
xyz = input[:, -self.geom_dimension :]
|
|
83
|
+
latent = input[:, : -self.geom_dimension]
|
|
84
|
+
|
|
85
|
+
if self.use_pe:
|
|
86
|
+
xyz = self.positional_encoding(xyz)
|
|
87
|
+
|
|
88
|
+
x = torch.cat([latent, xyz], dim=-1)
|
|
89
|
+
|
|
90
|
+
residual = None
|
|
91
|
+
for i, layer in enumerate(self.layers):
|
|
92
|
+
x_in = x
|
|
93
|
+
x = layer(x)
|
|
94
|
+
if i < self.num_layers - 1:
|
|
95
|
+
x = self.act(x)
|
|
96
|
+
|
|
97
|
+
if self.use_resnet and i % self.resnet_every == 0:
|
|
98
|
+
|
|
99
|
+
if residual is None:
|
|
100
|
+
residual = x_in
|
|
101
|
+
else:
|
|
102
|
+
|
|
103
|
+
if i < self.num_layers - 1:
|
|
104
|
+
if residual.shape == x.shape:
|
|
105
|
+
x = x + residual
|
|
106
|
+
residual = x_in
|
|
107
|
+
|
|
108
|
+
return x
|