|
a |
|
b/src/LFBNet/preprocessing/preprocessing.py |
|
|
1 |
""" Script to preprocess a given FDG PET image in .nii. |
|
|
2 |
""" |
|
|
3 |
# Import libraries |
|
|
4 |
import glob |
|
|
5 |
import os |
|
|
6 |
|
|
|
7 |
import numpy as np |
|
|
8 |
from numpy import ndarray |
|
|
9 |
from numpy.random import seed |
|
|
10 |
from tqdm import tqdm |
|
|
11 |
from typing import List, Tuple |
|
|
12 |
import matplotlib.pyplot as plt |
|
|
13 |
import warnings |
|
|
14 |
|
|
|
15 |
from skimage.transform import resize |
|
|
16 |
import scipy.ndimage |
|
|
17 |
import nibabel as nib |
|
|
18 |
|
|
|
19 |
# seed random number generator |
|
|
20 |
seed(1) |
|
|
21 |
|
|
|
22 |
|
|
|
23 |
def directory_exist(dir_check: str = None): |
|
|
24 |
""" Check if the given path exists. |
|
|
25 |
|
|
|
26 |
Args: |
|
|
27 |
dir_check: directory path to check |
|
|
28 |
Raises: |
|
|
29 |
Raises exception if the given directory path doesn't exist. |
|
|
30 |
""" |
|
|
31 |
if os.path.exists(dir_check): |
|
|
32 |
# print("The directory %s does exist \n" % dir_check) |
|
|
33 |
pass |
|
|
34 |
else: |
|
|
35 |
raise Exception("Please provide the correct path to the processed data: %s not found \n" % (dir_check)) |
|
|
36 |
|
|
|
37 |
|
|
|
38 |
def generate_mip_show(pet: int = None, gt: int = None, identifier: str = None): |
|
|
39 |
""" Display the MIP images of a given 3D FDG PET image , and corresponding ground truth gt. |
|
|
40 |
|
|
|
41 |
Args: |
|
|
42 |
pet: 3D array of PET images |
|
|
43 |
gt: 3D array of reference or ground truth lymphoma segmentation. |
|
|
44 |
identifier: Name (identifier) of the patient. |
|
|
45 |
""" |
|
|
46 |
# consider axis 0 for sagittal and axis 1 for coronal views |
|
|
47 |
pet, gt = np.array(pet), np.array(gt) |
|
|
48 |
pet = [np.amax(pet, axis=0), np.amax(pet, axis=1)] |
|
|
49 |
gt = [np.amax(gt, axis=0), np.amax(gt, axis=1)] |
|
|
50 |
try: |
|
|
51 |
pet = np.squeeze(pet) |
|
|
52 |
gt = np.squeeze(gt) |
|
|
53 |
except: |
|
|
54 |
pass |
|
|
55 |
|
|
|
56 |
img_gt = np.concatenate((pet, gt, pet), axis=0) |
|
|
57 |
display_image(img_gt, identifier=identifier) |
|
|
58 |
|
|
|
59 |
|
|
|
60 |
def display_image(im_display: ndarray, identifier: str = None): |
|
|
61 |
""" display given array of images. |
|
|
62 |
|
|
|
63 |
Args: |
|
|
64 |
im_display: array of images to show |
|
|
65 |
identifier: patient name to display as title |
|
|
66 |
""" |
|
|
67 |
plt.figure(figsize=(10, 1)) |
|
|
68 |
plt.subplots_adjust(hspace=0.015) |
|
|
69 |
plt.suptitle("Showing image: " + str(identifier), fontsize=12, y=0.95) |
|
|
70 |
# loop through the length of tickers and keep track of index |
|
|
71 |
for n, im in enumerate(im_display): |
|
|
72 |
# add a new subplot iteratively |
|
|
73 |
plt.subplot(int(len(im_display) // 2), 2, n + 1) |
|
|
74 |
plt.imshow(np.log(im + 1)) |
|
|
75 |
plt.show() |
|
|
76 |
|
|
|
77 |
|
|
|
78 |
def get_pet_gt(current_dir: str = None): |
|
|
79 |
""" Read pet and corresponding reference images. |
|
|
80 |
|
|
|
81 |
Args: |
|
|
82 |
current_dir: directory path to the PET and corresponding ground truth image. |
|
|
83 |
Returns: |
|
|
84 |
Returns array of pet and ground truth images. |
|
|
85 |
|
|
|
86 |
""" |
|
|
87 |
|
|
|
88 |
def get_nii_files(nii_path): |
|
|
89 |
""" Read .nii data in the given nii_path. |
|
|
90 |
if there are more .nii or .nii.gz file inside the nii_path directory, the function will return the |
|
|
91 |
first read image. |
|
|
92 |
|
|
|
93 |
Args: |
|
|
94 |
nii_path: directory path to the .nii or .nii.gz file. |
|
|
95 |
|
|
|
96 |
Returns: |
|
|
97 |
Returns loaded nibble format if it exists, else it returns empty array. |
|
|
98 |
|
|
|
99 |
""" |
|
|
100 |
# more than one .nii or .nii.gz is found in the folder the first will be returned |
|
|
101 |
full_path = [path_i for path_i in glob.glob(str(nii_path) + "/*.nii.gz")] |
|
|
102 |
|
|
|
103 |
if not len(full_path): # if no file exists that ends wtih .nii.gz |
|
|
104 |
full_path = [path_i for path_i in glob.glob(str(nii_path) + "/*.nii")] |
|
|
105 |
|
|
|
106 |
if not len(full_path): |
|
|
107 |
get_image = None # raise Exception("No .nii or .nii.gz found in %s dirctory" % nii_path) |
|
|
108 |
|
|
|
109 |
else: |
|
|
110 |
print("%d files found in %s \t the first will be read " % (len(full_path), nii_path)) |
|
|
111 |
print("reading ... %s" % full_path[0]) |
|
|
112 |
get_image = nib.load(full_path[0]) |
|
|
113 |
|
|
|
114 |
return get_image |
|
|
115 |
|
|
|
116 |
# all folders files |
|
|
117 |
# all_ = [path_i for path_i in glob.glob(str(nii_path) + "/*.nii")] |
|
|
118 |
|
|
|
119 |
# Get pet image |
|
|
120 |
# if "pet" in str() |
|
|
121 |
pet_path = str(current_dir) + "/pet/" |
|
|
122 |
pet = get_nii_files(pet_path) |
|
|
123 |
|
|
|
124 |
# Get gt image |
|
|
125 |
try: |
|
|
126 |
# if the ground truth exists |
|
|
127 |
gt_path = str(current_dir) + "/gt/" |
|
|
128 |
gt = get_nii_files(gt_path) |
|
|
129 |
except: |
|
|
130 |
# if the ground truth does not exist |
|
|
131 |
gt = None |
|
|
132 |
|
|
|
133 |
return [pet, gt] |
|
|
134 |
|
|
|
135 |
|
|
|
136 |
def resize_nii_to_desired_spacing( |
|
|
137 |
data: int = None, data_spacing: Tuple[float] = None, desired_spacing: ndarray = None, |
|
|
138 |
interpolation_order_value: int = None |
|
|
139 |
): |
|
|
140 |
""" resizes a given input data into the desired spacing using the specified interpolation order. |
|
|
141 |
|
|
|
142 |
Args: |
|
|
143 |
data: array of input data to resize. |
|
|
144 |
data_spacing: original input data spacing. |
|
|
145 |
desired_spacing: required output data spacing. |
|
|
146 |
interpolation_order_value: interpolation order. E.g., 0 for binary images, and 3 for cubic interpolation. |
|
|
147 |
|
|
|
148 |
Returns: |
|
|
149 |
resized data. |
|
|
150 |
|
|
|
151 |
""" |
|
|
152 |
if desired_spacing is None: |
|
|
153 |
desired_spacing_x, desired_spacing_y, desired_spacing_z = [4.0, 4.0, 4.0] |
|
|
154 |
else: |
|
|
155 |
desired_spacing_x, desired_spacing_y, desired_spacing_z = desired_spacing |
|
|
156 |
|
|
|
157 |
print("Given data spacing \t Desired spacing \n") |
|
|
158 |
print(data_spacing, "\t", end="") |
|
|
159 |
print(desired_spacing, "\n") |
|
|
160 |
|
|
|
161 |
if not isinstance(data, list): |
|
|
162 |
data = np.array(data) |
|
|
163 |
|
|
|
164 |
# New resolution, consider at least the input image is two-dimensional |
|
|
165 |
new_x_resolution = np.ceil(data.shape[0] * (data_spacing[0] / desired_spacing_x)) |
|
|
166 |
new_y_resolution = np.ceil(data.shape[1] * (data_spacing[1] / desired_spacing_y)) |
|
|
167 |
|
|
|
168 |
print('resizing') |
|
|
169 |
print(np.array(data).shape) |
|
|
170 |
|
|
|
171 |
if len(data_spacing) == 3 and len(np.squeeze(data).shape) == 3: # 3D input image |
|
|
172 |
new_z_resolution = np.ceil(data.shape[2] * (data_spacing[2] / desired_spacing_z)) |
|
|
173 |
|
|
|
174 |
# resize to new image resolution |
|
|
175 |
image_resized = resize( |
|
|
176 |
data, (new_x_resolution, new_y_resolution, new_z_resolution), order=interpolation_order_value, |
|
|
177 |
preserve_range=True, anti_aliasing=False |
|
|
178 |
) |
|
|
179 |
|
|
|
180 |
else: # if the given input image is 2D |
|
|
181 |
image_resized = resize( |
|
|
182 |
data, (new_x_resolution, new_y_resolution), order=interpolation_order_value, preserve_range=True, |
|
|
183 |
anti_aliasing=False |
|
|
184 |
) |
|
|
185 |
|
|
|
186 |
return image_resized |
|
|
187 |
|
|
|
188 |
|
|
|
189 |
def z_score(image): |
|
|
190 |
""" z-score operation on the input data. |
|
|
191 |
|
|
|
192 |
Args: |
|
|
193 |
image: input data to apply the z-score. |
|
|
194 |
|
|
|
195 |
Returns: |
|
|
196 |
Standardized value of the input using z-score. Z-score = (input - mean(input))/(std(input)) |
|
|
197 |
|
|
|
198 |
""" |
|
|
199 |
image = image.copy() |
|
|
200 |
if not isinstance(image, ndarray): |
|
|
201 |
image = np.array(image) |
|
|
202 |
|
|
|
203 |
image_nan = np.where(image > 0, image, np.nan) |
|
|
204 |
means = np.nanmean(image_nan, axis=(image.shape)) |
|
|
205 |
stds = np.nanstd(image_nan, axis=(image.shape)) |
|
|
206 |
image_norm = (image - means) / (stds + 1e-8) |
|
|
207 |
|
|
|
208 |
return image_norm |
|
|
209 |
|
|
|
210 |
|
|
|
211 |
# cropping from the center based: |
|
|
212 |
def crop_nii_to_desired_resolution(data: ndarray = None, cropped_resolution: List[int] = None): |
|
|
213 |
""" Crops the input data in the given cropped resolution. |
|
|
214 |
|
|
|
215 |
Args: |
|
|
216 |
data: input data to be cropped. |
|
|
217 |
cropped_resolution: desired output resolution. |
|
|
218 |
|
|
|
219 |
Returns: |
|
|
220 |
Returns cropped data of size cropped resolution. |
|
|
221 |
|
|
|
222 |
""" |
|
|
223 |
# |
|
|
224 |
if not isinstance(data, list): # if data is not array change it to array |
|
|
225 |
data = np.array(data) |
|
|
226 |
|
|
|
227 |
try: |
|
|
228 |
data = np.squeeze(data) |
|
|
229 |
except: |
|
|
230 |
pass |
|
|
231 |
|
|
|
232 |
if cropped_resolution is not None: |
|
|
233 |
cropped_resolution = [128, 128, 256] |
|
|
234 |
|
|
|
235 |
print("\n Initial data size \t Cropped data size ") |
|
|
236 |
print(data.shape, "\t", end=" ") |
|
|
237 |
print(cropped_resolution, "\n") |
|
|
238 |
|
|
|
239 |
if len(cropped_resolution) == 3 and len(data.shape) == 3: # 3D data |
|
|
240 |
x, y, z = data.shape |
|
|
241 |
else: |
|
|
242 |
raise Exception("Input image not !") |
|
|
243 |
|
|
|
244 |
# start cropping : get middle x, y, z values by dividing by 2 and subtract the desired center of image resolution |
|
|
245 |
start_cropping_at_x = (x // 2 - (cropped_resolution[0] // 2)) |
|
|
246 |
start_cropping_at_y = (y // 2 - (cropped_resolution[1] // 2)) |
|
|
247 |
start_cropping_at_z = (z // 2 - (cropped_resolution[2] // 2)) |
|
|
248 |
|
|
|
249 |
# check for off sets: mean the new cropping resolution is bigger than the input image's resolution |
|
|
250 |
off_set_x, off_set_y, off_set_z = 0, 0, 0 |
|
|
251 |
if start_cropping_at_x < 0: |
|
|
252 |
off_set_x = np.abs(start_cropping_at_x) |
|
|
253 |
# set the first pixel at zero |
|
|
254 |
start_cropping_at_x = 0 |
|
|
255 |
if start_cropping_at_y < 0: |
|
|
256 |
off_set_y = np.abs(start_cropping_at_y) |
|
|
257 |
# set the first pixel at zero |
|
|
258 |
start_cropping_at_y = 0 |
|
|
259 |
if start_cropping_at_z < 0: |
|
|
260 |
off_set_z = np.abs(start_cropping_at_z) |
|
|
261 |
# set the first pixel at zero |
|
|
262 |
start_cropping_at_z = 0 |
|
|
263 |
else: |
|
|
264 |
# take [0:cropped_resolution[0]] |
|
|
265 |
# Patients are given [x, y, z] when we say z it starts at zero (leg) to z (head). |
|
|
266 |
start_cropping_at_z = 2 * (z // 2 - (cropped_resolution[2] // 2)) |
|
|
267 |
|
|
|
268 |
npad = ((off_set_x, off_set_x), (off_set_y, off_set_y), (2 * off_set_z, 0)) |
|
|
269 |
|
|
|
270 |
# set zero value to the off set pixels mode='constant', |
|
|
271 |
data = np.pad(data, pad_width=npad, constant_values=0) |
|
|
272 |
|
|
|
273 |
# cropping to the given or set cropping resolution |
|
|
274 |
data = data[start_cropping_at_x:start_cropping_at_x + cropped_resolution[0], |
|
|
275 |
start_cropping_at_y:start_cropping_at_y + cropped_resolution[1], |
|
|
276 |
start_cropping_at_z:start_cropping_at_z + cropped_resolution[2]] |
|
|
277 |
|
|
|
278 |
return data |
|
|
279 |
|
|
|
280 |
|
|
|
281 |
def save_nii_images( |
|
|
282 |
image: List[ndarray] = None, affine: ndarray = None, path_save: str = None, identifier: str = None, |
|
|
283 |
name: List[str] = None |
|
|
284 |
): |
|
|
285 |
""" Save given images into the given directory. If no saving directory is given it will save into |
|
|
286 |
./data/predicted/' directory. |
|
|
287 |
|
|
|
288 |
Args: |
|
|
289 |
image: data to save. |
|
|
290 |
affine: affine value. |
|
|
291 |
path_save: saving directory path. |
|
|
292 |
identifier: unique name of the case. |
|
|
293 |
name: name of the case to read. |
|
|
294 |
Returns: |
|
|
295 |
Saved image. |
|
|
296 |
""" |
|
|
297 |
try: |
|
|
298 |
directory_exist(path_save) |
|
|
299 |
except: |
|
|
300 |
try: |
|
|
301 |
os.mkdir(path_save) |
|
|
302 |
except: |
|
|
303 |
if not os.path.exists("../data"): |
|
|
304 |
os.mkdir("../data") |
|
|
305 |
|
|
|
306 |
if not os.path.exists('../data/predicted/'): |
|
|
307 |
os.mkdir('../data/predicted/') |
|
|
308 |
|
|
|
309 |
path_save = '../data/predicted/' |
|
|
310 |
if identifier is not None: # associate file name e.g. patient name |
|
|
311 |
dir = os.path.join(path_save, str(identifier)) |
|
|
312 |
else: |
|
|
313 |
dir = path_save |
|
|
314 |
|
|
|
315 |
if not os.path.exists(dir): |
|
|
316 |
os.mkdir(dir) # os.mkdir("./" + str(identifier)) |
|
|
317 |
|
|
|
318 |
# print('saving it to %s\n' % dir) |
|
|
319 |
# .nii name, e.g. pet, gt, but if not given the base name of given directory |
|
|
320 |
if name is None: |
|
|
321 |
name = ['image_' + str(os.path.basename(dir)).split('.')[0]] |
|
|
322 |
|
|
|
323 |
if affine is None: |
|
|
324 |
affine = np.diag([4, 4, 4, 1]) |
|
|
325 |
|
|
|
326 |
# image = np.flip(image, axis=-1) |
|
|
327 |
for select_image in range(len(image)): |
|
|
328 |
im_ = nib.Nifti1Image(image[select_image], affine) |
|
|
329 |
save_to = str(dir) + "/" + str(name[select_image]) |
|
|
330 |
im_.to_filename(save_to) |
|
|
331 |
|
|
|
332 |
|
|
|
333 |
def generate_mip_from_3D(given_3d_nii: ndarray = None, mip_axis: int = 0): |
|
|
334 |
""" Projects a 3D data into MIP along the selected MIP axis. |
|
|
335 |
|
|
|
336 |
Args: |
|
|
337 |
given_3d_nii: 3D array to project into MIP. |
|
|
338 |
mip_axis: Projecting axis. |
|
|
339 |
|
|
|
340 |
Returns: |
|
|
341 |
Returns MIP image. |
|
|
342 |
|
|
|
343 |
""" |
|
|
344 |
if given_3d_nii is None: |
|
|
345 |
raise Exception("3D image not given") |
|
|
346 |
|
|
|
347 |
if not isinstance(given_3d_nii, list): |
|
|
348 |
given_3d_nii = np.array(given_3d_nii) |
|
|
349 |
|
|
|
350 |
mip = np.amax(given_3d_nii, axis=mip_axis) |
|
|
351 |
|
|
|
352 |
mip = np.asarray(mip) |
|
|
353 |
|
|
|
354 |
return mip |
|
|
355 |
|
|
|
356 |
|
|
|
357 |
def transform_coordinate_space(modality_1, modality_2, mode='nearest'): |
|
|
358 |
""" Apply affine transformation on given data using affine from the second data. |
|
|
359 |
|
|
|
360 |
Adapted from: https://gist.github.com/zivy/79d7ee0490faee1156c1277a78e4a4c4 |
|
|
361 |
Transfers coordinate space from modality_2 to modality_1 |
|
|
362 |
Input images are in nifty/nibabel format (.nii or .nii.gz) |
|
|
363 |
|
|
|
364 |
|
|
|
365 |
Args: |
|
|
366 |
modality_1: reference modality. |
|
|
367 |
modality_2: image modality to apply affine transformation. |
|
|
368 |
|
|
|
369 |
Returns: |
|
|
370 |
Returns affine transformed form of modality_2 using modality_1 as reference. |
|
|
371 |
|
|
|
372 |
""" |
|
|
373 |
aff_t1 = modality_1.affine |
|
|
374 |
try: |
|
|
375 |
aff_t2 = modality_2.affine |
|
|
376 |
except: |
|
|
377 |
aff_t2 = aff_t1 |
|
|
378 |
|
|
|
379 |
inv_af_2 = np.linalg.inv(aff_t2) |
|
|
380 |
out_shape = modality_1.get_fdata().shape |
|
|
381 |
|
|
|
382 |
# desired transformation |
|
|
383 |
T = inv_af_2.dot(aff_t1) |
|
|
384 |
|
|
|
385 |
# apply transformation |
|
|
386 |
transformed_img = scipy.ndimage.affine_transform(modality_2.get_fdata(), T, output_shape=out_shape, mode=mode) |
|
|
387 |
return transformed_img |
|
|
388 |
|
|
|
389 |
|
|
|
390 |
# read PET and GT images |
|
|
391 |
def read_pet_gt_resize_crop_save_as_3d_andor_mip( |
|
|
392 |
data_path: str = None, data_name: str = None, saving_dir: str = None, save_3D: bool = False, crop: bool = True, |
|
|
393 |
output_resolution: List[int] = None, desired_spacing: List[float] = None, generate_mip: bool = False |
|
|
394 |
): |
|
|
395 |
""" Read pet and ground truth images from teh input data path. It also apply resize, and cropping operations. |
|
|
396 |
|
|
|
397 |
Args: |
|
|
398 |
data_path: directory to the raw 3D pet and ground truth .nii fies. |
|
|
399 |
data_name: unique the whole data identifier. |
|
|
400 |
saving_dir: directory path to save the processed images. |
|
|
401 |
save_3D: Save the processed 3D data or not. |
|
|
402 |
crop: apply cropping operations. |
|
|
403 |
output_resolution: desired output resolution. |
|
|
404 |
desired_spacing: required to be output spacing. |
|
|
405 |
generate_mip: project the processed 3D data into MIPs. |
|
|
406 |
|
|
|
407 |
Returns: |
|
|
408 |
Returns processed data. |
|
|
409 |
|
|
|
410 |
""" |
|
|
411 |
if output_resolution is not None: |
|
|
412 |
# output resized and cropped image resolution |
|
|
413 |
rows, columns, depths = output_resolution |
|
|
414 |
else: # default values |
|
|
415 |
# output resized and cropped image resolution= |
|
|
416 |
output_resolution = [128, 128, 256] |
|
|
417 |
|
|
|
418 |
if data_name is None: |
|
|
419 |
data_name = "unknown_data" |
|
|
420 |
|
|
|
421 |
''' |
|
|
422 |
get directory name or patient id of the PET and gt images |
|
|
423 |
Assuming the file structure is : |
|
|
424 |
---patient_id |
|
|
425 |
-- PET |
|
|
426 |
-- *.nii or .nii.gz |
|
|
427 |
-- gt |
|
|
428 |
-- *.nii or .nii.gz |
|
|
429 |
''' |
|
|
430 |
|
|
|
431 |
# check if the directory exist |
|
|
432 |
directory_exist(data_path) |
|
|
433 |
|
|
|
434 |
# by default the processed 3d and 2D MIP will be saved into the 'data' subdirectory, respectively with name tags |
|
|
435 |
# as '_default_3d_dir' and '_default_MIP_dir' |
|
|
436 |
|
|
|
437 |
def create_directory(directory_to_create: list): |
|
|
438 |
""" |
|
|
439 |
|
|
|
440 |
:param directory_to_create: |
|
|
441 |
""" |
|
|
442 |
for directory in directory_to_create: |
|
|
443 |
if not os.path.exists(directory): |
|
|
444 |
os.mkdir(directory) # os.mkdir("./" + str(identifier)) |
|
|
445 |
|
|
|
446 |
# if saving_dir is None: |
|
|
447 |
# if not os.path.exists("../data"): |
|
|
448 |
# os.mkdir("../data") |
|
|
449 |
# |
|
|
450 |
# saving_dir_3d = "../data/" + str(data_name) + "_default_3d_dir" |
|
|
451 |
# |
|
|
452 |
# # create directory in the data with name resized_cropped_MIP_default |
|
|
453 |
# saving_dir_mip = "../data/" + str(data_name) + "_default_MIP_dir" |
|
|
454 |
# |
|
|
455 |
# create_directory([saving_dir_3d, saving_dir_mip]) |
|
|
456 |
# |
|
|
457 |
# # If the saving directory is given, a sub folder for the processed 3d and 2D MIP will be created |
|
|
458 |
# else: |
|
|
459 |
data_3d = str(data_name) + "_default_3d_dir_" |
|
|
460 |
data_mip = str(data_name) + "_default_MIP_dir" |
|
|
461 |
saving_dir_3d = os.path.join(saving_dir, data_3d) |
|
|
462 |
saving_dir_mip = os.path.join(saving_dir, data_mip) |
|
|
463 |
|
|
|
464 |
create_directory([saving_dir_3d, saving_dir_mip]) |
|
|
465 |
|
|
|
466 |
# all patient ids |
|
|
467 |
case_ids = os.listdir(data_path) |
|
|
468 |
|
|
|
469 |
if not len(case_ids): # reise exception if the directory is empty |
|
|
470 |
raise Exception("Directory %s is empty" % data_path) |
|
|
471 |
else: # read the pet and gt images |
|
|
472 |
print("Continuing to read %d cases" % len(case_ids)) |
|
|
473 |
|
|
|
474 |
# resize, to given resolution |
|
|
475 |
if desired_spacing is None: |
|
|
476 |
desired_spacing = [4.0, 4.0, 4.0] |
|
|
477 |
|
|
|
478 |
# print where will be saved the 3d and mip |
|
|
479 |
print("\n") |
|
|
480 |
print(40 * "**==**") |
|
|
481 |
print("3D resized and cropped images will be saved to %s, if set save" % saving_dir_3d) |
|
|
482 |
print("Generated MIPs will be saved to %s, if set to save" % saving_dir_mip) |
|
|
483 |
print(40 * "**==**") |
|
|
484 |
print("\n") |
|
|
485 |
|
|
|
486 |
for image_name in tqdm(list(case_ids)): |
|
|
487 |
# if image_name in images: |
|
|
488 |
current_id = os.path.join(data_path, image_name) |
|
|
489 |
# read, resize, crop and save as 3D |
|
|
490 |
pet, gt = get_pet_gt(current_id) |
|
|
491 |
|
|
|
492 |
# resolution |
|
|
493 |
res_pet = pet.header.get_zooms() |
|
|
494 |
res_pet = tuple([float(round(res, 2)) for res in list(res_pet[:3])]) |
|
|
495 |
affine = pet.affine |
|
|
496 |
print(f'Size of the PET input image: {np.asanyarray(pet.dataobj).shape}') |
|
|
497 |
|
|
|
498 |
# if there is a ground truth: |
|
|
499 |
if gt is not None: |
|
|
500 |
res_gt = gt.header.get_zooms() |
|
|
501 |
res_gt = tuple([float(round(res, 2)) for res in list(res_gt[:3])]) |
|
|
502 |
|
|
|
503 |
# check if pet and gt are on the same spacing, otherwise gt could be generated from CT images |
|
|
504 |
if not res_pet[:3] == res_gt[:3]: # assert (res_pet == res_gt) |
|
|
505 |
print("pet, and gt resolutions, respectively \n") |
|
|
506 |
print(res_pet, "\t", res_gt) |
|
|
507 |
warnings.warn("Pet and gt have different spacing, Alignment continue...") |
|
|
508 |
# apply affine transformation to move the gt to the PET space, |
|
|
509 |
# probably the gt was generated from the CT images |
|
|
510 |
gt = transform_coordinate_space(pet, gt, mode='constant') |
|
|
511 |
# raise Exception('Ground truth and pet images are not on the same spacing') |
|
|
512 |
gt[gt >= 1] = 1 |
|
|
513 |
gt[gt < 1] = 0 |
|
|
514 |
else: |
|
|
515 |
gt = np.asanyarray(gt.dataobj) |
|
|
516 |
|
|
|
517 |
""" |
|
|
518 |
For remarc data the ground truth is inverted along the z-axis |
|
|
519 |
""" |
|
|
520 |
if data_name == "remarc": |
|
|
521 |
gt = np.flip(gt, axis=-1) |
|
|
522 |
|
|
|
523 |
gt = resize_nii_to_desired_spacing( |
|
|
524 |
gt, data_spacing=res_pet, desired_spacing=desired_spacing, interpolation_order_value=0 |
|
|
525 |
) |
|
|
526 |
|
|
|
527 |
pet = np.asanyarray(pet.dataobj) |
|
|
528 |
# if the given image has stacked as channel example one image in remarc : 175x175x274x2 |
|
|
529 |
if pet.shape[-1] == 2: |
|
|
530 |
pet = pet[..., 0] |
|
|
531 |
|
|
|
532 |
# generate_mip_show(pet, gt, identifier=str(image_name)) |
|
|
533 |
|
|
|
534 |
pet = resize_nii_to_desired_spacing( |
|
|
535 |
pet, data_spacing=res_pet, desired_spacing=desired_spacing, interpolation_order_value=3 |
|
|
536 |
) |
|
|
537 |
|
|
|
538 |
''' |
|
|
539 |
if most data are with brain images at the very top, avoid cropping the brain images,instead crop from bottom |
|
|
540 |
or the leg part |
|
|
541 |
''' |
|
|
542 |
if gt is not None: |
|
|
543 |
if str(data_name).lower() == 'lnh': |
|
|
544 |
# flip left to right to mach lnh data to remarc |
|
|
545 |
gt = np.flip(gt, axis=-1) |
|
|
546 |
|
|
|
547 |
crop_zero_above_brain = True |
|
|
548 |
if crop_zero_above_brain: |
|
|
549 |
# remove all zero pixels just before the brian image |
|
|
550 |
xs, ys, zs = np.where(pet != 0) |
|
|
551 |
if len(zs): # use zs for cropping the ground truth data also |
|
|
552 |
pet = pet[:, :, min(zs):] |
|
|
553 |
|
|
|
554 |
# if gt is None: |
|
|
555 |
# generate_mip_show(pet, pet, identifier=str(image_name)) |
|
|
556 |
# else: |
|
|
557 |
# generate_mip_show(pet, gt, identifier=str(image_name)) |
|
|
558 |
|
|
|
559 |
if crop: |
|
|
560 |
pet = crop_nii_to_desired_resolution(pet.copy(), cropped_resolution=output_resolution.copy()) |
|
|
561 |
|
|
|
562 |
if gt is not None: |
|
|
563 |
# remove the zero values of pet image above the brain |
|
|
564 |
if len(zs): # use zs for cropping the ground truth data also |
|
|
565 |
gt = gt[:, :, min(zs):] |
|
|
566 |
|
|
|
567 |
gt = crop_nii_to_desired_resolution(gt.copy(), cropped_resolution=output_resolution.copy()) |
|
|
568 |
|
|
|
569 |
# if gt is None: |
|
|
570 |
# generate_mip_show(pet, pet, identifier=str(image_name)) |
|
|
571 |
# else: |
|
|
572 |
# generate_mip_show(pet, gt, identifier=str(image_name)) |
|
|
573 |
|
|
|
574 |
if save_3D: |
|
|
575 |
# output image affine |
|
|
576 |
affine = np.diag([desired_spacing[0], desired_spacing[1], desired_spacing[2], 1]) |
|
|
577 |
if gt is not None: |
|
|
578 |
save_nii_images( |
|
|
579 |
[pet, gt], affine=affine, path_save=saving_dir_3d, identifier=str(image_name), |
|
|
580 |
name=['pet', 'ground_truth'] |
|
|
581 |
) |
|
|
582 |
else: |
|
|
583 |
save_nii_images([pet], affine=affine, path_save=saving_dir_3d, identifier=str(image_name), name=['pet']) |
|
|
584 |
|
|
|
585 |
# generate Sagittal and coronal MIPs |
|
|
586 |
if generate_mip: |
|
|
587 |
for sagittal_coronal in range(2): |
|
|
588 |
pet_mip = generate_mip_from_3D(pet, mip_axis=int(sagittal_coronal)) # pet mip |
|
|
589 |
|
|
|
590 |
# assuming sagittal is on axis 0, and coronal is on axis 1 |
|
|
591 |
if sagittal_coronal == 0: # sagittal |
|
|
592 |
naming_ = "sagittal" |
|
|
593 |
elif sagittal_coronal == 1: |
|
|
594 |
naming_ = "coronal" |
|
|
595 |
|
|
|
596 |
if gt is not None: |
|
|
597 |
gt_mip = generate_mip_from_3D(gt, mip_axis=int(sagittal_coronal)) # gt mip |
|
|
598 |
# save the generated MIP |
|
|
599 |
save_nii_images( |
|
|
600 |
[pet_mip, gt_mip], affine, path_save=saving_dir_mip, identifier=str(image_name), |
|
|
601 |
name=['pet_' + str(naming_), 'ground_truth_' + str(naming_)] |
|
|
602 |
) |
|
|
603 |
else: |
|
|
604 |
# save the generated MIP |
|
|
605 |
save_nii_images( |
|
|
606 |
[pet_mip], affine, path_save=saving_dir_mip, identifier=str(image_name), |
|
|
607 |
name=['pet_' + str(naming_)] |
|
|
608 |
) |
|
|
609 |
return saving_dir_mip |
|
|
610 |
|
|
|
611 |
|
|
|
612 |
# Read .nii files using itk |
|
|
613 |
if __name__ == '__main__': |
|
|
614 |
# how to run examples. |
|
|
615 |
# input_path = r"F:\Data\Remarc\REMARC/" |
|
|
616 |
# data_ = "remarc" |
|
|
617 |
# |
|
|
618 |
input_path = r"F:\Data\Vienna\No_ground_truth/" |
|
|
619 |
data_ = "LNH" |
|
|
620 |
saving_dir_mip = read_pet_gt_resize_crop_save_as_3d_andor_mip( |
|
|
621 |
data_path=input_path, data_name=data_, saving_dir=None, save_3D=True, crop=True, |
|
|
622 |
output_resolution=[128, 128, 256], desired_spacing=None, generate_mip=True |
|
|
623 |
) |