marta-marta commited on
Commit
4661dbe
·
1 Parent(s): 6f15aad

Added to incorporate a function to export the STL files directly from the user generated inputs

Browse files
Files changed (1) hide show
  1. voxel_to_SDF_to_STL.py +220 -0
voxel_to_SDF_to_STL.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import trimesh
3
+ from scipy.ndimage import morphology
4
+ from skimage import measure
5
+ from stl import mesh
6
+ import pyvista as pv
7
+ import streamlit as st
8
+ from stpyvista import stpyvista
9
+ import tempfile
10
+ import os
11
+
12
+
13
+ ########################################################################################################################
14
+ def voxel_to_sdf(voxel, voxel_threshold, pad_size=5):
15
+ """
16
+ :param voxel: - a continuous or binary 3D array of values that range [0,1]
17
+ :param voxel_threshold: - the value that is used to threshold the array and convert it into a binary array,
18
+ a larger value will restrict the array to more concrete values but will produce a rougher shape.
19
+ :param pad_size: - the size of padding to encompass the array to ensure the body is internal to the SDF
20
+ :return: sdf - a signed distance array, where -'s are the internal components and +'s and the external components
21
+ """
22
+ # Convert a 3D voxel array to a 3D SDF
23
+ # Where voxel is a continuous array [0,1]
24
+ # Pad the voxel array with extra border elements
25
+ padded_voxel = np.pad(voxel, pad_size, mode='constant', constant_values=0)
26
+
27
+ # The values are switched, since SDF considers +'s to be outside the body and -'s internal to the body
28
+ binary_voxel_data = np.where(np.array(padded_voxel) > voxel_threshold, 0, 1)
29
+
30
+ # Calculate the Signed Distance Field (SDF)
31
+ sdf = morphology.distance_transform_edt(binary_voxel_data) - 0.5
32
+
33
+ # Plot Binary Data to see how it looks
34
+ # voxel_plot = pv.wrap(np.array(voxel))
35
+ # voxel_plot.plot(volume=True)
36
+
37
+ # Plot the SDF to see how it looks before proceeding
38
+
39
+ # HOW TO PLOT THE SDF IN STREAMLIT
40
+ # # Create a PyVista grid from the SDF
41
+ # grid = pv.wrap(sdf)
42
+ # # # Plot the grid as a volume rendering
43
+ # # grid.plot(volume=True)
44
+ #
45
+ # # Set up plotter
46
+ # plotter = pv.Plotter(window_size=[600,600])
47
+ # plotter.add_volume(grid, opacity='linear')
48
+ # plotter.view_zy()
49
+ #
50
+ # # Pass the plotter (not the mesh) to stpyvista
51
+ # stpyvista(plotter)
52
+
53
+ return sdf
54
+
55
+
56
+ ########################################################################################################################
57
+ def single_body_check(sdf, threshold_divisor):
58
+ """
59
+ :param sdf: - a signed distance array, where -'s are the internal components and +'s and the external components
60
+ :return: boolean - indicates whether the SDF is a single body, which can be converted into an STL file
61
+ """
62
+ # This function is intended to be used to determine whether the sdf contains more than one body prior to converting to 3MF
63
+
64
+ # Determine the SDF range (minimum and maximum values)
65
+ sdf_min = np.min(sdf)
66
+ sdf_max = np.max(sdf)
67
+
68
+ # Set the surface level within the SDF range (or close to zero for the surface)
69
+ surface_level = (sdf_min + sdf_max) / threshold_divisor # For example, set it to the middle of the range
70
+
71
+ # Threshold the SDF to identify regions close to the surface (threshold_value may vary based on your data)
72
+ threshold_value = 0.05
73
+ surface_regions = (np.abs(sdf) <= surface_level)
74
+
75
+ # Perform connected component analysis on the thresholded SDF to label individual bodies
76
+ connected_components = measure.label(surface_regions)
77
+
78
+ # Count the number of connected components to determine the number of bodies
79
+ num_labels = np.max(connected_components)
80
+
81
+ # Count the number of connected components to determine the number of bodies
82
+ if num_labels == 1:
83
+ print("SDF contains single body, processing into STL")
84
+ return True
85
+ else:
86
+ st.warning(f"ERROR: SDF was not a single body, contained {num_labels} bodies, therefore model requires further "
87
+ f"modification before proceeding. Recommended to start by changing threshold value for SDF (3), "
88
+ f"or restart with modifying the image threshold (2).")
89
+ return False
90
+
91
+
92
+ ########################################################################################################################
93
+ def sdf_to_stl(sdf, threshold_divsor):
94
+ """
95
+ :param sdf: - a signed distance array, where -'s are the internal components and +'s and the external components
96
+ :param filename_stl: - the file name desired for the STL
97
+ :param threshold_divsor: - this divisor determines the threshold for the SDF to be converted into the STL.
98
+ A lower threshold will result in a lower resolution model with more spherical shapes,
99
+ and a higher threshold will produce rugged shapes that better match the original voxel format
100
+ :return: stl file - an STL file is saved based on the SDF provided, it is also displayed in the IDE
101
+ """
102
+ # Determine the SDF range (minimum and maximum values)
103
+ sdf_min = np.min(sdf)
104
+ sdf_max = np.max(sdf)
105
+
106
+ # Set the surface level within the SDF range (or close to zero for the surface)
107
+ surface_level = (sdf_min + abs(sdf_max)) / threshold_divsor # For example, set it to the middle of the range
108
+
109
+ # Generate a 3D surface mesh using marching cubes from the SDF
110
+ vertices, triangles, _, _ = measure.marching_cubes(sdf, level=surface_level)
111
+
112
+ '''
113
+ # Display resulting triangular mesh using Matplotlib. This can also be done
114
+ # with mayavi (see skimage.measure.marching_cubes docstring).
115
+ fig = plt.figure(figsize=(10, 10))
116
+ ax = fig.add_subplot(111, projection='3d')
117
+
118
+ # Fancy indexing: `verts[faces]` to generate a collection of triangles
119
+ mesh = Poly3DCollection(vertices[triangles])
120
+ mesh.set_edgecolor('k')
121
+ ax.add_collection3d(mesh)
122
+
123
+ ax.set_xlim(0, 50) # a = 6 (times two for 2nd ellipsoid)
124
+ ax.set_ylim(0, 50) # b = 10
125
+ ax.set_zlim(0, 50) # c = 16
126
+
127
+ plt.tight_layout()
128
+ plt.show()
129
+ '''
130
+
131
+ # Create an STL mesh object
132
+ stl_mesh = mesh.Mesh(np.zeros(triangles.shape[0], dtype=mesh.Mesh.dtype))
133
+ for i, triangle in enumerate(triangles):
134
+ for j in range(3):
135
+ stl_mesh.vectors[i][j] = vertices[triangle[j], :]
136
+
137
+
138
+ # # Save the STL mesh to a file
139
+ # stl_mesh.save(filename_stl)
140
+ #
141
+ # # Load the STL file using trimesh
142
+ # stl = trimesh.load(filename_stl)
143
+ #
144
+ # # Show the mesh using trimesh's built-in viewer
145
+ # stl.show()
146
+
147
+
148
+ # # Set up plotter
149
+ # plotter = pv.Plotter(window_size=[600, 600])
150
+ # plotter.add_mesh(stl_mesh, color='teal')
151
+
152
+ # Create a PyVista mesh from the stl_mesh
153
+ pyvista_mesh = pv.PolyData(vertices)
154
+ # pyvista_mesh = pv.PolyData(stl_mesh.vectors.copy().reshape(-1, 3))
155
+ faces = np.hstack([np.full((len(triangles), 1), 3), triangles])
156
+ pyvista_mesh.faces = faces
157
+
158
+ # # Optionally, you can assign a color to the mesh
159
+ # pyvista_mesh['color'] = [0.5, 0.5, 1.0] # RGB color, e.g., teal
160
+
161
+ # Create a plotter and add the PyVista mesh to it
162
+ plotter = pv.Plotter(window_size=[600, 600])
163
+ plotter.add_mesh(pyvista_mesh, style='wireframe', show_edges=True, lighting=True, edge_color='black')
164
+ plotter.background_color = 'white'
165
+ plotter.view_zy() # if mesh_2D is on the xy plane.
166
+
167
+ # # Show the plot
168
+ # plotter.show()
169
+
170
+ # Pass the plotter (not the mesh) to stpyvista
171
+ stpyvista(plotter)
172
+
173
+ # Create a temporary directory to store the STL file
174
+ temp_dir = tempfile.mkdtemp()
175
+
176
+ # Define the temporary STL file path
177
+ temp_stl_file = os.path.join(temp_dir, 'interpolation.stl')
178
+
179
+ # Save the STL data to the temporary file
180
+ stl_mesh.save(temp_stl_file)
181
+
182
+ return temp_stl_file
183
+
184
+
185
+ ########################################################################################################################
186
+ # Creating a unit cell using two interpolation points
187
+ #
188
+ # def linear_interp_unit_cell(image_1, image_2, test_data, geometry_train, encoder, decoder, number_of_inputs, filename_stl):
189
+ # image_size = np.shape(test_data[image_1])[0]
190
+ # interpolation_length = int(image_size/2)
191
+ #
192
+ # # 1. Select Latent Endpoints to Interpolate Between
193
+ # sample_indices, [latent_point_1, latent_point_2] = encode_random_latent_endpoints(test_data, encoder,
194
+ # number_latent_points=2,
195
+ # sample_indices=[image_1, image_2],
196
+ # random_seed=0,
197
+ # num_inputs=number_of_inputs)
198
+ #
199
+ # # 2. Create a list of all latent points in a linear interpolation between two endpoints
200
+ # latent_interpolation_points = linear_interpolation_matrix(interpolation_length, latent_point_1, latent_point_2)
201
+ #
202
+ # # 3. Plot the results of the linear interpolation
203
+ # linear_predicted_geometry = linear_interpolation_reconstruction_plot(latent_interpolation_points,
204
+ # geometry_train, decoder,
205
+ # sample_indices[0], sample_indices[1])
206
+ #
207
+ #
208
+ # # Mirror the array along the x-axis
209
+ # mirrored_array = np.flip(linear_predicted_geometry, axis=0)
210
+ #
211
+ # # Create the combined array with shape (50, 50, 50) by concatenating the mirrored array with the original array
212
+ # single_axis_cell = np.concatenate((linear_predicted_geometry, mirrored_array), axis=0)
213
+ #
214
+ # sdf = voxel_to_sdf(single_axis_cell, 0.1)
215
+ #
216
+ # sdf_to_stl(sdf, filename_stl, 21.0)
217
+
218
+
219
+
220
+