a b/code/image_utils.py
1
import os
2
import numpy as np
3
import nibabel as nib
4
from numpy.linalg import inv
5
6
def rescale_intensity(image, thres=(1.0, 99.0)):
7
    """ Rescale the image intensity to the range of [0, 1] """
8
    val_l, val_h = np.percentile(image, thres)
9
    image2 = image
10
    image2[image < val_l] = val_l
11
    image2[image > val_h] = val_h
12
    image2 = (image2.astype(np.float32) - val_l) / (val_h - val_l + 1e-6)
13
    return image2
14
15
16
def imagePreprocessing(originalNii, data_dir, atlas_dir):
17
     
18
    os.system('headertool '
19
              '{0} '
20
              '{1}/lvsa_.nii.gz '
21
              '-reset'
22
              .format(originalNii, data_dir))
23
24
    os.system('temporalalign '
25
              '{0}/temporal.gipl '
26
              '{1}/lvsa_.nii.gz '
27
              '{1}/lvsa_.nii.gz ' 
28
              '-St1 0 '
29
              '-St2 0 '
30
              '-Et1 1 '
31
              '-Et2 1'
32
              .format(atlas_dir, data_dir))
33
34
    os.system('autocontrast '
35
              '{0}/lvsa_.nii.gz '
36
              '{0}/lvsa_.nii.gz'
37
              .format(data_dir))
38
        
39
    os.system('cardiacphasedetection '
40
              '{0}/lvsa_.nii.gz '
41
              '{0}/lvsa_ED.nii.gz '
42
              '{0}/lvsa_ES.nii.gz'
43
              .format(data_dir))
44
    
45
    print('  Image preprocessing is done ...')
46
47
48
def clearBaseManbrance(data_dir, output_name):
49
50
    nim = nib.load('{0}/{1}'.format(data_dir, output_name))
51
    seg = nim.get_data()
52
    if seg.ndim == 4:
53
        seg = np.squeeze(seg, axis=-1).astype(np.int16)
54
        
55
    # find the last slice that has RV component 
56
    rv = (seg == 4).astype(np.int16)   
57
    for i in reversed(range(seg.shape[2])):
58
        if np.sum(rv[:,:,i]) > 0: 
59
            break
60
    lastAppearSlice = i 
61
    
62
    # create a 2D mask that average the last 12 slice of RV which is robust to noise
63
    slicesUsed = 12
64
    tmp = np.zeros((seg.shape[0], seg.shape[1]), dtype=np.int16)
65
    for j in range(slicesUsed):
66
        tmp = tmp + rv[:,:,lastAppearSlice-j]
67
    tmp = (tmp > 0).astype(np.int16)   
68
    
69
    # create a volume mask used to remove the base manbrance caused by RV myocardium
70
    mask = np.ones((seg.shape[2], seg.shape[0], seg.shape[1]), dtype=np.int16)
71
    mask[lastAppearSlice-slicesUsed:,:,:] = 1 - tmp
72
    mask = np.transpose(mask, axes=(1, 2, 0))
73
    seg = np.multiply(seg, mask)
74
    
75
    # fill the gap/hole of RV 
76
    flat = False
77
    if flat:
78
        seg = np.transpose(seg, axes=(2, 0, 1))
79
        seg[lastAppearSlice-slicesUsed:lastAppearSlice-3,:,:] = seg[lastAppearSlice-slicesUsed:lastAppearSlice-3,:,:] + 4*tmp
80
        seg = np.transpose(seg, axes=(1, 2, 0))
81
    else:
82
        seg[:,:,lastAppearSlice-slicesUsed:] = seg[:,:,lastAppearSlice-slicesUsed:] + 4*rv[:,:,lastAppearSlice-slicesUsed:]
83
    
84
    ###############################################################################
85
    # find the last slice that has LV component 
86
    lv = (seg == 1).astype(np.int16)   
87
    for i in reversed(range(seg.shape[2])):
