mosta02 commited on
Commit
bbb030c
·
verified ·
1 Parent(s): 3b68129

Upload endograft_generation.py

Browse files
Files changed (1) hide show
  1. endograft_generation.py +193 -188
endograft_generation.py CHANGED
@@ -9,218 +9,98 @@ def arc_length(centerline):
9
  arc_lengths[i] = arc_lengths[i-1] + np.linalg.norm(centerline[i] - centerline[i-1])
10
  return arc_lengths
11
 
12
- def smooth_centerline(centerline, smoothing=0.5, samples=500):
13
- centerline = np.asarray(centerline)
14
-
15
- # 1. Fit the initial spline
16
- spline, _ = splprep(centerline.T, s=smoothing, k=3)
17
-
18
- # 2. Evaluate at extremely high resolution to measure true physical arc-length
19
- u_fine = np.linspace(0, 1, 3000)
20
- fine_points = np.vstack(splev(u_fine, spline)).T
21
-
22
- fine_arc_lengths = np.zeros(len(fine_points))
23
- for i in range(1, len(fine_points)):
24
- fine_arc_lengths[i] = fine_arc_lengths[i-1] + np.linalg.norm(fine_points[i] - fine_points[i-1])
25
-
26
- # 3. Re-sample the points so they are STRICTLY equidistant in true 3D space
27
- target_arc_lengths = np.linspace(0, fine_arc_lengths[-1], samples)
28
-
29
- smoothed_centerline = np.zeros((samples, 3))
30
- for i in range(3):
31
- smoothed_centerline[:, i] = np.interp(target_arc_lengths, fine_arc_lengths, fine_points[:, i])
32
-
33
- return smoothed_centerline
34
-
35
  def compute_parallel_frames(centerline):
36
- """
37
- Uses Wang's Double Reflection Rotation Minimizing Frame (RMF) algorithm.
38
- Mathematically guarantees zero twist along the 3D curve, eliminating strut shear.
39
- """
40
  frames = []
41
-
42
- # Initial frame
43
- t0 = centerline[1] - centerline[0]
44
- t0 /= np.linalg.norm(t0)
45
-
46
- ref = np.array([0.0, 0.0, 1.0])
47
- if abs(np.dot(t0, ref)) > 0.9:
48
- ref = np.array([1.0, 0.0, 0.0])
49
-
50
- n0 = np.cross(t0, ref)
51
- n0 /= np.linalg.norm(n0)
52
- b0 = np.cross(t0, n0)
53
-
54
- frames.append((t0, n0, b0))
55
-
56
- for i in range(1, len(centerline) - 1):
57
- x0 = centerline[i-1]
58
- x1 = centerline[i]
59
- x2 = centerline[i+1]
60
-
61
- t1 = x2 - x0
62
- t1 /= np.linalg.norm(t1)
63
-
64
- t0, n0, b0 = frames[-1]
65
-
66
- # Wang's Double Reflection Method
67
- v1 = x1 - x0
68
- c1 = np.dot(v1, v1)
69
- if c1 == 0:
70
- frames.append((t1, n0, b0))
71
- continue
72
-
73
- rL_t0 = t0 - (2.0 / c1) * np.dot(v1, t0) * v1
74
- rL_n0 = n0 - (2.0 / c1) * np.dot(v1, n0) * v1
75
-
76
- v2 = t1 - rL_t0
77
- c2 = np.dot(v2, v2)
78
- if c2 == 0:
79
- n1 = rL_n0
80
- else:
81
- n1 = rL_n0 - (2.0 / c2) * np.dot(v2, rL_n0) * v2
82
-
83
- n1 /= np.linalg.norm(n1)
84
- b1 = np.cross(t1, n1)
85
-
86
- frames.append((t1, n1, b1))
87
-
88
- # Append the last frame
89
- t_last = centerline[-1] - centerline[-2]
90
- t_last /= np.linalg.norm(t_last)
91
- _, n_last, b_last = frames[-1]
92
- frames.append((t_last, n_last, b_last))
93
-
94
- return frames
95
 
96
- def sample_centerline_by_distance(centerline, arc_lengths, frames, query_distance):
97
- # Safe linear extrapolation for fixation struts that extend past the centerline
98
- if query_distance <= 0:
99
- t0, n0, b0 = frames[0]
100
- return centerline[0] + query_distance * t0, (t0, n0, b0)
101
- if query_distance >= arc_lengths[-1]:
102
- t1, n1, b1 = frames[-1]
103
- return centerline[-1] + (query_distance - arc_lengths[-1]) * t1, (t1, n1, b1)
104
 
