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)
|