88
        if np.sum(lv[:,:,i]) > 0: 
89
            break
90
    lastAppearSlice = i 
91
    
92
    # create a 2D mask that average the last 5 slice of RV which is robust to noise
93
    slicesUsed = 10
94
    tmp = np.zeros((seg.shape[0], seg.shape[1]), dtype=np.int16)
95
    for j in range(slicesUsed):
96
        tmp = tmp + lv[:,:,lastAppearSlice-j]
97
    tmp = (tmp > 0).astype(np.int16)   
98
    
99
    # create a volume mask used to remove the manbrance caused by LV myocardium
100
    mask = np.ones((seg.shape[2], seg.shape[0], seg.shape[1]), dtype=np.int16)
101
    mask[lastAppearSlice-slicesUsed:,:,:] = 1 - tmp
102
    mask = np.transpose(mask, axes=(1, 2, 0))
103
    seg = np.multiply(seg, mask)
104
    
105
    # fill the gap/hole of LV 
106
    flat = True
107
    if flat:
108
        seg = np.transpose(seg, axes=(2, 0, 1))
109
        seg[lastAppearSlice-slicesUsed:lastAppearSlice-1,:,:] = seg[lastAppearSlice-slicesUsed:lastAppearSlice-1,:,:] + tmp
110
        seg = np.transpose(seg, axes=(1, 2, 0))
111
    else:
112
        seg[:,:,lastAppearSlice-slicesUsed:] = seg[:,:,lastAppearSlice-slicesUsed:] + lv[:,:,lastAppearSlice-slicesUsed:]
113
114
    # save the result
115
    nim2 = nib.Nifti1Image(seg, nim.affine)
116
    nim2.header['pixdim'] = nim.header['pixdim']
117
    nib.save(nim2, '{0}/{1}'.format(data_dir, output_name))
118
119
120
def removeSegsAboveBase(data_dir, output_name):
121
122
    # Read segmentations
123
    nim = nib.load('{0}/{1}'.format(data_dir, output_name))
124
    seg = nim.get_data()
125
    if seg.ndim == 4:
126
        seg = np.squeeze(seg, axis=-1).astype(np.int16)
127
    os.system('vtk2txt {0}/landmarks.vtk {0}/landmarks.txt'.format(data_dir)) 
128
    
129
    # convert txt file into matrix
130
    file = open('{0}/landmarks.txt'.format(data_dir), 'r') 
131
    A = file.read()
132
    tmp = np.zeros(18)  
133
    i, c = 0, 0
134
    for p in range(len(A)):
135
        if A[p] == ' ' or A[p] == '\n':
136
            tmp[i] = np.float32(A[c:p])
137
            i = i + 1
138
            c = p + 1;
139
    tmp = np.reshape(tmp,(6,3))        
140
    landmarks = np.ones((6,4)) 
141
    landmarks[:,:-1] = tmp
142
    landmarks = np.transpose(landmarks).astype(np.float32)           
143
    os.system('rm {0}/landmarks.txt'.format(data_dir))    
144
       
145
    # map world coordinate system to image pixel position
146
    pixelsPositions = np.ceil(np.dot(inv(nim.affine),landmarks)).astype(np.int16) 
147
    pixelsPositions = np.delete(pixelsPositions, (-1), axis=0)
148
    pixelsPositions = np.transpose(pixelsPositions).astype(np.int16) 
149
    
150
    if output_name[9:11]=='ED':
151
        seg[:,:,pixelsPositions[5,2]+1:] = 0
152
    else:
153
        seg[:,:,pixelsPositions[5,2]:] = 0
154
    
155
    nim2 = nib.Nifti1Image(seg, nim.affine)
156
    nim2.header['pixdim'] = nim.header['pixdim']
157
    nib.save(nim2, '{0}/{1}'.format(data_dir, output_name))
158
    
159
160
def formHighResolutionImg(subject_dir, fr): 
161
    