105
- idx = np.searchsorted(arc_lengths, query_distance)
106
- arc_length_0, arc_length_1 = arc_lengths[idx-1], arc_lengths[idx]
107
- tangent_weight = (query_distance - arc_length_0) / (arc_length_1 - arc_length_0 + 1e-12)
108
 
109
- point = (1 - tangent_weight) * centerline[idx-1] + tangent_weight * centerline[idx]
 
 
110
 
111
- t0, n0, b0 = frames[idx-1]
112
- t1, n1, b1 = frames[idx]
113
 
114
- t_vec = (1 - tangent_weight) * t0 + tangent_weight * t1
115
- n_vec = (1 - tangent_weight) * n0 + tangent_weight * n1
116
- b_vec = (1 - tangent_weight) * b0 + tangent_weight * b1
117
 
118
- t_vec /= np.linalg.norm(t_vec)
119
- n_vec /= np.linalg.norm(n_vec)
120
- b_vec /= np.linalg.norm(b_vec)
121
 
122
- return point, (t_vec, n_vec, b_vec)
 
 
123
 
 
 
124
 
125
- def create_stent(centerline, frames, fabric_start_distance, main_diam_mm, length, main_fabric, peak_to_peak_amplitude=15.0,
126
- peak_spacing=7.0, component_type="proximal", wire_radius=0.35, waves=8, samples=240, phase_angle=0.0):
127
 
128
- arc_lengths = arc_length(centerline)
129
- phase_rad = np.radians(phase_angle)
130
- append = vtk.vtkAppendPolyData()
131
 
132
- A = peak_to_peak_amplitude
133
- r = main_diam_mm / 2.0
134
- gap = peak_spacing
135
- step = A + gap
136
 
137
- n_crowns = int(np.ceil(length / step))
138
- distance_peaks = [fabric_start_distance + 1.0 + i * step for i in range(n_crowns)]
139
 
140
- if ((component_type == "distal" or component_type == "extension") and (len(distance_peaks) > 4)):
141
- distance_peaks = distance_peaks[3:]
142
- elif (component_type =="extension" and (2 < len(distance_peaks) <= 4)):
143
- distance_peaks = distance_peaks[2:]
144
-
145
- # ---------------------------------------------------------
146
- # 1. Fixation Struts
147
- # ---------------------------------------------------------
148
- A_extra = peak_to_peak_amplitude / 2.0
149
- pts = vtk.vtkPoints()
150
- lines = vtk.vtkCellArray()
151
 
152
- for i in range(samples + 1):
153
- base_theta = 2 * np.pi * i / samples
154
- theta_rot = base_theta + phase_rad
155
 
156
- if component_type == "proximal":
157
- axial_offset = A_extra * min(0.0, np.sin(waves * base_theta))
158
- distance = fabric_start_distance + axial_offset
159
- elif component_type == "distal" and length != 0:
160
- axial_offset = A_extra * max(0.0, np.sin(waves * base_theta))
161
- distance = fabric_start_distance + length + axial_offset
162
- else:
163
- continue
164
 
165
- point, (tangent, normal, binormal) = sample_centerline_by_distance(centerline, arc_lengths, frames, distance)
166
- position = point + (r + wire_radius) * (np.cos(theta_rot) * normal + np.sin(theta_rot) * binormal)
167
- pts.InsertNextPoint(position)
168
 
169
- if i > 0:
170
- l = vtk.vtkLine()
171
- l.GetPointIds().SetId(0, i-1)
172
- l.GetPointIds().SetId(1, i)
173
- lines.InsertNextCell(l)
174
 
175
- if pts.GetNumberOfPoints() > 0:
176
- poly = vtk.vtkPolyData()
177
- poly.SetPoints(pts)
178
- poly.SetLines(lines)
179
- tube = vtk.vtkTubeFilter()
180
- tube.SetInputData(poly)
181
- tube.SetRadius(wire_radius)
182
- tube.SetNumberOfSides(18)
183
- tube.CappingOff()
184
- tube.Update()
185
- append.AddInputData(tube.GetOutput())
186
-
187
- # ---------------------------------------------------------
188
- # 2. Body Struts
189
- # ---------------------------------------------------------
190
- for distance_peak in distance_peaks:
191
- points = vtk.vtkPoints()
192
- lines_body = vtk.vtkCellArray()
193
 
