-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathcomputeVoxel.py
274 lines (223 loc) · 9.57 KB
/
computeVoxel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
'''
Cohen, D. Voxel traversal along a 3D line.
from Heckbert, P.S. Graphics Gems IV. Morgan Kaufmann.
Visits Subroutine visits all voxels along the line
segment (x,y,z) and (x+dx,y+dy,z+dz).
'''
import numpy as np
from utils import generate_points
def check_boundary(image):
"""
Sets the values of the boundary pixels of a 3D numpy array to zero.
Args:
image (numpy.ndarray): 3D numpy array representing the image.
Returns:
numpy.ndarray: 3D numpy array with boundary pixels set to zero.
"""
image[0,] = 0
image[-1,] = 0
image[:, 0, :] = 0
image[:, -1, :] = 0
image[..., 0] = 0
image[..., -1] = 0
return image
def transversal(x, y, z, dx, dy, dz, diam,
img_stack=None, tVol=(600,600,700), resolution=(1,1,1)):
"""
Finds the voxels transversed by a segment between two points.
Args:
x (int): X-coordinate of the starting point.
y (int): Y-coordinate of the starting point.
z (int): Z-coordinate of the starting point.
dx (int): Change in X-coordinate between starting and ending points.
dy (int): Change in Y-coordinate between starting and ending points.
dz (int): Change in Z-coordinate between starting and ending points.
diam (float): Diameter of the segment.
img_stack (np.ndarray, optional): Image stack. Defaults to None.
tVol (tuple, optional): Tissue volume. Defaults to (600, 600, 700).
resolution (tuple, optional): Voxel resolution. Defaults to (1, 1, 1).
Returns:
np.ndarray: Image stack with voxels transversed by the segment.
"""
# Calculate change in coordinates
dx = dx - x
dy = dy - y
dz = dz - z
# Calculate sign and absolute value of the change in coordinates
sx = np.sign(dx)
sy = np.sign(dy)
sz = np.sign(dz)
ax = abs(dx)
ay = abs(dy)
az = abs(dz)
bx = 2 * ax
by = 2 * ay
bz = 2 * az
exy = ay - ax
exz = az - ax
ezy = ay - az
n = ax + ay + az
# Calculate radius
r = int(np.floor(0.5 * diam))
mx, my, mz = np.minimum((x + r, y + r, z + r), tVol) - (x, y, z)
# Find voxels within the tube/vessel
while n > 0:
if exy < 0:
if exz < 0:
x += sx
exy += by
exz += bz
if 0 <= x < tVol[0]:
img_stack = diamVoxels(x, y, z, 0, r, img_stack, tVol, res=resolution)
else:
z += sz
exz -= bx
ezy += by
if 0 <= z < tVol[2]:
img_stack = diamVoxels(x, y, z, 2, r, img_stack, tVol, res=resolution)
else:
if ezy < 0:
z += sz
exz -= bx
ezy += by
if 0 <= z < tVol[2]:
img_stack = diamVoxels(x, y, z, 2, r, img_stack, tVol, res=resolution)
else:
y += sy
exy -= bx
ezy -= bz
if 0 <= y < tVol[1]:
img_stack = diamVoxels(x, y, z, 1, r, img_stack, tVol, res=resolution)
n -= 1
return img_stack
def discretisation(p1, dmin, dmax, tVol):
"""
Converts a 3D point from real coordinates to discrete voxels coordinates.
Args:
- p1 (ndarray): 3D point in real coordinates with the format [x, y, z, d].
- dmin (ndarray): 3D point representing the minimum values for x, y and z.
- dmax (ndarray): 3D point representing the maximum values for x, y and z.
- tVol (tuple): tuple with the 3 dimensions of the target 3D image volume.
Returns:
- ndarray: the converted 3D point in discrete voxels coordinates with the format [x, y, z, d].
"""
# Calculate the discrete voxel coordinates
x = np.floor(tVol[0] * (p1[0] - dmin[0]) / (dmax[0] - dmin[0]))
y = np.floor(tVol[1] * (p1[1] - dmin[1]) / (dmax[1] - dmin[1]))
z = np.floor(tVol[2] * (p1[2] - dmin[2]) / (dmax[2] - dmin[2]))
# Return the converted point as a column vector
return np.array([x, y, z, p1[3]]).reshape(-1, 1)
def diamVoxels(x, y, z, idx, r, img_stack, tVol, res):
'''
Creates a binary image of the voxels in a sphere centered at (x, y, z) with radius r.
The size of the pixels is relative to the diameter of the sphere.
Args:
x (int): x-coordinate of the center of the sphere.
y (int): y-coordinate of the center of the sphere.
z (int): z-coordinate of the center of the sphere.
idx (int): The axis index of the diameter. 0, 1, or 2.
r (int): Radius of the sphere.
img_stack (ndarray): 3D numpy array representing the stack of images.
tVol (tuple): Tuple of three integers representing the dimensions of the image stack.
res (tuple): Tuple of three floats representing the resolution of each voxel in x, y, and z directions.
Returns:
ndarray: A binary 3D numpy array of the voxels within the sphere.
'''
t0, t1, t2 = tVol[0], tVol[1], tVol[2]
r0, r1, r2 = res[0], res[1], res[2]
# Calculate the indices of the voxels within the sphere and set them to 1
if idx == 0:
for i in range(-r, r+1):
if (int(y+i) < t1) & (int(y+i) >= 0):
for j in range(-r, r+1):
if (int(z+j) < t2) & (int(z+j) >= 0):
if pow(pow(i*r1, 2) + pow(j*r2, 2), 0.5) <= r:
img_stack[int(x-1), int(y+i-1), int(z+j-1)] = 1
elif idx == 1:
for i in range(-r, r+1):
if (int(x+i) < t0) & (int(x+i) >= 0):
for j in range(-r, r+1):
if (int(z+j) < t2) & (int(z+j) >= 0):
if pow(pow(i*r0, 2) + pow(j*r2, 2), 0.5) <= r:
img_stack[int(x+i-1), int(y-1), int(z+j-1)] = 1
else:
for i in range(-r, r+1):
if (int(x+i) < t0) & (int(x+i) >= 0):
for j in range(-r, r+1):
if (int(y+j) < t1) & (int(y+j) >= 0):
if pow(pow(i*r0, 2) + pow(j*r1, 2), 0.5) <= r:
img_stack[int(x+i-1), int(y+j-1), int(z-1)] = 1
return img_stack
def findVessel(i, data):
"""
Returns a vessel (a numpy array) that contains data from column i until it encounters NaN value.
It also returns the index of the next column to be read.
Args:
i (int): The index of the column to start reading the data from.
data (numpy.ndarray): The input data, with each column representing a different variable and each row representing a different observation.
Returns:
Tuple[int, numpy.ndarray]: The index of the next column to be read and the vessel containing the read data.
"""
# Initialize an empty array to hold the read data
vessel = np.array([])
# Initialize a flag variable to indicate if the function should stop reading data
stop = False
# While the function hasn't encountered a NaN value, it keeps reading data from the next column
while not stop:
# If the data in the current column isn't NaN, read it into the vessel
if not np.isnan(data[0,i]):
# If the vessel is empty, add the data as a column vector
if vessel.size == 0:
vessel = data[:,i].reshape(-1,1)
# If the vessel already contains data, add the new column to the right of the previous columns
else:
vessel = np.hstack((vessel, data[:,i]))
# Move to the next column
i += 1
# If the data in the current column is NaN, stop reading data
else:
stop = True
# Return the index of the next column to be read and the vessel containing the read data
return i, vessel
def process_network(data,tVol):
"""
Processes a 3D medical image data, generates point data,
and finds the voxels transversed by segments between two points.
Args:
data (np.ndarray): a 2D array of medical image data where each column represents a vessel
tVol (tuple): a tuple of integers (x, y, z) representing the size of the tissue volume
Returns:
np.ndarray: A 3D array of integers representing the voxels transversed by segments
between two points after checking for boundary violations
"""
# Find non-NaN min/max values across data leaving a 10% margin in case of
# vessel/tissue boundary contact
dmin = np.nanmin(data, axis=1) * 1.1
dmax = np.nanmax(data, axis=1) * 1.1
# Initializing new array
newarray = np.array([])
# Creating an empty 3D array with the given shape
img_stack = np.empty(tVol, dtype=int)
# Loop over columns of data
for i in range(data.shape[1]):
if not np.isnan(data[0, i]):
# Discretise data into defined tissue volume
tempvec = discretisation(data[:, i], dmin, dmax, tVol)
else:
tempvec = data[:, i].reshape(-1, 1)
# Generating & organising point data into array
newarray = generate_points(newarray, tempvec, usenan=False)
# Loop over rows of newarray
j = 0
for i in range(newarray.shape[1] - 1):
j += 1
if not np.isnan(newarray[0, i]):
if not np.isnan(newarray[0, j]):
# Calculate diameter and pass into function to find voxels transversed by
# by segment between two points
diam = 0.5 * (newarray[3, i] + newarray[3, j])
transversal(newarray[0, i], newarray[1, i], newarray[2, i],
newarray[0, j], newarray[1, j], newarray[2, j],
diam, img_stack, tVol)
# Check boundary violations and return img_stack
return check_boundary(img_stack)