Spaces:
Sleeping
Sleeping
Update Comp2Comp-main/comp2comp/aaa/aaa.py
Browse files- Comp2Comp-main/comp2comp/aaa/aaa.py +343 -165
Comp2Comp-main/comp2comp/aaa/aaa.py
CHANGED
@@ -1,24 +1,46 @@
|
|
1 |
-
import math
|
2 |
-
import operator
|
3 |
import os
|
4 |
import zipfile
|
5 |
from pathlib import Path
|
6 |
from time import time
|
7 |
-
from tkinter import Tcl
|
8 |
from typing import Union
|
|
|
9 |
|
|
|
|
|
|
|
10 |
import cv2
|
11 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
import moviepy.video.io.ImageSequenceClip
|
13 |
-
|
14 |
-
import numpy as np
|
15 |
import pandas as pd
|
16 |
-
import
|
17 |
-
import wget
|
18 |
-
from totalsegmentator.libs import nostdout
|
19 |
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
class AortaSegmentation(InferenceClass):
|
24 |
"""Spine segmentation."""
|
@@ -42,16 +64,16 @@ class AortaSegmentation(InferenceClass):
|
|
42 |
self.output_dir_segmentations + "spine.nii.gz",
|
43 |
inference_pipeline.model_dir,
|
44 |
)
|
45 |
-
|
46 |
seg = seg.get_fdata()
|
47 |
medical_volume = mv.get_fdata()
|
48 |
-
|
49 |
axial_masks = []
|
50 |
ct_image = []
|
51 |
|
52 |
for i in range(seg.shape[2]):
|
53 |
axial_masks.append(seg[:, :, i])
|
54 |
-
|
55 |
for i in range(medical_volume.shape[2]):
|
56 |
ct_image.append(medical_volume[:, :, i])
|
57 |
|
@@ -68,13 +90,13 @@ class AortaSegmentation(InferenceClass):
|
|
68 |
|
69 |
model_dir = Path(model_dir)
|
70 |
config_dir = model_dir / Path("." + self.model_name)
|
71 |
-
(config_dir / "nnunet/results/nnUNet/3d_fullres").mkdir(
|
72 |
-
exist_ok=True, parents=True
|
73 |
-
)
|
74 |
(config_dir / "nnunet/results/nnUNet/2d").mkdir(exist_ok=True, parents=True)
|
75 |
weights_dir = config_dir / "nnunet/results"
|
76 |
self.weights_dir = weights_dir
|
77 |
|
|
|
|
|
78 |
os.environ["nnUNet_raw_data_base"] = str(
|
79 |
weights_dir
|
80 |
) # not needed, just needs to be an existing directory
|
@@ -98,9 +120,7 @@ class AortaSegmentation(InferenceClass):
|
|
98 |
"https://huggingface.co/AdritRao/aaa_test/resolve/main/fold_0.zip",
|
99 |
out=os.path.join(download_dir, "fold_0.zip"),
|
100 |
)
|
101 |
-
with zipfile.ZipFile(
|
102 |
-
os.path.join(download_dir, "fold_0.zip"), "r"
|
103 |
-
) as zip_ref:
|
104 |
zip_ref.extractall(download_dir)
|
105 |
os.remove(os.path.join(download_dir, "fold_0.zip"))
|
106 |
wget.download(
|
@@ -111,9 +131,7 @@ class AortaSegmentation(InferenceClass):
|
|
111 |
else:
|
112 |
print("Spine model already downloaded.")
|
113 |
|
114 |
-
def spine_seg(
|
115 |
-
self, input_path: Union[str, Path], output_path: Union[str, Path], model_dir
|
116 |
-
):
|
117 |
"""Run spine segmentation.
|
118 |
|
119 |
Args:
|
@@ -133,13 +151,14 @@ class AortaSegmentation(InferenceClass):
|
|
133 |
trainer = "nnUNetTrainerV2_ep4000_nomirror"
|
134 |
crop_path = None
|
135 |
task_id = [253]
|
136 |
-
|
137 |
self.setup_nnunet_c2c(model_dir)
|
138 |
self.download_spine_model(model_dir)
|
139 |
|
140 |
from totalsegmentator.nnunet import nnUNet_predict_image
|
141 |
|
142 |
with nostdout():
|
|
|
143 |
img, seg = nnUNet_predict_image(
|
144 |
input_path,
|
145 |
output_path,
|
@@ -171,8 +190,8 @@ class AortaSegmentation(InferenceClass):
|
|
171 |
|
172 |
return seg, img
|
173 |
|
174 |
-
|
175 |
class AortaDiameter(InferenceClass):
|
|
|
176 |
def __init__(self):
|
177 |
super().__init__()
|
178 |
|
@@ -186,14 +205,11 @@ class AortaDiameter(InferenceClass):
|
|
186 |
return (img - img.min()) / (img.max() - img.min())
|
187 |
|
188 |
def __call__(self, inference_pipeline):
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
) # 3D numpy array of shape (512, 512, num_axial_slices)
|
195 |
-
|
196 |
-
# image output directory
|
197 |
output_dir = inference_pipeline.output_dir
|
198 |
output_dir_slices = os.path.join(output_dir, "images/slices/")
|
199 |
if not os.path.exists(output_dir_slices):
|
@@ -205,11 +221,11 @@ class AortaDiameter(InferenceClass):
|
|
205 |
os.makedirs(output_dir_summary)
|
206 |
|
207 |
DICOM_PATH = inference_pipeline.dicom_series_path
|
208 |
-
dicom = pydicom.dcmread(DICOM_PATH
|
209 |
-
|
210 |
-
dicom.PhotometricInterpretation =
|
211 |
pixel_conversion = dicom.PixelSpacing
|
212 |
-
print("Pixel conversion: "
|
213 |
RATIO_PIXEL_TO_MM = pixel_conversion[0]
|
214 |
|
215 |
SLICE_COUNT = dicom["InstanceNumber"].value
|
@@ -217,9 +233,10 @@ class AortaDiameter(InferenceClass):
|
|
217 |
|
218 |
SLICE_COUNT = len(ct_img)
|
219 |
diameterDict = {}
|
220 |
-
|
221 |
for i in range(len(ct_img)):
|
222 |
-
|
|
|
223 |
|
224 |
img = ct_img[i]
|
225 |
|
@@ -231,170 +248,331 @@ class AortaDiameter(InferenceClass):
|
|
231 |
contours, _ = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
|
232 |
|
233 |
if len(contours) != 0:
|
234 |
-
areas = [cv2.contourArea(c) for c in contours]
|
235 |
-
sorted_areas = np.sort(areas)
|
236 |
|
237 |
-
|
238 |
-
|
239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
|
241 |
-
|
242 |
|
243 |
-
back = img.copy()
|
244 |
-
cv2.drawContours(back, [contours], 0, (0, 255, 0), -1)
|
245 |
|
246 |
-
|
247 |
-
img = cv2.addWeighted(img, 1 - alpha, back, alpha, 0)
|
248 |
|
249 |
-
|
250 |
-
|
251 |
|
252 |
-
|
|
|
|
|
253 |
|
254 |
-
|
255 |
-
cv2.circle(img, (int(xc), int(yc)), 5, (0, 0, 255), -1)
|
256 |
|
257 |
-
|
258 |
-
|
|
|
|
|
|
|
|
|
259 |
|
260 |
-
|
261 |
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
|
275 |
-
|
|
|
276 |
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
x2 = xc + math.cos(math.radians(angle + 180)) * rminor
|
285 |
-
y2 = yc + math.sin(math.radians(angle + 180)) * rminor
|
286 |
-
cv2.line(img, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0), 3)
|
287 |
|
288 |
-
# pixel_length = math.sqrt( (x1-x2)**2 + (y1-y2)**2 )
|
289 |
-
pixel_length = rminor * 2
|
290 |
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
292 |
|
293 |
-
area_px = cv2.contourArea(contours)
|
294 |
-
area_mm = round(area_px * RATIO_PIXEL_TO_MM)
|
295 |
-
area_cm = area_mm / 10
|
296 |
|
297 |
-
|
298 |
-
|
|
|
|
|
299 |
|
300 |
-
diameterDict[(SLICE_COUNT - (i))] = diameter_cm
|
301 |
|
302 |
-
|
|
|
|
|
|
|
303 |
|
304 |
-
h, w, c = img.shape
|
305 |
-
lbls = [
|
306 |
-
"Area (mm): " + str(area_mm) + "mm",
|
307 |
-
"Area (cm): " + str(area_cm) + "cm",
|
308 |
-
"Diameter (mm): " + str(diameter_mm) + "mm",
|
309 |
-
"Diameter (cm): " + str(diameter_cm) + "cm",
|
310 |
-
"Slice: " + str(SLICE_COUNT - (i)),
|
311 |
-
]
|
312 |
-
font = cv2.FONT_HERSHEY_SIMPLEX
|
313 |
|
314 |
-
|
315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
|
317 |
-
cv2.putText(img, lbls[0], (10, 40), font, fontScale, (0, 255, 0), 2)
|
318 |
|
319 |
-
|
|
|
|
|
|
|
320 |
|
321 |
-
|
322 |
|
323 |
-
|
|
|
|
|
|
|
324 |
|
325 |
-
|
326 |
|
327 |
-
cv2.imwrite(
|
328 |
-
output_dir_slices + "slice" + str(SLICE_COUNT - (i)) + ".png", img
|
329 |
-
)
|
330 |
|
331 |
-
|
|
|
|
|
332 |
|
333 |
-
plt.title(r"$\bf{Diameter}$" + " " + r"$\bf{Progression}$")
|
334 |
|
335 |
-
|
|
|
|
|
|
|
|
|
336 |
|
337 |
-
|
338 |
-
|
339 |
|
340 |
-
|
341 |
-
|
342 |
-
print(diameterDict[max(diameterDict.items(), key=operator.itemgetter(1))[0]])
|
343 |
|
344 |
-
|
345 |
-
|
346 |
-
]
|
347 |
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
img = np.clip(img, -300, 1800)
|
352 |
-
img = self.normalize_img(img) * 255.0
|
353 |
-
img = img.reshape((img.shape[0], img.shape[1], 1))
|
354 |
-
img2 = np.tile(img, (1, 1, 3))
|
355 |
-
img2 = cv2.rotate(img2, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
356 |
|
357 |
-
|
358 |
-
|
359 |
-
+ "slice"
|
360 |
-
+ str(max(diameterDict.items(), key=operator.itemgetter(1))[0])
|
361 |
-
+ ".png"
|
362 |
-
)
|
363 |
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
373 |
)
|
374 |
-
|
375 |
-
|
376 |
-
top=border_size,
|
377 |
-
bottom=border_size,
|
378 |
-
left=border_size,
|
379 |
-
right=border_size,
|
380 |
-
borderType=cv2.BORDER_CONSTANT,
|
381 |
-
value=[244, 0, 0],
|
382 |
)
|
383 |
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
396 |
)
|
397 |
-
clip.write_videofile(output_dir_summary + "aaa.mp4")
|
398 |
|
399 |
return {}
|
400 |
|
@@ -420,5 +598,5 @@ class AortaMetricsSaver(InferenceClass):
|
|
420 |
"""Save results to a CSV file."""
|
421 |
_, filename = os.path.split(self.dicom_series_path)
|
422 |
data = [[filename, str(self.max_diameter)]]
|
423 |
-
df = pd.DataFrame(data, columns=[
|
424 |
-
df.to_csv(os.path.join(self.csv_output_dir, "aorta_metrics.csv"), index=False)
|
|
|
|
|
|
|
1 |
import os
|
2 |
import zipfile
|
3 |
from pathlib import Path
|
4 |
from time import time
|
|
|
5 |
from typing import Union
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
|
8 |
+
import dosma
|
9 |
+
import numpy as np
|
10 |
+
import wget
|
11 |
import cv2
|
12 |
+
import scipy.misc
|
13 |
+
from PIL import Image
|
14 |
+
|
15 |
+
import dicom2nifti
|
16 |
+
import math
|
17 |
+
import pydicom
|
18 |
+
import operator
|
19 |
import moviepy.video.io.ImageSequenceClip
|
20 |
+
from tkinter import Tcl
|
|
|
21 |
import pandas as pd
|
22 |
+
import warnings
|
|
|
|
|
23 |
|
24 |
+
import numpy as np
|
25 |
+
from skimage.morphology import skeletonize_3d
|
26 |
+
from scipy.spatial.distance import pdist, squareform
|
27 |
+
from scipy.interpolate import splprep, splev
|
28 |
+
import nibabel as nib
|
29 |
+
from nibabel.processing import resample_to_output
|
30 |
+
|
31 |
+
import matplotlib.pyplot as plt
|
32 |
+
from scipy.interpolate import interp1d
|
33 |
|
34 |
+
from totalsegmentator.libs import (
|
35 |
+
download_pretrained_weights,
|
36 |
+
nostdout,
|
37 |
+
setup_nnunet,
|
38 |
+
)
|
39 |
+
|
40 |
+
from comp2comp.inference_class_base import InferenceClass
|
41 |
+
from comp2comp.models.models import Models
|
42 |
+
from comp2comp.spine import spine_utils
|
43 |
+
import nibabel as nib
|
44 |
|
45 |
class AortaSegmentation(InferenceClass):
|
46 |
"""Spine segmentation."""
|
|
|
64 |
self.output_dir_segmentations + "spine.nii.gz",
|
65 |
inference_pipeline.model_dir,
|
66 |
)
|
67 |
+
|
68 |
seg = seg.get_fdata()
|
69 |
medical_volume = mv.get_fdata()
|
70 |
+
|
71 |
axial_masks = []
|
72 |
ct_image = []
|
73 |
|
74 |
for i in range(seg.shape[2]):
|
75 |
axial_masks.append(seg[:, :, i])
|
76 |
+
|
77 |
for i in range(medical_volume.shape[2]):
|
78 |
ct_image.append(medical_volume[:, :, i])
|
79 |
|
|
|
90 |
|
91 |
model_dir = Path(model_dir)
|
92 |
config_dir = model_dir / Path("." + self.model_name)
|
93 |
+
(config_dir / "nnunet/results/nnUNet/3d_fullres").mkdir(exist_ok=True, parents=True)
|
|
|
|
|
94 |
(config_dir / "nnunet/results/nnUNet/2d").mkdir(exist_ok=True, parents=True)
|
95 |
weights_dir = config_dir / "nnunet/results"
|
96 |
self.weights_dir = weights_dir
|
97 |
|
98 |
+
|
99 |
+
|
100 |
os.environ["nnUNet_raw_data_base"] = str(
|
101 |
weights_dir
|
102 |
) # not needed, just needs to be an existing directory
|
|
|
120 |
"https://huggingface.co/AdritRao/aaa_test/resolve/main/fold_0.zip",
|
121 |
out=os.path.join(download_dir, "fold_0.zip"),
|
122 |
)
|
123 |
+
with zipfile.ZipFile(os.path.join(download_dir, "fold_0.zip"), "r") as zip_ref:
|
|
|
|
|
124 |
zip_ref.extractall(download_dir)
|
125 |
os.remove(os.path.join(download_dir, "fold_0.zip"))
|
126 |
wget.download(
|
|
|
131 |
else:
|
132 |
print("Spine model already downloaded.")
|
133 |
|
134 |
+
def spine_seg(self, input_path: Union[str, Path], output_path: Union[str, Path], model_dir):
|
|
|
|
|
135 |
"""Run spine segmentation.
|
136 |
|
137 |
Args:
|
|
|
151 |
trainer = "nnUNetTrainerV2_ep4000_nomirror"
|
152 |
crop_path = None
|
153 |
task_id = [253]
|
154 |
+
|
155 |
self.setup_nnunet_c2c(model_dir)
|
156 |
self.download_spine_model(model_dir)
|
157 |
|
158 |
from totalsegmentator.nnunet import nnUNet_predict_image
|
159 |
|
160 |
with nostdout():
|
161 |
+
|
162 |
img, seg = nnUNet_predict_image(
|
163 |
input_path,
|
164 |
output_path,
|
|
|
190 |
|
191 |
return seg, img
|
192 |
|
|
|
193 |
class AortaDiameter(InferenceClass):
|
194 |
+
|
195 |
def __init__(self):
|
196 |
super().__init__()
|
197 |
|
|
|
205 |
return (img - img.min()) / (img.max() - img.min())
|
206 |
|
207 |
def __call__(self, inference_pipeline):
|
208 |
+
|
209 |
+
axial_masks = inference_pipeline.axial_masks # list of 2D numpy arrays of shape (512, 512)
|
210 |
+
ct_img = inference_pipeline.ct_image # 3D numpy array of shape (512, 512, num_axial_slices)
|
211 |
+
|
212 |
+
# image output directory
|
|
|
|
|
|
|
213 |
output_dir = inference_pipeline.output_dir
|
214 |
output_dir_slices = os.path.join(output_dir, "images/slices/")
|
215 |
if not os.path.exists(output_dir_slices):
|
|
|
221 |
os.makedirs(output_dir_summary)
|
222 |
|
223 |
DICOM_PATH = inference_pipeline.dicom_series_path
|
224 |
+
dicom = pydicom.dcmread(DICOM_PATH+"/"+os.listdir(DICOM_PATH)[0])
|
225 |
+
|
226 |
+
dicom.PhotometricInterpretation = 'YBR_FULL'
|
227 |
pixel_conversion = dicom.PixelSpacing
|
228 |
+
print("Pixel conversion: "+str(pixel_conversion))
|
229 |
RATIO_PIXEL_TO_MM = pixel_conversion[0]
|
230 |
|
231 |
SLICE_COUNT = dicom["InstanceNumber"].value
|
|
|
233 |
|
234 |
SLICE_COUNT = len(ct_img)
|
235 |
diameterDict = {}
|
236 |
+
|
237 |
for i in range(len(ct_img)):
|
238 |
+
|
239 |
+
mask = axial_masks[i].astype('uint8')
|
240 |
|
241 |
img = ct_img[i]
|
242 |
|
|
|
248 |
contours, _ = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
|
249 |
|
250 |
if len(contours) != 0:
|
|
|
|
|
251 |
|
252 |
+
areas = [cv2.contourArea(c) for c in contours]
|
253 |
+
sorted_areas = np.sort(areas)
|
254 |
+
|
255 |
+
contours = contours[areas.index(sorted_areas[-1])]
|
256 |
+
|
257 |
+
overlay = img.copy()
|
258 |
+
|
259 |
+
back = img.copy()
|
260 |
+
cv2.drawContours(back, [contours], 0, (0,255,0), -1)
|
261 |
+
|
262 |
+
alpha = 0.25
|
263 |
+
img = cv2.addWeighted(img, 1-alpha, back, alpha, 0)
|
264 |
+
|
265 |
+
ellipse = cv2.fitEllipse(contours)
|
266 |
+
(xc,yc),(d1,d2),angle = ellipse
|
267 |
+
|
268 |
+
cv2.ellipse(img, ellipse, (0, 255, 0), 1)
|
269 |
+
|
270 |
+
xc, yc = ellipse[0]
|
271 |
+
cv2.circle(img, (int(xc),int(yc)), 5, (0, 0, 255), -1)
|
272 |
+
|
273 |
+
rmajor = max(d1,d2)/2
|
274 |
+
rminor = min(d1,d2)/2
|
275 |
+
|
276 |
+
### Draw major axes
|
277 |
+
|
278 |
+
if angle > 90:
|
279 |
+
angle = angle - 90
|
280 |
+
else:
|
281 |
+
angle = angle + 90
|
282 |
+
print(angle)
|
283 |
+
xtop = xc + math.cos(math.radians(angle))*rmajor
|
284 |
+
ytop = yc + math.sin(math.radians(angle))*rmajor
|
285 |
+
xbot = xc + math.cos(math.radians(angle+180))*rmajor
|
286 |
+
ybot = yc + math.sin(math.radians(angle+180))*rmajor
|
287 |
+
cv2.line(img, (int(xtop),int(ytop)), (int(xbot),int(ybot)), (0, 0, 255), 3)
|
288 |
+
|
289 |
+
### Draw minor axes
|
290 |
+
|
291 |
+
if angle > 90:
|
292 |
+
angle = angle - 90
|
293 |
+
else:
|
294 |
+
angle = angle + 90
|
295 |
+
print(angle)
|
296 |
+
x1 = xc + math.cos(math.radians(angle))*rminor
|
297 |
+
y1 = yc + math.sin(math.radians(angle))*rminor
|
298 |
+
x2 = xc + math.cos(math.radians(angle+180))*rminor
|
299 |
+
y2 = yc + math.sin(math.radians(angle+180))*rminor
|
300 |
+
cv2.line(img, (int(x1),int(y1)), (int(x2),int(y2)), (255, 0, 0), 3)
|
301 |
+
|
302 |
+
# pixel_length = math.sqrt( (x1-x2)**2 + (y1-y2)**2 )
|
303 |
+
pixel_length = rminor*2
|
304 |
+
|
305 |
+
print("Pixel_length_minor: "+str(pixel_length))
|
306 |
+
|
307 |
+
area_px = cv2.contourArea(contours)
|
308 |
+
area_mm = round(area_px*RATIO_PIXEL_TO_MM)
|
309 |
+
area_cm = area_mm/10
|
310 |
+
|
311 |
+
diameter_mm = round((pixel_length)*RATIO_PIXEL_TO_MM)
|
312 |
+
diameter_cm = diameter_mm/10
|
313 |
+
|
314 |
+
diameterDict[(SLICE_COUNT-(i))] = diameter_cm
|
315 |
+
|
316 |
+
img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
317 |
+
|
318 |
+
h,w,c = img.shape
|
319 |
+
lbls = ["Area (mm): "+str(area_mm)+"mm", "Area (cm): "+str(area_cm)+"cm", "Diameter (mm): "+str(diameter_mm)+"mm", "Diameter (cm): "+str(diameter_cm)+"cm", "Slice: "+str(SLICE_COUNT-(i))]
|
320 |
+
offset = 0
|
321 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
322 |
+
|
323 |
+
scale = 0.03
|
324 |
+
fontScale = min(w,h)/(25/scale)
|
325 |
+
|
326 |
+
cv2.putText(img, lbls[0], (10, 40), font, fontScale, (0, 255, 0), 2)
|
327 |
+
|
328 |
+
cv2.putText(img, lbls[1], (10, 70), font, fontScale, (0, 255, 0), 2)
|
329 |
+
|
330 |
+
cv2.putText(img, lbls[2], (10, 100), font, fontScale, (0, 255, 0), 2)
|
331 |
+
|
332 |
+
cv2.putText(img, lbls[3], (10, 130), font, fontScale, (0, 255, 0), 2)
|
333 |
+
|
334 |
+
cv2.putText(img, lbls[4], (10, 160), font, fontScale, (0, 255, 0), 2)
|
335 |
+
|
336 |
+
cv2.imwrite(output_dir_slices+"slice"+str(SLICE_COUNT-(i))+".png", img)
|
337 |
+
|
338 |
+
plt.bar(list(diameterDict.keys()), diameterDict.values(), color='b')
|
339 |
|
340 |
+
plt.title(r"$\bf{Diameter}$" + " " + r"$\bf{Progression}$")
|
341 |
|
|
|
|
|
342 |
|
343 |
+
plt.xlabel('Slice Number')
|
|
|
344 |
|
345 |
+
plt.ylabel('Diameter Measurement (cm)')
|
346 |
+
plt.savefig(output_dir_summary+"diameter_graph.png", dpi=500)
|
347 |
|
348 |
+
print(diameterDict)
|
349 |
+
print(max(diameterDict.items(), key=operator.itemgetter(1))[0])
|
350 |
+
print(diameterDict[max(diameterDict.items(), key=operator.itemgetter(1))[0]])
|
351 |
|
352 |
+
inference_pipeline.max_diameter = diameterDict[max(diameterDict.items(), key=operator.itemgetter(1))[0]]
|
|
|
353 |
|
354 |
+
img = ct_img[SLICE_COUNT-(max(diameterDict.items(), key=operator.itemgetter(1))[0])]
|
355 |
+
img = np.clip(img, -300, 1800)
|
356 |
+
img = self.normalize_img(img) * 255.0
|
357 |
+
img = img.reshape((img.shape[0], img.shape[1], 1))
|
358 |
+
img2 = np.tile(img, (1, 1, 3))
|
359 |
+
img2 = cv2.rotate(img2, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
360 |
|
361 |
+
img1 = cv2.imread(output_dir_slices+'slice'+str(max(diameterDict.items(), key=operator.itemgetter(1))[0])+'.png')
|
362 |
|
363 |
+
border_size = 3
|
364 |
+
img1 = cv2.copyMakeBorder(
|
365 |
+
img1,
|
366 |
+
top=border_size,
|
367 |
+
bottom=border_size,
|
368 |
+
left=border_size,
|
369 |
+
right=border_size,
|
370 |
+
borderType=cv2.BORDER_CONSTANT,
|
371 |
+
value=[0, 244, 0]
|
372 |
+
)
|
373 |
+
img2 = cv2.copyMakeBorder(
|
374 |
+
img2,
|
375 |
+
top=border_size,
|
376 |
+
bottom=border_size,
|
377 |
+
left=border_size,
|
378 |
+
right=border_size,
|
379 |
+
borderType=cv2.BORDER_CONSTANT,
|
380 |
+
value=[244, 0, 0]
|
381 |
+
)
|
382 |
|
383 |
+
vis = np.concatenate((img2, img1), axis=1)
|
384 |
+
cv2.imwrite(output_dir_summary+'out.png', vis)
|
385 |
|
386 |
+
image_folder=output_dir_slices
|
387 |
+
fps=20
|
388 |
+
image_files = [os.path.join(image_folder,img)
|
389 |
+
for img in Tcl().call('lsort', '-dict', os.listdir(image_folder))
|
390 |
+
if img.endswith(".png")]
|
391 |
+
clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(image_files, fps=fps)
|
392 |
+
clip.write_videofile(output_dir_summary+'aaa.mp4')
|
|
|
|
|
|
|
393 |
|
|
|
|
|
394 |
|
395 |
+
def compute_centerline_3d(aorta_segmentation):
|
396 |
+
skeleton = skeletonize_3d(aorta_segmentation)
|
397 |
+
z, y, x = np.where(skeleton)
|
398 |
+
centerline_points = np.vstack((x, y, z)).T
|
399 |
+
centerline_points = centerline_points[centerline_points[:, 0].argsort()]
|
400 |
+
return centerline_points
|
401 |
|
|
|
|
|
|
|
402 |
|
403 |
+
def fit_bspline(centerline_points, smoothness=1e8):
|
404 |
+
x, y, z = centerline_points.T
|
405 |
+
tck, _ = splprep([x, y, z], s=smoothness)
|
406 |
+
return tck
|
407 |
|
|
|
408 |
|
409 |
+
def evaluate_bspline(tck, num_points=1000):
|
410 |
+
u = np.linspace(0, 1, num_points)
|
411 |
+
x, y, z = splev(u, tck)
|
412 |
+
return np.vstack((x, y, z)).T
|
413 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
414 |
|
415 |
+
def interpolate_points(data, num_points=32):
|
416 |
+
x = data[:, 0]
|
417 |
+
y = data[:, 1:]
|
418 |
+
f_y = interp1d(x, y, kind="nearest", fill_value="extrapolate", axis=0)
|
419 |
+
new_x = np.arange(0, num_points)
|
420 |
+
new_y = f_y(new_x)
|
421 |
+
new_data = np.round(np.hstack((new_x.reshape(-1, 1), new_y)))
|
422 |
+
return new_data
|
423 |
|
|
|
424 |
|
425 |
+
def compute_orthogonal_planes(tck, num_points=100):
|
426 |
+
u = np.linspace(0, 1, num_points)
|
427 |
+
points = np.vstack(splev(u, tck)).T
|
428 |
+
tangents = np.vstack(splev(u, tck, der=1)).T
|
429 |
|
430 |
+
normals = tangents / np.linalg.norm(tangents, axis=1)[:, np.newaxis]
|
431 |
|
432 |
+
planes = []
|
433 |
+
for point, normal in zip(points, normals):
|
434 |
+
d = -np.dot(point, normal)
|
435 |
+
planes.append((normal, d))
|
436 |
|
437 |
+
return planes
|
438 |
|
|
|
|
|
|
|
439 |
|
440 |
+
def compute_maximum_diameter(aorta_segmentation, planes):
|
441 |
+
z, y, x = np.where(aorta_segmentation)
|
442 |
+
aorta_points = np.vstack((x, y, z)).T
|
443 |
|
|
|
444 |
|
445 |
+
max_diameters = []
|
446 |
+
intersecting_points_list = []
|
447 |
+
for normal, d in planes:
|
448 |
+
distances = np.dot(aorta_points, normal) + d
|
449 |
+
intersecting_points = aorta_points[np.abs(distances) < 0.5]
|
450 |
|
451 |
+
if len(intersecting_points) < 2:
|
452 |
+
continue
|
453 |
|
454 |
+
dist_matrix = squareform(pdist(intersecting_points))
|
455 |
+
intersecting_points_list.append(intersecting_points)
|
|
|
456 |
|
457 |
+
max_diameter = np.max(dist_matrix)
|
458 |
+
max_diameters.append(max_diameter)
|
|
|
459 |
|
460 |
+
max_diameter_index = np.argmax(max_diameters)
|
461 |
+
max_diameter_in_pixels = max_diameters[max_diameter_index]
|
462 |
+
print(f'Maximum Diameter in Pixels: {max_diameter_in_pixels}')
|
|
|
|
|
|
|
|
|
|
|
463 |
|
464 |
+
diameter_mm = round((max_diameter_in_pixels)*RATIO_PIXEL_TO_MM)
|
465 |
+
print(f'Maximum Diameter in mm: {diameter_mm}')
|
|
|
|
|
|
|
|
|
466 |
|
467 |
+
max_diameters = np.array(max_diameters) * 0.15
|
468 |
+
max_diameter_index = np.argmax(max_diameters)
|
469 |
+
max_diameter_normal, max_diameter_point = planes[max_diameter_index]
|
470 |
+
max_intersecting_points = intersecting_points_list[max_diameter_index]
|
471 |
+
print("max_diameter_normal type:", type(max_diameter_normal))
|
472 |
+
print("max_diameter_normal shape:", np.shape(max_diameter_normal))
|
473 |
+
print("max_diameter_point type:", type(max_diameter_point))
|
474 |
+
print("max_diameter_point shape:", np.shape(max_diameter_point))
|
475 |
+
|
476 |
+
print("max intersecting points type:", type(max_intersecting_points))
|
477 |
+
print("max intersecting points shape:", np.shape(max_intersecting_points))
|
478 |
+
print("max intersecting points:", max_intersecting_points)
|
479 |
+
|
480 |
+
return (
|
481 |
+
max_diameters,
|
482 |
+
max_diameter_point,
|
483 |
+
max_diameter_normal,
|
484 |
+
max_intersecting_points,
|
485 |
+
)
|
486 |
+
|
487 |
+
|
488 |
+
def plot_2d_planar_reconstruction(
|
489 |
+
image,
|
490 |
+
segmentation,
|
491 |
+
interpolated_points,
|
492 |
+
max_diameter_point,
|
493 |
+
max_diameter_normal,
|
494 |
+
max_intersecting_points,
|
495 |
+
):
|
496 |
+
fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(15, 10))
|
497 |
+
|
498 |
+
sagittal_index = interpolated_points[:, 2].astype(int)
|
499 |
+
image_2d = image[sagittal_index, :, range(image.shape[2])]
|
500 |
+
seg_2d = segmentation[sagittal_index, :, range(image.shape[2])]
|
501 |
+
|
502 |
+
# axs[0].imshow(image_2d, cmap="gray")
|
503 |
+
# axs[0].imshow(seg_2d, cmap="jet", alpha=0.3)
|
504 |
+
axs[0].scatter(
|
505 |
+
interpolated_points[:, 1].astype(int),
|
506 |
+
interpolated_points[:, 0].astype(int),
|
507 |
+
color="red",
|
508 |
+
s=1,
|
509 |
+
)
|
510 |
+
axs[0].plot(
|
511 |
+
max_intersecting_points[:, 1].astype(int),
|
512 |
+
max_intersecting_points[:, 0].astype(int),
|
513 |
+
color="blue",
|
514 |
+
)
|
515 |
+
|
516 |
+
coronal_index = interpolated_points[:, 1].astype(int)
|
517 |
+
image_2d = image[:, coronal_index, range(image.shape[2])].T
|
518 |
+
seg_2d = segmentation[:, coronal_index, range(image.shape[2])].T
|
519 |
+
|
520 |
+
# axs[1].imshow(image_2d, cmap="gray")
|
521 |
+
# axs[1].imshow(seg_2d, cmap="jet", alpha=0.3)
|
522 |
+
axs[1].scatter(
|
523 |
+
interpolated_points[:, 2].astype(int),
|
524 |
+
interpolated_points[:, 0].astype(int),
|
525 |
+
color="red",
|
526 |
+
s=1,
|
527 |
+
)
|
528 |
+
axs[1].plot(
|
529 |
+
max_intersecting_points[:, 2].astype(int),
|
530 |
+
max_intersecting_points[:, 0].astype(int),
|
531 |
+
color="blue",
|
532 |
+
)
|
533 |
+
|
534 |
+
plt.savefig(output_dir_summary+"planar_reconstruction.png")
|
535 |
+
|
536 |
+
output_dir = inference_pipeline.output_dir_segmentations
|
537 |
+
|
538 |
+
segmentation = nib.load(
|
539 |
+
os.path.join(output_dir, "converted_dcm.nii.gz")
|
540 |
)
|
541 |
+
image = nib.load(
|
542 |
+
os.path.join(output_dir, "spine.nii.gz")
|
|
|
|
|
|
|
|
|
|
|
|
|
543 |
)
|
544 |
|
545 |
+
image = resample_to_output(image, (1.5, 1.5, 1.5))
|
546 |
+
segmentation = resample_to_output(segmentation, (1.5, 1.5, 1.5), order=0)
|
547 |
+
image = image.get_fdata()
|
548 |
+
segmentation = segmentation.get_fdata()
|
549 |
+
|
550 |
+
segmentation[segmentation == 42] = 1
|
551 |
+
|
552 |
+
print(segmentation.shape)
|
553 |
+
print(np.unique(segmentation))
|
554 |
+
centerline_points = compute_centerline_3d(segmentation)
|
555 |
+
print(centerline_points)
|
556 |
+
tck = fit_bspline(centerline_points)
|
557 |
+
evaluated_points = evaluate_bspline(tck)
|
558 |
+
print(evaluated_points)
|
559 |
+
interpolated_points = interpolate_points(evaluated_points, image.shape[2])
|
560 |
+
print(interpolated_points)
|
561 |
+
planes = compute_orthogonal_planes(tck)
|
562 |
+
(
|
563 |
+
cmax_diameters,
|
564 |
+
max_diameter_point,
|
565 |
+
max_diameter_normal,
|
566 |
+
max_intersecting_points,
|
567 |
+
) = compute_maximum_diameter(segmentation, planes)
|
568 |
+
plot_2d_planar_reconstruction(
|
569 |
+
image,
|
570 |
+
segmentation,
|
571 |
+
interpolated_points,
|
572 |
+
max_diameter_point,
|
573 |
+
max_diameter_normal,
|
574 |
+
max_intersecting_points,
|
575 |
)
|
|
|
576 |
|
577 |
return {}
|
578 |
|
|
|
598 |
"""Save results to a CSV file."""
|
599 |
_, filename = os.path.split(self.dicom_series_path)
|
600 |
data = [[filename, str(self.max_diameter)]]
|
601 |
+
df = pd.DataFrame(data, columns=['Filename', 'Max Diameter'])
|
602 |
+
df.to_csv(os.path.join(self.csv_output_dir, "aorta_metrics.csv"), index=False)
|