162
    os.system('resample ' 
163
              '{0}/lvsa_{1}.nii.gz '
164
              '{0}/lvsa_SR_{1}.nii.gz '
165
              '-size 1.25 1.25 2'
166
              .format(subject_dir, fr))
167
        
168
#    os.system('enlarge_image '
169
#              '{0}/lvsa_SR_{1}.nii.gz '
170
#              '{0}/lvsa_SR_{1}.nii.gz '
171
#              '-z 20 '
172
#              '-value 0'
173
#              .format(subject_dir, fr))
174
175
    
176
def convertImageSegment(data_dir, fr):
177
   
178
    os.system('convert '
179
              '{0}/seg_lvsa_SR_{1}.nii.gz '
180
              '{0}/PHsegmentation_{1}.gipl'
181
              .format(data_dir, fr))
182
    
183
    os.system('cp '
184
              '{0}/lvsa_SR_{1}.nii.gz '
185
              '{0}/lvsa_{1}_enlarged_SR.nii.gz'
186
              .format(data_dir, fr))
187
    
188
    
189
def outputVolumes(subject_dir, data_dir, subject, fr):
190
     
191
    os.system('rm '
192
              '{0}/{1}_{2}.txt'
193
              .format(data_dir, subject, fr))
194
    
195
    os.system('cardiacvolumecount '
196
              '{0}/PHsegmentation_{3}.gipl 1 '
197
              '-output '
198
              '{1}/{2}_{3}.txt'
199
              .format(subject_dir, data_dir, subject, fr))
200
    
201
    os.system('cardiacvolumecount '
202
              '{0}/PHsegmentation_{3}.gipl 2 '
203
              '-output '
204
              '{1}/{2}_{3}.txt '
205
              '-scale 1.05'
206
              .format(subject_dir, data_dir, subject, fr))
207
    
208
    os.system('cardiacvolumecount '
209
              '{0}/PHsegmentation_{3}.gipl 4 '
210
              '-output '
211
              '{1}/{2}_{3}.txt'
212
              .format(subject_dir, data_dir, subject, fr))
213
    
214
    os.system('cardiacvolumecount '
215
              '{0}/PHsegmentation_{3}.gipl 3 '
216
              '-output '
217
              '{1}/{2}_{3}.txt '
218
              '-scale 1.05'
219
              .format(subject_dir, data_dir, subject, fr))
220
    
221
    
222
def moveVolumes(subject_dir, sizes_dir, fr):
223
     
224
    os.system('cp '
225
              '{0}/lvsa_{2}.nii.gz '
226
              '{1}/lvsa_{2}.nii.gz'
227
              .format(subject_dir, sizes_dir, fr))
228
    
229
    os.system('rm '
230
              '{0}/lvsa_{1}.nii.gz '
231
              .format(subject_dir, fr))
232
    
233
    os.system('cp '
234
              '{0}/seg_lvsa_{2}.nii.gz '
235
              '{1}/2D_segmentation_{2}.nii.gz'
236
              .format(subject_dir, sizes_dir, fr))
237
    
238
    os.system('rm '
239
              '{0}/seg_lvsa_{1}.nii.gz '
240
              .format(subject_dir, fr))
241
    
242
    os.system('cp '
243
              '{0}/lvsa_SR_{2}.nii.gz '
244
              '{1}/lvsa_{2}_SR.nii.gz'
245
              .format(subject_dir, sizes_dir, fr))
246
    
247
    os.system('rm '
248
              '{0}/lvsa_SR_{1}.nii.gz '
249
              .format(subject_dir, fr))
250
    
251
    os.system('cp '
252
              '{0}/seg_lvsa_SR_{2}.nii.gz '
253
              '{1}/3D_segmentation_{2}.nii.gz'
254
              .format(subject_dir, sizes_dir, fr))
255
    