194
- for i in range(samples + 1):
195
- base_theta = 2 * np.pi * i / samples
196
- theta_rot = base_theta + phase_rad
197
- axial_offset = A * (1 + np.sin(waves * base_theta)) / 2.0
198
- distance = distance_peak + axial_offset
199
 
200
- point, (tangent, normal, binormal) = sample_centerline_by_distance(centerline, arc_lengths, frames, distance)
201
- position = point + (r + wire_radius) * (np.cos(theta_rot) * normal + np.sin(theta_rot) * binormal)
202
- points.InsertNextPoint(position)
203
 
204
- if i > 0:
205
- l = vtk.vtkLine()
206
- l.GetPointIds().SetId(0, i-1)
207
- l.GetPointIds().SetId(1, i)
208
- lines_body.InsertNextCell(l)
209
-
210
- poly = vtk.vtkPolyData()
211
- poly.SetPoints(points)
212
- poly.SetLines(lines_body)
213
-
214
- tube = vtk.vtkTubeFilter()
215
- tube.SetInputData(poly)
216
- tube.SetRadius(wire_radius)
217
- tube.SetNumberOfSides(18)
218
- tube.CappingOff()
219
- tube.Update()
220
- append.AddInputData(tube.GetOutput())
221
 
222
- append.Update()
223
- return append.GetOutput()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  # Fabric Component
226
  def create_fabric(centerline, frames, start_position, length, diameter, start_amplitude_pp=2.0, waves=8, phase_angle=0.0):
