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,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeepSDFStruct - Differentiable Framework for 3D Microstructured Materials
|
|
3
|
+
===========================================================================
|
|
4
|
+
|
|
5
|
+
DeepSDFStruct is a comprehensive library for creating, manipulating, and optimizing
|
|
6
|
+
3D microstructured geometries using Signed Distance Functions (SDFs) and spline-based
|
|
7
|
+
lattices. The framework integrates classical geometric representations with deep
|
|
8
|
+
learning approaches, enabling differentiable design and optimization workflows.
|
|
9
|
+
|
|
10
|
+
Key Components
|
|
11
|
+
--------------
|
|
12
|
+
|
|
13
|
+
SDF Representations
|
|
14
|
+
- ``DeepSDFStruct.SDF``: Abstract base class and core SDF utilities
|
|
15
|
+
- ``DeepSDFStruct.sdf_primitives``: Geometric primitives (spheres, cylinders, etc.)
|
|
16
|
+
- ``DeepSDFStruct.lattice_structure``: Periodic lattice microstructures
|
|
17
|
+
|
|
18
|
+
Mesh Operations
|
|
19
|
+
- ``DeepSDFStruct.mesh``: Mesh generation, processing, and export
|
|
20
|
+
- ``DeepSDFStruct.flexicubes``: Advanced dual contouring (3D)
|
|
21
|
+
- ``DeepSDFStruct.flexisquares``: Dual contouring for 2D cross-sections
|
|
22
|
+
|
|
23
|
+
Deep Learning
|
|
24
|
+
- ``DeepSDFStruct.deep_sdf``: Neural network models and training
|
|
25
|
+
- ``DeepSDFStruct.sampling``: Data generation and sampling strategies
|
|
26
|
+
|
|
27
|
+
Optimization
|
|
28
|
+
- ``DeepSDFStruct.optimization``: MMA and gradient-based optimization
|
|
29
|
+
- ``DeepSDFStruct.parametrization``: Spatially-varying parameter functions
|
|
30
|
+
|
|
31
|
+
Utilities
|
|
32
|
+
- ``DeepSDFStruct.torch_spline``: Differentiable B-spline operations
|
|
33
|
+
- ``DeepSDFStruct.plotting``: Visualization tools
|
|
34
|
+
- ``DeepSDFStruct.utils``: General utility functions
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
Create a simple sphere and generate a mesh::
|
|
39
|
+
|
|
40
|
+
from DeepSDFStruct.sdf_primitives import SphereSDF
|
|
41
|
+
from DeepSDFStruct.mesh import create_3D_mesh
|
|
42
|
+
|
|
43
|
+
sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
|
|
44
|
+
mesh = create_3D_mesh(sphere, resolution=64)
|
|
45
|
+
|
|
46
|
+
Create a lattice structure::
|
|
47
|
+
|
|
48
|
+
from DeepSDFStruct.lattice_structure import LatticeSDFStruct
|
|
49
|
+
from DeepSDFStruct.torch_spline import TorchSpline
|
|
50
|
+
|
|
51
|
+
# Define deformation spline and unit cell
|
|
52
|
+
# ... (see documentation for details)
|
|
53
|
+
|
|
54
|
+
lattice = LatticeSDFStruct(
|
|
55
|
+
tiling=[3, 3, 3],
|
|
56
|
+
microtile=unit_cell
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
For comprehensive examples, see the example notebook in the repository.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
import DeepSDFStruct.utils
|
|
63
|
+
|
|
64
|
+
DeepSDFStruct.utils.configure_logging()
|
|
65
|
+
|
|
66
|
+
__author__ = "Michael Kofler"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Deep Learning for Signed Distance Functions (DeepSDF)
|
|
6
|
+
======================================================
|
|
7
|
+
|
|
8
|
+
This submodule implements the DeepSDF approach for learning implicit neural
|
|
9
|
+
representations of 3D geometry. It provides complete workflows for:
|
|
10
|
+
|
|
11
|
+
- Training neural networks to represent geometry as learned SDFs
|
|
12
|
+
- Generating datasets from explicit geometry
|
|
13
|
+
- Reconstructing shapes from latent codes
|
|
14
|
+
- Optimizing latent codes for shape fitting
|
|
15
|
+
|
|
16
|
+
The implementation is based on "DeepSDF: Learning Continuous Signed Distance
|
|
17
|
+
Functions for Shape Representation" (Park et al., CVPR 2019) with extensions
|
|
18
|
+
for microstructured materials and lattice geometries.
|
|
19
|
+
|
|
20
|
+
Key Components
|
|
21
|
+
--------------
|
|
22
|
+
|
|
23
|
+
models.py
|
|
24
|
+
DeepSDFModel class wrapping trained decoder networks and latent codes.
|
|
25
|
+
|
|
26
|
+
training.py
|
|
27
|
+
Complete training pipeline including loss functions, learning rate
|
|
28
|
+
schedules, and training loops.
|
|
29
|
+
|
|
30
|
+
data.py
|
|
31
|
+
Dataset classes for loading and batching SDF samples.
|
|
32
|
+
|
|
33
|
+
reconstruction.py
|
|
34
|
+
Methods for fitting latent codes to target geometries.
|
|
35
|
+
|
|
36
|
+
workspace.py
|
|
37
|
+
Utilities for managing experiments, checkpoints, and results.
|
|
38
|
+
|
|
39
|
+
networks/
|
|
40
|
+
Neural network architectures (decoders, hierarchical models).
|
|
41
|
+
|
|
42
|
+
Typical Workflow
|
|
43
|
+
----------------
|
|
44
|
+
|
|
45
|
+
1. Generate training data from explicit geometries::
|
|
46
|
+
|
|
47
|
+
from DeepSDFStruct.sampling import generate_dataset
|
|
48
|
+
generate_dataset(geometries, output_dir, n_samples=500000)
|
|
49
|
+
|
|
50
|
+
2. Train a DeepSDF model::
|
|
51
|
+
|
|
52
|
+
from DeepSDFStruct.deep_sdf import training
|
|
53
|
+
training.main(specs) # specs define architecture, training params
|
|
54
|
+
|
|
55
|
+
3. Use the trained model::
|
|
56
|
+
|
|
57
|
+
from DeepSDFStruct.SDF import SDFfromDeepSDF
|
|
58
|
+
from DeepSDFStruct.deep_sdf.models import DeepSDFModel
|
|
59
|
+
|
|
60
|
+
model = DeepSDFModel(decoder, latent_vectors, device)
|
|
61
|
+
sdf = SDFfromDeepSDF(model, latent_code=latent_vectors[0])
|
|
62
|
+
|
|
63
|
+
4. Reconstruct new shapes::
|
|
64
|
+
|
|
65
|
+
from DeepSDFStruct.deep_sdf import reconstruction
|
|
66
|
+
latent_code = reconstruction.reconstruct(model, target_sdf)
|
|
67
|
+
|
|
68
|
+
For complete examples, see the example notebook and test files.
|
|
69
|
+
"""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PLY File Screenshot Generator
|
|
3
|
+
=============================
|
|
4
|
+
|
|
5
|
+
This utility script generates screenshots and animated GIFs from PLY mesh files.
|
|
6
|
+
It uses Vedo for 3D rendering with a fixed camera configuration to ensure
|
|
7
|
+
consistent viewpoints across multiple meshes.
|
|
8
|
+
|
|
9
|
+
Usage
|
|
10
|
+
-----
|
|
11
|
+
From command line::
|
|
12
|
+
|
|
13
|
+
python create_screenshots_from_plyfiles.py /path/to/ply/files
|
|
14
|
+
|
|
15
|
+
The script will:
|
|
16
|
+
1. Find all .ply files in the specified folder
|
|
17
|
+
2. Generate a .png screenshot for each mesh
|
|
18
|
+
3. Create an animated GIF combining all screenshots
|
|
19
|
+
|
|
20
|
+
This is useful for:
|
|
21
|
+
- Visualizing reconstruction sequences
|
|
22
|
+
- Creating animations of optimization progress
|
|
23
|
+
- Generating consistent documentation images
|
|
24
|
+
- Batch processing of mesh files
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import vedo
|
|
28
|
+
import pathlib
|
|
29
|
+
from natsort import natsorted
|
|
30
|
+
import imageio.v3 as imageio
|
|
31
|
+
|
|
32
|
+
cam = dict(
|
|
33
|
+
position=(3.51484, 3.27242, 4.06787),
|
|
34
|
+
focal_point=(-3.98290e-3, -2.36815e-3, 1.02887e-3),
|
|
35
|
+
viewup=(-0.308731, 0.853375, -0.420043),
|
|
36
|
+
roll=2.37119,
|
|
37
|
+
distance=6.29647,
|
|
38
|
+
clipping_range=(3.00412, 10.4561),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main(ply_file_folder):
|
|
43
|
+
images = []
|
|
44
|
+
plt = vedo.Plotter(interactive=False)
|
|
45
|
+
for ply_filename in natsorted(pathlib.Path(ply_file_folder).rglob("*.ply")):
|
|
46
|
+
print(f"Processing {ply_filename}")
|
|
47
|
+
mesh = vedo.load(str(ply_filename))
|
|
48
|
+
plt.show(mesh, interactive=False, camera=vedo.camera_from_dict(cam))
|
|
49
|
+
vedo.screenshot(ply_filename.with_suffix(".png").as_posix())
|
|
50
|
+
image = imageio.imread(str(ply_filename.with_suffix(".png")))
|
|
51
|
+
images.append(image)
|
|
52
|
+
plt.clear()
|
|
53
|
+
|
|
54
|
+
plt.close()
|
|
55
|
+
imageio.imwrite(ply_filename.parent / "reconstruction.gif", images, duration=300)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
# argument parser
|
|
60
|
+
import argparse
|
|
61
|
+
|
|
62
|
+
parser = argparse.ArgumentParser(description="Create screenshots from ply files")
|
|
63
|
+
parser.add_argument("ply_file_folder", type=str, help="Folder containing ply files")
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
main(args.ply_file_folder)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeepSDF Data Loading and Preprocessing
|
|
3
|
+
======================================
|
|
4
|
+
|
|
5
|
+
This module provides PyTorch Dataset classes and utilities for loading
|
|
6
|
+
and preprocessing SDF training data. It handles:
|
|
7
|
+
|
|
8
|
+
- Loading sampled SDF values from .npz files
|
|
9
|
+
- Splitting samples into positive and negative regions
|
|
10
|
+
- Batching and shuffling for training
|
|
11
|
+
- Data normalization and clamping
|
|
12
|
+
- Multi-class and multi-dataset support
|
|
13
|
+
|
|
14
|
+
The module is designed to work with the dataset format produced by the
|
|
15
|
+
sampling module, where each geometry is stored as a .npz file containing
|
|
16
|
+
positive and negative SDF samples.
|
|
17
|
+
|
|
18
|
+
Classes
|
|
19
|
+
-------
|
|
20
|
+
SDFSamples
|
|
21
|
+
PyTorch Dataset for loading SDF samples with lazy loading and caching.
|
|
22
|
+
|
|
23
|
+
NoMeshFileError
|
|
24
|
+
Exception raised when a mesh file cannot be found.
|
|
25
|
+
|
|
26
|
+
MultipleMeshFileError
|
|
27
|
+
Exception raised when multiple mesh files exist in a directory.
|
|
28
|
+
|
|
29
|
+
Functions
|
|
30
|
+
---------
|
|
31
|
+
get_instance_filenames
|
|
32
|
+
Retrieve file paths for all instances in a dataset split.
|
|
33
|
+
|
|
34
|
+
unpack_sdf_samples
|
|
35
|
+
Load and preprocess SDF samples from a .npz file.
|
|
36
|
+
|
|
37
|
+
read_sdf_samples_into_ram
|
|
38
|
+
Load entire dataset into RAM for faster training.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
#!/usr/bin/env python3
|
|
42
|
+
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
43
|
+
|
|
44
|
+
import glob
|
|
45
|
+
import logging
|
|
46
|
+
import numpy as np
|
|
47
|
+
import os
|
|
48
|
+
import random
|
|
49
|
+
import torch
|
|
50
|
+
import torch.utils.data
|
|
51
|
+
|
|
52
|
+
import DeepSDFStruct.deep_sdf.workspace as ws
|
|
53
|
+
|
|
54
|
+
logger = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_instance_filenames(data_source, split):
|
|
58
|
+
npzfiles = []
|
|
59
|
+
for dataset in split:
|
|
60
|
+
for class_name in split[dataset]:
|
|
61
|
+
for instance_name in split[dataset][class_name]:
|
|
62
|
+
instance_filename = os.path.join(
|
|
63
|
+
dataset, class_name, instance_name + ".npz"
|
|
64
|
+
)
|
|
65
|
+
if not os.path.isfile(
|
|
66
|
+
os.path.join(data_source, ws.sdf_samples_subdir, instance_filename)
|
|
67
|
+
):
|
|
68
|
+
# raise RuntimeError(
|
|
69
|
+
# 'Requested non-existent file "' + instance_filename + "'"
|
|
70
|
+
# )
|
|
71
|
+
logging.warning(
|
|
72
|
+
"Requested non-existent file '{}'".format(instance_filename)
|
|
73
|
+
)
|
|
74
|
+
npzfiles += [instance_filename]
|
|
75
|
+
return npzfiles
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class NoMeshFileError(RuntimeError):
|
|
79
|
+
"""Raised when a mesh file is not found in a shape directory"""
|
|
80
|
+
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MultipleMeshFileError(RuntimeError):
|
|
85
|
+
""" "Raised when a there a multiple mesh files in a shape directory"""
|
|
86
|
+
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def find_mesh_in_directory(shape_dir):
|
|
91
|
+
mesh_filenames = list(glob.iglob(shape_dir + "/**/*.obj")) + list(
|
|
92
|
+
glob.iglob(shape_dir + "/*.obj")
|
|
93
|
+
)
|
|
94
|
+
if len(mesh_filenames) == 0:
|
|
95
|
+
raise NoMeshFileError()
|
|
96
|
+
elif len(mesh_filenames) > 1:
|
|
97
|
+
raise MultipleMeshFileError()
|
|
98
|
+
return mesh_filenames[0]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# and also convert to single precision
|
|
102
|
+
# usually one function should only contain one functionality, but here I think
|
|
103
|
+
# it makes sense, because the data is loaded at 3 different positions in the code
|
|
104
|
+
def remove_nans(tensor, geom_dimension):
|
|
105
|
+
tensor_nan = torch.isnan(tensor[:, geom_dimension])
|
|
106
|
+
return tensor[~tensor_nan, :].float()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def read_sdf_samples_into_ram(filename):
|
|
110
|
+
npz = np.load(filename)
|
|
111
|
+
pos_tensor = torch.from_numpy(npz["pos.npy"]).float()
|
|
112
|
+
neg_tensor = torch.from_numpy(npz["neg.npy"]).float()
|
|
113
|
+
|
|
114
|
+
return [pos_tensor, neg_tensor]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def unpack_sdf_samples(filename, geom_dimension, subsample=None):
|
|
118
|
+
npz = np.load(filename)
|
|
119
|
+
|
|
120
|
+
pos_tensor = remove_nans(torch.from_numpy(npz["pos.npy"]), geom_dimension)
|
|
121
|
+
neg_tensor = remove_nans(torch.from_numpy(npz["neg.npy"]), geom_dimension)
|
|
122
|
+
|
|
123
|
+
if subsample is None:
|
|
124
|
+
return torch.cat([pos_tensor, neg_tensor], 0)
|
|
125
|
+
|
|
126
|
+
half = int(subsample / 2)
|
|
127
|
+
pos_len = len(pos_tensor)
|
|
128
|
+
neg_len = len(neg_tensor)
|
|
129
|
+
if pos_len < half:
|
|
130
|
+
neg_len = 2 * half - pos_len
|
|
131
|
+
elif neg_len < half:
|
|
132
|
+
pos_len = 2 * half - neg_len
|
|
133
|
+
else:
|
|
134
|
+
pos_len = neg_len = half
|
|
135
|
+
|
|
136
|
+
use_randperm = True
|
|
137
|
+
if use_randperm:
|
|
138
|
+
random_pos = torch.randperm(len(pos_tensor))[:pos_len]
|
|
139
|
+
random_neg = torch.randperm(len(neg_tensor))[:neg_len]
|
|
140
|
+
else:
|
|
141
|
+
# split the sample into half
|
|
142
|
+
random_pos = (torch.rand(half) * pos_tensor.shape[0]).long()
|
|
143
|
+
random_neg = (torch.rand(half) * neg_tensor.shape[0]).long()
|
|
144
|
+
|
|
145
|
+
sample_pos = torch.index_select(pos_tensor, 0, random_pos)
|
|
146
|
+
sample_neg = torch.index_select(neg_tensor, 0, random_neg)
|
|
147
|
+
|
|
148
|
+
samples = torch.cat([sample_pos, sample_neg], 0)
|
|
149
|
+
|
|
150
|
+
# if len(samples) < 50:
|
|
151
|
+
# print("less than half")
|
|
152
|
+
|
|
153
|
+
return samples
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def unpack_sdf_samples_from_ram(data, subsample=None):
|
|
157
|
+
if subsample is None:
|
|
158
|
+
return torch.cat(data, 0)
|
|
159
|
+
pos_tensor = data[0]
|
|
160
|
+
neg_tensor = data[1]
|
|
161
|
+
|
|
162
|
+
# split the sample into half
|
|
163
|
+
half = int(subsample / 2)
|
|
164
|
+
|
|
165
|
+
pos_size = pos_tensor.shape[0]
|
|
166
|
+
neg_size = neg_tensor.shape[0]
|
|
167
|
+
|
|
168
|
+
if pos_size <= half:
|
|
169
|
+
random_pos = (torch.rand(half) * pos_tensor.shape[0]).long()
|
|
170
|
+
sample_pos = torch.index_select(pos_tensor, 0, random_pos)
|
|
171
|
+
else:
|
|
172
|
+
pos_start_ind = random.randint(0, pos_size - half)
|
|
173
|
+
sample_pos = pos_tensor[pos_start_ind : (pos_start_ind + half)]
|
|
174
|
+
|
|
175
|
+
if neg_size <= half:
|
|
176
|
+
random_neg = (torch.rand(half) * neg_tensor.shape[0]).long()
|
|
177
|
+
sample_neg = torch.index_select(neg_tensor, 0, random_neg)
|
|
178
|
+
else:
|
|
179
|
+
neg_start_ind = random.randint(0, neg_size - half)
|
|
180
|
+
sample_neg = neg_tensor[neg_start_ind : (neg_start_ind + half)]
|
|
181
|
+
|
|
182
|
+
samples = torch.cat([sample_pos, sample_neg], 0)
|
|
183
|
+
|
|
184
|
+
return samples
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class SDFSamples(torch.utils.data.Dataset):
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
data_source,
|
|
191
|
+
split,
|
|
192
|
+
subsample,
|
|
193
|
+
geom_dimension,
|
|
194
|
+
load_ram=False,
|
|
195
|
+
print_filename=False,
|
|
196
|
+
num_files=1000000,
|
|
197
|
+
):
|
|
198
|
+
self.subsample = subsample
|
|
199
|
+
self.geom_dimension = geom_dimension
|
|
200
|
+
self.data_source = data_source
|
|
201
|
+
self.npyfiles = get_instance_filenames(data_source, split)
|
|
202
|
+
self.filenames = []
|
|
203
|
+
logger.debug(
|
|
204
|
+
"using "
|
|
205
|
+
+ str(len(self.npyfiles))
|
|
206
|
+
+ " shapes from data source "
|
|
207
|
+
+ data_source
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self.load_ram = load_ram
|
|
211
|
+
|
|
212
|
+
self.loaded_mat_properties = []
|
|
213
|
+
if load_ram:
|
|
214
|
+
self.loaded_data = []
|
|
215
|
+
for f in self.npyfiles:
|
|
216
|
+
filename = os.path.join(self.data_source, ws.sdf_samples_subdir, f)
|
|
217
|
+
npz = np.load(filename)
|
|
218
|
+
pos_tensor = remove_nans(
|
|
219
|
+
torch.from_numpy(npz["pos.npy"]), self.geom_dimension
|
|
220
|
+
)
|
|
221
|
+
neg_tensor = remove_nans(
|
|
222
|
+
torch.from_numpy(npz["neg.npy"]), self.geom_dimension
|
|
223
|
+
)
|
|
224
|
+
self.loaded_data.append(
|
|
225
|
+
[
|
|
226
|
+
pos_tensor[torch.randperm(pos_tensor.shape[0])],
|
|
227
|
+
neg_tensor[torch.randperm(neg_tensor.shape[0])],
|
|
228
|
+
]
|
|
229
|
+
)
|
|
230
|
+
if "C" in npz.keys():
|
|
231
|
+
self.loaded_mat_properties.append(
|
|
232
|
+
torch.from_numpy(npz["C.npy"]).to(torch.float32).reshape(-1)
|
|
233
|
+
)
|
|
234
|
+
self.filenames.append(filename)
|
|
235
|
+
|
|
236
|
+
def __len__(self):
|
|
237
|
+
return len(self.npyfiles)
|
|
238
|
+
|
|
239
|
+
def __getitem__(self, idx):
|
|
240
|
+
filename = os.path.join(
|
|
241
|
+
self.data_source, ws.sdf_samples_subdir, self.npyfiles[idx]
|
|
242
|
+
)
|
|
243
|
+
if len(self.loaded_mat_properties) != 0:
|
|
244
|
+
mat_prop = self.loaded_mat_properties[idx]
|
|
245
|
+
else:
|
|
246
|
+
mat_prop = torch.tensor(0)
|
|
247
|
+
if self.load_ram:
|
|
248
|
+
return (
|
|
249
|
+
unpack_sdf_samples_from_ram(self.loaded_data[idx], self.subsample),
|
|
250
|
+
mat_prop.unsqueeze(0).expand(self.subsample, -1),
|
|
251
|
+
idx,
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
return (
|
|
255
|
+
unpack_sdf_samples(filename, self.geom_dimension, self.subsample),
|
|
256
|
+
mat_prop.unsqueeze(0).expand(self.subsample, -1),
|
|
257
|
+
idx,
|
|
258
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geometry Evaluation Metrics
|
|
3
|
+
===========================
|
|
4
|
+
|
|
5
|
+
This module provides utilities for evaluating the quality of generated
|
|
6
|
+
geometries by comparing them to ground truth analytical SDFs.
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
mesh_to_analytical
|
|
12
|
+
Compute SDF values at mesh vertices using an analytical SDF function.
|
|
13
|
+
Useful for quantifying reconstruction accuracy.
|
|
14
|
+
"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mesh-to-Analytical SDF Evaluation
|
|
3
|
+
=================================
|
|
4
|
+
|
|
5
|
+
This module provides functions for evaluating mesh quality by computing
|
|
6
|
+
SDF values at mesh vertices using ground truth analytical SDFs.
|
|
7
|
+
|
|
8
|
+
This is useful for:
|
|
9
|
+
- Quantifying reconstruction accuracy
|
|
10
|
+
- Measuring surface deviation
|
|
11
|
+
- Validating mesh generation algorithms
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import trimesh
|
|
15
|
+
from DeepSDFStruct.mesh import torchSurfMesh
|
|
16
|
+
from DeepSDFStruct.SDF import SDFBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mesh_to_analytical(gt_sdf: SDFBase, gen_mesh: torchSurfMesh) -> float:
|
|
20
|
+
"""
|
|
21
|
+
Calculates the SDF for every vertex on the mesh
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
return gt_sdf.forward(gen_mesh.vertices).abs().mean().item()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeepSDF Model Classes
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
This module provides the DeepSDFModel class, which wraps a trained decoder
|
|
6
|
+
network and latent vectors for evaluating learned implicit representations.
|
|
7
|
+
|
|
8
|
+
The model can be used to:
|
|
9
|
+
- Decode latent codes to SDF values at query points
|
|
10
|
+
- Export models for deployment (TorchScript/LibTorch)
|
|
11
|
+
- Integrate with gradient-based optimization
|
|
12
|
+
|
|
13
|
+
Classes
|
|
14
|
+
-------
|
|
15
|
+
DeepSDFModel
|
|
16
|
+
Main interface for trained DeepSDF models, combining a decoder network
|
|
17
|
+
with learned latent vectors.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DeepSDFModel:
|
|
24
|
+
"""Wrapper for trained DeepSDF decoder and latent vectors.
|
|
25
|
+
|
|
26
|
+
This class provides a convenient interface for using trained DeepSDF models.
|
|
27
|
+
It combines the decoder network (which maps latent+position to SDF values)
|
|
28
|
+
with the trained latent vectors (which encode different shapes).
|
|
29
|
+
|
|
30
|
+
The model supports both constant latent codes (one code for all query points)
|
|
31
|
+
and spatially-varying codes (different codes per query point), enabling
|
|
32
|
+
flexible shape representation.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
decoder : torch.nn.Module
|
|
37
|
+
The trained decoder network. Should accept input of shape
|
|
38
|
+
(N, latent_dim + 3) and output SDF values of shape (N, 1).
|
|
39
|
+
trained_latent_vectors : torch.Tensor
|
|
40
|
+
Trained latent codes of shape (num_shapes, latent_dim).
|
|
41
|
+
Each row encodes one learned shape.
|
|
42
|
+
device : str or torch.device
|
|
43
|
+
Device for computation ('cpu' or 'cuda').
|
|
44
|
+
|
|
45
|
+
Attributes
|
|
46
|
+
----------
|
|
47
|
+
_decoder : torch.nn.Module
|
|
48
|
+
The decoder network.
|
|
49
|
+
_trained_latent_vectors : torch.Tensor
|
|
50
|
+
The latent code library.
|
|
51
|
+
device : str or torch.device
|
|
52
|
+
Computation device.
|
|
53
|
+
|
|
54
|
+
Methods
|
|
55
|
+
-------
|
|
56
|
+
_decode_sdf(latent_vec, queries)
|
|
57
|
+
Decode SDF values from a latent code and spatial queries.
|
|
58
|
+
export_libtorch_executable(filename)
|
|
59
|
+
Export model to TorchScript for C++ deployment.
|
|
60
|
+
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
63
|
+
>>> import torch
|
|
64
|
+
>>> from DeepSDFStruct.deep_sdf.models import DeepSDFModel
|
|
65
|
+
>>>
|
|
66
|
+
>>> # Assume we have a trained decoder and latent vectors
|
|
67
|
+
>>> # decoder = ...
|
|
68
|
+
>>> # latents = torch.randn(10, 256) # 10 shapes, 256-dim codes
|
|
69
|
+
>>>
|
|
70
|
+
>>> # Create model
|
|
71
|
+
>>> model = DeepSDFModel(decoder, latents, device='cuda')
|
|
72
|
+
>>>
|
|
73
|
+
>>> # Query first shape
|
|
74
|
+
>>> points = torch.rand(1000, 3, device='cuda')
|
|
75
|
+
>>> distances = model._decode_sdf(latents[0], points)
|
|
76
|
+
>>>
|
|
77
|
+
>>> # Use with SDFfromDeepSDF
|
|
78
|
+
>>> from DeepSDFStruct.SDF import SDFfromDeepSDF
|
|
79
|
+
>>> sdf = SDFfromDeepSDF(model, latent_code=latents[0])
|
|
80
|
+
>>> mesh = create_3D_mesh(sdf, N_base=64, mesh_type='surface')
|
|
81
|
+
|
|
82
|
+
Notes
|
|
83
|
+
-----
|
|
84
|
+
The decoder architecture typically consists of multiple fully-connected
|
|
85
|
+
layers with skip connections. See networks/ for architecture definitions.
|
|
86
|
+
|
|
87
|
+
References
|
|
88
|
+
----------
|
|
89
|
+
.. [1] Park, J. J., Florence, P., Straub, J., Newcombe, R., & Lovegrove, S.
|
|
90
|
+
(2019). DeepSDF: Learning continuous signed distance functions for
|
|
91
|
+
shape representation. In CVPR.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self, decoder: torch.nn.Module, trained_latent_vectors: torch.Tensor, device
|
|
96
|
+
):
|
|
97
|
+
self._decoder = decoder
|
|
98
|
+
self._trained_latent_vectors = trained_latent_vectors
|
|
99
|
+
self.device = device
|
|
100
|
+
|
|
101
|
+
def _decode_sdf(
|
|
102
|
+
self, latent_vec: torch.Tensor, queries: torch.Tensor
|
|
103
|
+
) -> torch.Tensor:
|
|
104
|
+
"""Decode SDF values from latent vector and xyz queries.
|
|
105
|
+
|
|
106
|
+
Combines latent codes with spatial coordinates and passes through
|
|
107
|
+
the decoder to obtain SDF values. Handles both per-query and
|
|
108
|
+
constant latent vectors.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
latent_vec : torch.Tensor
|
|
113
|
+
Latent code(s). Two formats supported:
|
|
114
|
+
- Shape (latent_dim,): Constant code used for all queries
|
|
115
|
+
- Shape (num_samples, latent_dim): Per-query codes
|
|
116
|
+
queries : torch.Tensor
|
|
117
|
+
Query point coordinates of shape (num_samples, 3).
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
torch.Tensor
|
|
122
|
+
SDF values of shape (num_samples, 1).
|
|
123
|
+
|
|
124
|
+
Raises
|
|
125
|
+
------
|
|
126
|
+
ValueError
|
|
127
|
+
If latent_vec shape doesn't match expected dimensions.
|
|
128
|
+
|
|
129
|
+
Notes
|
|
130
|
+
-----
|
|
131
|
+
If latent_vec is constant (1D), it's expanded to match the number
|
|
132
|
+
of query points. If latent_vec is per-query (2D), it must have
|
|
133
|
+
exactly one code per query point.
|
|
134
|
+
"""
|
|
135
|
+
latent_dim = self._trained_latent_vectors[0].shape[0]
|
|
136
|
+
num_samples = queries.shape[0]
|
|
137
|
+
if latent_vec.ndim == 1:
|
|
138
|
+
if latent_vec.shape[0] != latent_dim:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Latent vector shape mismatch: {latent_vec.shape} does"
|
|
141
|
+
f"not align with latent dimension {latent_dim}."
|
|
142
|
+
)
|
|
143
|
+
latent_repeat = latent_vec.expand(-1, num_samples).T
|
|
144
|
+
elif latent_vec.ndim == 2:
|
|
145
|
+
if (latent_vec.shape[0] != num_samples) or (
|
|
146
|
+
latent_vec.shape[1] != latent_dim
|
|
147
|
+
):
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Latent vector shape mismatch: {latent_vec.shape} does"
|
|
150
|
+
f" not align with {num_samples} queries."
|
|
151
|
+
f" Must be of shape ({num_samples}, {latent_dim})"
|
|
152
|
+
)
|
|
153
|
+
latent_repeat = latent_vec
|
|
154
|
+
|
|
155
|
+
model_input = torch.cat([latent_repeat, queries], dim=1)
|
|
156
|
+
return self._decoder(model_input)
|
|
157
|
+
|
|
158
|
+
def export_libtorch_executable(self, filename: str, use_script: bool = True):
|
|
159
|
+
"""
|
|
160
|
+
Export the trained decoder model to a TorchScript file for use with LibTorch (C++).
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
filename (str): Path where the TorchScript model will be saved (e.g. "decoder.pt").
|
|
164
|
+
use_script (bool): If True, use torch.jit.script (recommended for C++ compatibility).
|
|
165
|
+
If False, use torch.jit.trace (legacy mode, may have control flow issues).
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> model.export_libtorch_executable("decoder.pt")
|
|
169
|
+
Example input: tensor([[...]])
|
|
170
|
+
Example Output: tensor([[...]])
|
|
171
|
+
# The file "decoder.pt" is now ready for loading in LibTorch.
|
|
172
|
+
"""
|
|
173
|
+
assert isinstance(
|
|
174
|
+
self._trained_latent_vectors, torch.Tensor
|
|
175
|
+
), "trained_latent_vectors must be a tensor"
|
|
176
|
+
assert (
|
|
177
|
+
self._trained_latent_vectors.shape[0] > 0
|
|
178
|
+
), "trained_latent_vectors must contain at least one element"
|
|
179
|
+
latent = self._trained_latent_vectors
|
|
180
|
+
example_input = torch.cat(
|
|
181
|
+
[latent[0], torch.tensor([0.0, 0.0, 0.0], device=self.device)]
|
|
182
|
+
).unsqueeze(0)
|
|
183
|
+
|
|
184
|
+
print("Example input: ", example_input)
|
|
185
|
+
print("Example Output: ", self._decoder(example_input))
|
|
186
|
+
|
|
187
|
+
self._decoder.eval()
|
|
188
|
+
|
|
189
|
+
if use_script:
|
|
190
|
+
print("Exporting with torch.jit.script (recommended)")
|
|
191
|
+
sm = torch.jit.script(self._decoder)
|
|
192
|
+
else:
|
|
193
|
+
print("Exporting with torch.jit.trace (legacy mode)")
|
|
194
|
+
sm = torch.jit.trace(self._decoder, example_input)
|
|
195
|
+
|
|
196
|
+
sm.save(filename)
|
|
197
|
+
print(f"Saved to {filename}")
|