256
    os.system('rm '
257
              '{0}/seg_lvsa_SR_{1}.nii.gz '
258
              .format(subject_dir, fr))
259
260
    
261
def refineFusionResults(data_dir, output_name, alfa): 
262
    
263
    ##########################################
264
    os.system('binarize '
265
              '{0}/{1} '
266
              '{0}/tmps/hrt.nii.gz '
267
              '1 4 255 0'
268
              .format(data_dir, output_name))
269
    
270
    os.system('blur '
271
              '{0}/tmps/hrt.nii.gz '
272
              '{0}/tmps/hrt.nii.gz '
273
              '{1}'
274
              .format(data_dir, alfa))
275
    
276
    os.system('threshold '
277
              '{0}/tmps/hrt.nii.gz '
278
              '{0}/tmps/hrt.nii.gz '
279
              '130'
280
              .format(data_dir))
281
    
282
    ##########################################
283
    os.system('binarize '
284
              '{0}/{1} '
285
              '{0}/tmps/rvendo.nii.gz '
286
              '4 4 255 0'
287
              .format(data_dir, output_name))
288
    
289
    os.system('blur '
290
              '{0}/tmps/rvendo.nii.gz '
291
              '{0}/tmps/rvendo.nii.gz '
292
              '{1}'
293
              .format(data_dir, alfa))
294
    
295
    os.system('threshold '
296
              '{0}/tmps/rvendo.nii.gz '
297
              '{0}/tmps/rvendo.nii.gz '
298
              '130'
299
              .format(data_dir))
300
    
301
    ##########################################
302
    os.system('binarize '
303
              '{0}/{1} '
304
              '{0}/tmps/lvepi.nii.gz '
305
              '1 2 255 0'
306
              .format(data_dir, output_name))
307
       
308
    os.system('blur '
309
              '{0}/tmps/lvepi.nii.gz '
310
              '{0}/tmps/lvepi.nii.gz '
311
              '{1}'
312
              .format(data_dir, alfa))
313
    
314
    os.system('threshold '
315
              '{0}/tmps/lvepi.nii.gz '
316
              '{0}/tmps/lvepi.nii.gz '
317
              '115'
318
              .format(data_dir))
319
    
320
    ##########################################
321
    os.system('binarize '
322
              '{0}/{1} '
323
              '{0}/tmps/lvendo.nii.gz '
324
              '1 1 255 0'
325
              .format(data_dir, output_name))
326
    
327
    os.system('blur '
328
              '{0}/tmps/lvendo.nii.gz '
329
              '{0}/tmps/lvendo.nii.gz '
330
              '{1}'
331
              .format(data_dir, alfa))
332
    
333
    os.system('threshold '
334
              '{0}/tmps/lvendo.nii.gz '
335
              '{0}/tmps/lvendo.nii.gz '
336
              '130'
337
              .format(data_dir))
338
       
339
    ##########################################
340
    os.system('padding '
341
              '{0}/tmps/hrt.nii.gz '
342
              '{0}/tmps/hrt.nii.gz '
343
              '{0}/tmps/hrt.nii.gz '
344
              '1 3'
345
              .format(data_dir))
346
    
347
    os.system('padding '
348
              '{0}/tmps/hrt.nii.gz '
349
              '{0}/tmps/rvendo.nii.gz '
350
              '{0}/tmps/rvendo.nii.gz '
351
              '1 4'
352
              .format(data_dir))
353
    
354
    os.system('padding '
355
              '{0}/tmps/rvendo.nii.gz '
356
              '{0}/tmps/lvepi.nii.gz '
357
              '{0}/tmps/lvepi.nii.gz '
358
              '1 2'
359
              .format(data_dir))
360
    
361
    os.system('padding '
362
              '{0}/tmps/lvepi.nii.gz '
363
              '{0}/tmps/lvendo.nii.gz '
364
              '{0}/{1} '
365
              '1 1'
366
              .format(data_dir, output_name))
367
    
368
    