@@ -275,6 +155,131 @@ def create_fabric(centerline, frames, start_position, length, diameter, start_am
275
 
276
  # Struts Component
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
  def create_stent_component(centerline, frames, fabric_start_distance, length, diameter, fabric, peak_to_peak=15.0, peak_spacing=7.0, component_type="proximal", phase_angle=0.0):
280
 
 
9
  arc_lengths[i] = arc_lengths[i-1] + np.linalg.norm(centerline[i] - centerline[i-1])
10
  return arc_lengths
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def compute_parallel_frames(centerline):
 
 
 
 
13
  frames = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ tangent_0 = centerline[1] - centerline[0]
16
+ tangent_0 /= np.linalg.norm(tangent_0)
 
 
 
 
 
 
17
 
18
+ reference_vector = np.array([0, 0, 1]) # selecting z-axis as reference for calculating initial normal
19
+ if abs(np.dot(tangent_0, reference_vector)) > 0.9:
20
+ reference_vector = np.array([1, 0, 0])
21
 
22
+ normal_0 = np.cross(tangent_0, reference_vector)
23
+ normal_0 /= np.linalg.norm(normal_0)
24
+ binormal_0 = np.cross(tangent_0, normal_0)
25
 
26
+ frames.append((tangent_0, normal_0, binormal_0))
 
27
 
28
+ for point_idx in range(1, len(centerline)):
29
+ tangent_point = centerline[point_idx] - centerline[point_idx-1]
30
+ tangent_point /= np.linalg.norm(tangent_point)
31
 
32
+ tangent_prev, normal_prev, binormal_prev = frames[-1]
33
+ rotation_axis = np.cross(tangent_prev, tangent_point)
 
34
 
35
+ if np.linalg.norm(rotation_axis) < 1e-6:
36
+ frames.append((tangent_point, normal_prev, binormal_prev))
37
+ continue
38
 
39
+ rotation_axis /= np.linalg.norm(rotation_axis)
40
+ angle = np.arccos(np.clip(np.dot(tangent_prev, tangent_point), -1, 1))
41
 
42
+ def rotate_vector(vector):
43
+ return (vector * np.cos(angle) + np.cross(rotation_axis, vector) * np.sin(angle) + rotation_axis * np.dot(rotation_axis, vector) * (1 - np.cos(angle)))
44
 
45
+ frames.append((tangent_point, rotate_vector(normal_prev), rotate_vector(binormal_prev)))
 
 
46
 
47
+ return frames
 
 
 
48
 
49
+ def project_point_to_surface(point, locator, polydata):
 
50
 
51
+ closest = [0.0, 0.0, 0.0]
52
+ cellId = vtk.mutable(0)
53
+ subId = vtk.mutable(0)
54
+ dist2 = vtk.mutable(0.0)
 
 
 
 
 
 
 
55
 
56
+ locator.FindClosestPoint(point, closest, cellId, subId, dist2)
 
 
57
 
58
+ cell = polydata.GetCell(cellId)
59
+ points = cell.GetPoints()
 
 
 
 
 
 
60
 
61
+ point_0 = np.array(points.GetPoint(0))
62
+ point_1 = np.array(points.GetPoint(1))
63
+ point_2 = np.array(points.GetPoint(2))
64
 
65
+ normal = np.cross(point_1 - point_0, point_2 - point_0)
66
+ normal /= np.linalg.norm(normal)
 
 
 
67
 
68
+ return np.array(closest), normal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ def smooth_centerline(centerline, smoothing=0.5, samples=500):
71
+ centerline = np.asarray(centerline)
72
+ spline, _ = splprep(centerline.T, s=smoothing, k=3)
73
+ sample_positions = np.linspace(0, 1, samples)
74
+ smoothed_centerline = splev(sample_positions, spline)
75
 
76
+ return np.vstack(smoothed_centerline).T
 
 
77
 
78
+ def sample_centerline_by_distance(centerline, arc_lengths, frames, query_distance):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ idx = np.searchsorted(arc_lengths, query_distance)
81
+ if idx <= 0:
82
+ return centerline[0], frames[0]
83
+ if idx >= len(centerline):
84
+ return centerline[-1], frames[-1]
85
+
86
+ arc_length_0, arc_length_1 = arc_lengths[idx-1], arc_lengths[idx]
87
+ point_0, point_1 = centerline[idx-1], centerline[idx]
88
+ tangent = (query_distance - arc_length_0) / (arc_length_1 - arc_length_0 + 1e-12)
89
+
90
+ point = (1 - tangent) * point_0 + tangent * point_1
91
+
92
+ tangent_0, normal_0, binormal_0 = frames[idx-1]
93
+ tangent_1, normal_1, binormal_1 = frames[idx]
94
+
95
+ tangent_vector = (1 - tangent) * tangent_0 + tangent * tangent_1
96
+ normal_vector = (1 - tangent) * normal_0 + tangent * normal_1
97
+ binormal_vector = (1 - tangent) * binormal_0 + tangent * binormal_1
98
+
99
+ tangent_vector /= np.linalg.norm(tangent_vector)
100
+ normal_vector /= np.linalg.norm(normal_vector)
101
+ binormal_vector /= np.linalg.norm(binormal_vector)
102
+
103
+ return point, (tangent_vector, normal_vector, binormal_vector)
104
 
105
  # Fabric Component
106
  def create_fabric(centerline, frames, start_position, length, diameter, start_amplitude_pp=2.0, waves=8, phase_angle=0.0):
 
155
 
156
  # Struts Component
157
 
158
+ def create_stent(centerline, frames, fabric_start_distance, main_diam_mm, length, main_fabric, peak_to_peak_amplitude=15.0,
159
+ peak_spacing=7.0, component_type="proximal", wire_radius=0.35, waves=8, samples=240, phase_angle=0.0):
160
+
161
+ arc_lengths = arc_length(centerline)
162
+ phase_rad = np.radians(phase_angle)
163
+ append = vtk.vtkAppendPolyData()
164
+
165
+ A = peak_to_peak_amplitude # peak-peak amplitude = 15
166
+ r = main_diam_mm / 2.0
167
+ gap = peak_spacing
168
+ step = A + gap
169
+
170
+ n_crowns = int(np.ceil(length / step))
171
+ distance_peaks = [fabric_start_distance + 1.0 + i * step for i in range(n_crowns)]
172
+ if ((component_type == "distal" or component_type == "extension") and (len(distance_peaks) > 4)):
173
+ distance_peaks = distance_peaks[3:]
174
+ elif(component_type =="extension" and (2 < len(distance_peaks) <= 4)):
175
+ distance_peaks = distance_peaks[2:]
176
+
177
+ # fixation strut
178
+ A_extra = peak_to_peak_amplitude / 2.0 # 7.5 mm height
179
+ extra_top = -1e-10 # peak at -7.5
180
+
181
+ if component_type == "proximal":
182
+ point_0 = centerline[0]
183
+ tangent_0, normal_0, binormal_0 = frames[0]
184
+ point = point_0 + extra_top * tangent_0 # extrapolate backward
185
+ tangent, normal, binormal = tangent_0, normal_0, binormal_0
186
+
187
+ elif component_type == "distal":
188
+ point, (tangent, normal, binormal) = sample_centerline_by_distance(centerline, arc_lengths, frames, fabric_start_distance + length - extra_top)
189
+
190
+ else:
191
+ point, (tangent, normal, binormal) = sample_centerline_by_distance(centerline, arc_lengths, frames, extra_top)
192
+
193
+ pts = vtk.vtkPoints()
194
+ lines = vtk.vtkCellArray()
195
+
196
+ for i in range(samples + 1):
197
+ base_theta = 2*np.pi*i/samples
198
+ theta_rot = base_theta + phase_rad
199
+
200
+ # keep only negative or positive half of sine
201
+ if component_type == "proximal":
202
+ axial_offset = A_extra * min(0.0, np.sin(waves * base_theta))
203
+ raw_position = (point
204
+ + (r + wire_radius) * (np.cos(theta_rot)*normal + np.sin(theta_rot)*binormal)
205
+ + axial_offset * tangent)
206
+ elif (component_type == "distal" and length != 0):
207
+ axial_offset = A_extra * max(0.0, np.sin(waves * base_theta))
208
+
209
+ raw_position = (point
210
+ + (r + wire_radius) * (np.cos(theta_rot)*normal + np.sin(theta_rot)*binormal)
211
+ + axial_offset * tangent)
212
+ else:
213
+ continue
214
+
215
+ pts.InsertNextPoint(raw_position)
216
+
217
+ if i > 0:
218
+ l = vtk.vtkLine()
219
+ l.GetPointIds().SetId(0, i-1)
220
+ l.GetPointIds().SetId(1, i)
221
+ lines.InsertNextCell(l)
222
+
223
+ poly = vtk.vtkPolyData()
224
+ poly.SetPoints(pts)
225
+ poly.SetLines(lines)
226
+
227
+ tube = vtk.vtkTubeFilter()
228
+ tube.SetInputData(poly)
229
+ tube.SetRadius(wire_radius)
230
+ tube.SetNumberOfSides(18)
231
+ tube.CappingOff()
232
+ tube.Update()
233
+
234
+ append.AddInputData(tube.GetOutput())
235
+
236
+ # normal struts along the fabric body
237
+
238
+ cell_locator = vtk.vtkCellLocator()
239
+ cell_locator.SetDataSet(main_fabric)
240
+ cell_locator.BuildLocator()
241
+
242
+ points = vtk.vtkPoints()
243
+ lines = vtk.vtkCellArray()
244
+
245
+ for distance_peak in distance_peaks:
246
+
247
+ local_ids = [] # store point ids for this ring
248
+
249
+ for i in range(samples + 1):
250
+ base_theta = 2*np.pi*i/samples
251
+ theta_rot = base_theta + phase_rad
252
+ axial_offset = A * (1 + np.sin(waves * base_theta)) / 2.0
253
+
254
+ distance = distance_peak + axial_offset
255
+ point, (tangent, normal, binormal) = sample_centerline_by_distance(centerline, arc_lengths, frames, distance)
256
+
257
+ raw_position = (point + (r + wire_radius) * (np.cos(theta_rot)*normal + np.sin(theta_rot)*binormal))
258
+ surface_point, surface_normal = project_point_to_surface(raw_position, cell_locator, main_fabric)
259
+ position = surface_point + wire_radius * surface_normal
260
+
261
+ point_id = points.InsertNextPoint(position)
262
+ local_ids.append(point_id)
263
+
264
+ if i > 0:
265
+ l = vtk.vtkLine()
266
+ l.GetPointIds().SetId(0, local_ids[i-1])
267
+ l.GetPointIds().SetId(1, local_ids[i])
268
+ lines.InsertNextCell(l)
269
+
270
+ poly = vtk.vtkPolyData()
271
+ poly.SetPoints(points)
272
+ poly.SetLines(lines)
273
+ tube = vtk.vtkTubeFilter()
274
+ tube.SetInputData(poly)
275
+ tube.SetRadius(wire_radius)
276
+ tube.SetNumberOfSides(18)
277
+ tube.CappingOff()
278
+ tube.Update()
279
+ append.AddInputData(tube.GetOutput())
280
+ append.Update()
281
+
282
+ return append.GetOutput()
283
 
284
  def create_stent_component(centerline, frames, fabric_start_distance, length, diameter, fabric, peak_to_peak=15.0, peak_spacing=7.0, component_type="proximal", phase_angle=0.0):
285