369
def allAtlasShapeSelection(dataset_dir):
370
   
371
    atlases_list, landmarks_list = {}, {}
372
    
373
    for fr in ['ED', 'ES']:
374
            
375
        atlases_list[fr], landmarks_list[fr] = [], [] 
376
        i = 0
377
        for atlas in sorted(os.listdir(dataset_dir)): 
378
            
379
            atlas_dir = os.path.join(dataset_dir, atlas)
380
            
381
            if not os.path.isdir(atlas_dir):
382
                
383
                print('  {0} is not a valid atlas directory, Discard'.format(atlas_dir))
384
                
385
                continue 
386
            
387
            atlas_3D_shape = '{0}/PHsegmentation_{1}.nii.gz'.format(atlas_dir, fr)
388
           
389
            landmarks = '{0}/landmarks.vtk'.format(atlas_dir)
390
        
391
            if i < 400:
392
                if os.path.exists(atlas_3D_shape) or os.path.exists(landmarks):
393
                    
394
                    atlases_list[fr] += [atlas_3D_shape]
395
                
396
                    landmarks_list[fr] += [landmarks]
397
            else:
398
                print(atlases_list)
399
                break
400
            
401
            i = i + 1
402
                 
403
    return atlases_list, landmarks_list
404
405
406
def topSimilarAtlasShapeSelection(atlases, atlas_landmarks, subject_landmarks, tmps_dir, dofs_dir, DLSeg, param_dir, topSimilarAltasNo):            
407
    
408
    landmarks = False
409
    
410
    nmi = []
411
    
412
    topSimilarAtlases_list = []
413
    
414
    atlasNo = len(atlases)
415
    
416
    os.system('rm {0}/shapenmi*.txt'.format(tmps_dir))
417
    
418
    for i in range(atlasNo):
419
        
420
        if landmarks:
421
            
422
            os.system('pareg '
423
                      '{0} '
424
                      '{1} '
425
                      '-dofout {2}/shapelandmarks_{3}.dof.gz'
426
                      .format(subject_landmarks, atlas_landmarks[i], dofs_dir, i))
427
        else: 
428
            
429
            os.system('areg '
430
                      '{0} '
431
                      '{1} '
432
                      '-dofout {2}/shapelandmarks_{4}.dof.gz '
433
                      '-parin {3}/segareg.txt'
434
                      .format(DLSeg, atlases[i], dofs_dir, param_dir, i))
435
                      
436
        os.system('cardiacimageevaluation '
437
                  '{0} '
438
                  '{1} '
439
                  '-nbins_x 64 '
440
                  '-nbins_y 64 '
441
                  '-dofin {2}/shapelandmarks_{4}.dof.gz '
442
                  '-output {3}/shapenmi_{4}.txt'
443
                  .format(DLSeg, atlases[i], dofs_dir, tmps_dir, i))
444
                        
445
        if os.path.exists('{0}/shapenmi_{1}.txt'.format(tmps_dir, i)):
446
447
            similarities = np.genfromtxt('{0}/shapenmi_{1}.txt'.format(tmps_dir, i))
448
            nmi += [similarities[3]]
449
            
450
        else:
451
            nmi += [0]
452
    
453
    if topSimilarAltasNo < atlasNo:
454
        
455
        sortedIndexes = np.array(nmi).argsort()[::-1]  
456
        
457
        savedInd = np.zeros(topSimilarAltasNo, dtype=int)
458
        
459
        for i in range(topSimilarAltasNo):
460
            
461
            topSimilarAtlases_list += [atlases[sortedIndexes[i]]]
462
            
463
            savedInd[i] = sortedIndexes[i]
464
    else:
465
        
466
        savedInd = np.zeros(atlasNo, dtype=int)
467
        
468
        topSimilarAtlases_list = atlases
469
        
470
        savedInd = np.arange(atlasNo)
471
        
472
    return topSimilarAtlases_list, savedInd    
473