Switch to unified view

a b/src/LiviaNet/Modules/IO/sampling.py
1
""" 
2
Copyright (c) 2016, Jose Dolz .All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification,
5
are permitted provided that the following conditions are met:
6
7
    1. Redistributions of source code must retain the above copyright notice,
8
       this list of conditions and the following disclaimer.
9
    2. Redistributions in binary form must reproduce the above copyright notice,
10
       this list of conditions and the following disclaimer in the documentation
11
       and/or other materials provided with the distribution.
12
13
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
    OTHER DEALINGS IN THE SOFTWARE.
21
22
Jose Dolz. Dec, 2016.
23
email: jose.dolz.upv@gmail.com
24
LIVIA Department, ETS, Montreal.
25
"""
26
27
from loadData import load_imagesSinglePatient
28
from loadData import getRandIndexes
29
import numpy as np
30
import math
31
import random
32
33
34
# **********************************  For Training *********************************
35
""" This function gets all the samples needed for training a sub-epoch """
36
def getSamplesSubepoch(numSamples,
37
                       imageNames,                                                                
38
                       groundTruthNames,
39
                       roiNames,
40
                       imageType,
41
                       sampleSizes,
42
                       receptiveField,
43
                       applyPadding
44
                       ):
45
    print (" ... Get samples for subEpoch...")
46
    
47
    numSubjects_Epoch = len(imageNames)
48
    randIdx = getRandIndexes(numSubjects_Epoch, numSubjects_Epoch)
49
50
    samplesPerSubject = numSamples/len(randIdx)
51
    print (" ... getting {} samples per subject...".format(samplesPerSubject)) 
52
    
53
    imagesSamplesAll = [] 
54
    gt_samplesAll = [] 
55
    
56
    numSubjectsSubEpoch = len(randIdx) 
57
    
58
    samplingDistribution = getSamplesDistribution(numSamples, numSubjectsSubEpoch)
59
    
60
    for i_d in xrange(0, numSubjectsSubEpoch) :
61
        # For displaying purposes
62
        perc = 100 * float(i_d+1)/numSubjectsSubEpoch
63
        print("...Processing subject: {}. {} % of the whole training set...".format(str(i_d + 1),perc))
64
65
        # -- Load images for a given patient --
66
        [imgSubject, 
67
         gtLabelsImage,
68
         roiMask,
69
         paddingValues] = load_imagesSinglePatient(randIdx[i_d],
70
                                                   imageNames,
71
                                                   groundTruthNames,
72
                                                   roiNames, 
73
                                                   applyPadding,
74
                                                   receptiveField,
75
                                                   sampleSizes,
76
                                                   imageType
77
                                                   )
78
                                                  
79
80
        # -- Get samples for that patient
81
        [imagesSamplesSinglePatient,
82
         gtSamplesSinglePatient] = getSamplesSubject(i_d,
83
                                                     imgSubject,
84
                                                     gtLabelsImage,
85
                                                     roiMask,
86
                                                     samplingDistribution,
87
                                                     sampleSizes,
88
                                                     receptiveField
89
                                                     )
90
91
        imagesSamplesAll = imagesSamplesAll + imagesSamplesSinglePatient
92
        gt_samplesAll = gt_samplesAll + gtSamplesSinglePatient
93
 
94
    # -- Permute the training samples so that in each batch both background and objects of interest are taken
95
    TrainingData = zip(imagesSamplesAll, gt_samplesAll)
96
    random.shuffle(TrainingData)
97
    rnd_imagesSamples = []
98
    rnd_gtSamples = []
99
    rnd_imagesSamples[:], rnd_gtSamples[:] = zip(*TrainingData)
100
101
    del imagesSamplesAll[:]
102
    del gt_samplesAll[:]
103
104
    return rnd_imagesSamples, rnd_gtSamples
105
106
107
108
def getSamplesSubject(imageIdx,
109
                      imgSubject,
110
                      gtLabelsImage,
111
                      roiMask,
112
                      samplingDistribution,
113
                      sampleSizes,
114
                      receptiveField
115
                      ):
116
    sampleSizes = sampleSizes
117
    imageSamplesSingleImage = []
118
    gt_samplesSingleImage = []
119
            
120
    imgDim = imgSubject.shape
121
122
    # Get weight maps for sampling
123
    weightMaps = getSamplingWeights(gtLabelsImage, roiMask)
124
125
126
    # We are extracting segments for foreground and background
127
    for c_i in xrange(2) :
128
        numOfSamplesToGet = samplingDistribution[c_i][imageIdx]
129
        weightMap = weightMaps[c_i]
130
        # Define variables to be used
131
        roiToApply = np.zeros(weightMap.shape, dtype="int32")
132
        halfSampleDim = np.zeros( (len(sampleSizes), 2) , dtype='int32')
133
134
135
        # Get the size of half patch (i.e sample)
136
        for i in xrange( len(sampleSizes) ) :
137
            if sampleSizes[i]%2 == 0: #even
138
                dimensionDividedByTwo = sampleSizes[i]/2
139
                halfSampleDim[i] = [dimensionDividedByTwo - 1, dimensionDividedByTwo] 
140
            else: #odd
141
                dimensionDividedByTwoFloor = math.floor(sampleSizes[i]/2) 
142
                halfSampleDim[i] = [dimensionDividedByTwoFloor, dimensionDividedByTwoFloor] 
143
    
144
        # --- Set to 1 those voxels in which we are interested in
145
        # - Define the limits
146
        roiMinx = halfSampleDim[0][0]
147
        roiMaxx = imgDim[0] - halfSampleDim[0][1]
148
        roiMiny = halfSampleDim[1][0]
149
        roiMaxy = imgDim[1] - halfSampleDim[1][1]
150
        roiMinz = halfSampleDim[2][0]
151
        roiMaxz = imgDim[2] - halfSampleDim[2][1]
152
153
        # Set
154
        roiToApply[roiMinx:roiMaxx,roiMiny:roiMaxy,roiMinz:roiMaxz] = 1
155
        
156
        maskCoords = weightMap * roiToApply
157
        
158
        # We do the following because np.random.choice 4th parameter needs the probabilities to sum 1
159
        maskCoords = maskCoords / (1.0* np.sum(maskCoords))
160
    
161
        maskCoordsFlattened = maskCoords.flatten()
162
  
163
        centralVoxelsIndexes = np.random.choice(maskCoords.size,
164
                                                size = numOfSamplesToGet,
165
                                                replace=True,
166
                                                p=maskCoordsFlattened)
167
168
        centralVoxelsCoord = np.asarray(np.unravel_index(centralVoxelsIndexes, maskCoords.shape))
169
        
170
        coordsToSampleArray = np.zeros(list(centralVoxelsCoord.shape) + [2], dtype="int32")
171
        coordsToSampleArray[:,:,0] = centralVoxelsCoord - halfSampleDim[ :, np.newaxis, 0 ] #np.newaxis broadcasts. To broadcast the -+.
172
        coordsToSampleArray[:,:,1] = centralVoxelsCoord + halfSampleDim[ :, np.newaxis, 1 ]
173
174
        
175
        # ----- Compute the coordinates that will be used to extract the samples ---- #
176
        numSamples = len(coordsToSampleArray[0])
177
178
        # Extract samples from computed coordinates
179
        for s_i in xrange(numSamples) :
180
181
            # Get one sample given a coordinate
182
            coordsToSample = coordsToSampleArray[:,s_i,:] 
183
184
            sampleSizes = sampleSizes
185
            imageSample = np.zeros((1, sampleSizes[0],sampleSizes[1],sampleSizes[2]), dtype = 'float32')
186
187
            xMin = coordsToSample[0][0]
188
            xMax = coordsToSample[0][1] + 1
189
            yMin = coordsToSample[1][0]
190
            yMax = coordsToSample[1][1] + 1
191
            zMin = coordsToSample[2][0]
192
            zMax = coordsToSample[2][1] + 1
193
194
            imageSample[:1] = imgSubject[ xMin:xMax,yMin:yMax,zMin:zMax]
195
            sample_gt_Orig = gtLabelsImage[xMin:xMax,yMin:yMax,zMin:zMax]
196
197
            roiLabelMin = np.zeros(3, dtype = "int8")
198
            roiLabelMax = np.zeros(3, dtype = "int8")
199
200
            for i_x in range(len(receptiveField)) :
201
                roiLabelMin[i_x] = (receptiveField[i_x] - 1)/2
202
                roiLabelMax[i_x] = sampleSizes[i_x] - roiLabelMin[i_x]
203
            
204
            gt_sample = sample_gt_Orig[roiLabelMin[0] : roiLabelMax[0],
205
                                       roiLabelMin[1] : roiLabelMax[1],
206
                                       roiLabelMin[2] : roiLabelMax[2]]
207
                                        
208
            imageSamplesSingleImage.append(imageSample)
209
            gt_samplesSingleImage.append(gt_sample)
210
211
    return imageSamplesSingleImage,gt_samplesSingleImage
212
   
213
214
215
def getSamplingWeights(gtLabelsImage,
216
                       roiMask
217
                       ) :
218
219
    foreMask = (gtLabelsImage>0).astype(int)
220
    backMask = (roiMask>0) * (foreMask==0)
221
    weightMaps = [ foreMask, backMask ] 
222
 
223
    return weightMaps
224
     
225
226
227
def getSamplesDistribution( numSamples,
228
                            numImagesToSample ) :
229
    # We have to sample foreground and background
230
    # Assuming that we extract the same number of samples per category: 50% each
231
232
    samplesPercentage = np.ones( 2, dtype="float32" ) * 0.5
233
    samplesPerClass = np.zeros( 2, dtype="int32" )
234
    samplesDistribution = np.zeros( [ 2, numImagesToSample ] , dtype="int32" )
235
    
236
    samplesAssigned = 0
237
    
238
    for c_i in xrange(2) :
239
        samplesAssignedClass = int(numSamples*samplesPercentage[c_i])
240
        samplesPerClass[c_i] += samplesAssignedClass
241
        samplesAssigned += samplesAssignedClass
242
 
243
    # Assign the samples that were not assigned due to the rounding error of integer division. 
244
    nonAssignedSamples = numSamples - samplesAssigned
245
    classesIDx= np.random.choice(2,
246
                                 nonAssignedSamples,
247
                                 True,
248
                                 p=samplesPercentage)
249
250
    for c_i in classesIDx : 
251
        samplesPerClass[c_i] += 1
252
        
253
    for c_i in xrange(2) :
254
        samplesAssignedClass = samplesPerClass[c_i] / numImagesToSample                
255
        samplesDistribution[c_i] += samplesAssignedClass
256
        samplesNonAssignedClass = samplesPerClass[c_i] % numImagesToSample
257
        for cU_i in xrange(samplesNonAssignedClass):
258
            samplesDistribution[c_i, random.randint(0, numImagesToSample-1)] += 1
259
260
    return samplesDistribution
261
262
# **********************************  For testing *********************************
263
264
def sampleWholeImage(imgSubject,
265
                     roi,
266
                     sampleSize,
267
                     strideVal,
268
                     batch_size
269
                     ):
270
271
    samplesCoords = []
272
 
273
    imgDims = list(imgSubject.shape)
274
    
275
    zMinNext=0
276
    zCentPredicted = False
277
    
278
    while not zCentPredicted :
279
        zMax = min(zMinNext+sampleSize[2], imgDims[2]) 
280
        zMin = zMax - sampleSize[2]
281
        zMinNext = zMinNext + strideVal[2]
282
283
        if zMax < imgDims[2]:
284
            zCentPredicted = False
285
        else:
286
            zCentPredicted = True 
287
        
288
        yMinNext=0
289
        yCentPredicted = False
290
        
291
        while not yCentPredicted :
292
            yMax = min(yMinNext+sampleSize[1], imgDims[1]) 
293
            yMin = yMax - sampleSize[1]
294
            yMinNext = yMinNext + strideVal[1]
295
296
            if yMax < imgDims[1]:
297
                yCentPredicted = False
298
            else:
299
                yCentPredicted = True
300
            
301
            xMinNext=0
302
            xCentPredicted = False
303
            
304
            while not xCentPredicted :
305
                xMax = min(xMinNext+sampleSize[0], imgDims[0])
306
                xMin = xMax - sampleSize[0]
307
                xMinNext = xMinNext + strideVal[0]
308
309
                if xMax < imgDims[0]:
310
                    xCentPredicted = False
311
                else:
312
                    xCentPredicted = True
313
                
314
                if isinstance(roi, (np.ndarray)) : 
315
                    if not np.any(roi[xMin:xMax, yMin:yMax, zMin:zMax ]) : 
316
                        continue
317
                    
318
                samplesCoords.append([ [xMin, xMax-1], [yMin, yMax-1], [zMin, zMax-1] ])
319
                
320
    # To Theano to not complain the number of samples have to exactly fit with the number of batches.
321
    sampledRegions = len(samplesCoords)
322
323
    if sampledRegions%batch_size <> 0:
324
        numberOfSamplesToAdd =  batch_size - sampledRegions%batch_size
325
    else:
326
      numberOfSamplesToAdd = 0
327
      
328
    for i in xrange(numberOfSamplesToAdd) :
329
        samplesCoords.append(samplesCoords[sampledRegions-1])
330
331
    return [samplesCoords]
332
333
334
def extractSamples(imgData,
335
                   sliceCoords,
336
                   imagePartDimensions,
337
                   patchDimensions
338
                   ) :
339
    numberOfSamples = len(sliceCoords)
340
    # Create the array that will contain the samples
341
    samplesArrayShape = [numberOfSamples, 1, imagePartDimensions[0], imagePartDimensions[1], imagePartDimensions[2]]
342
    samples = np.zeros(samplesArrayShape, dtype= "float32")
343
    
344
    for s_i in xrange(numberOfSamples) :
345
        cMin = []
346
        cMax = []
347
        for c_i in xrange(3):
348
            cMin.append(sliceCoords[s_i][c_i][0])
349
            cMax.append(sliceCoords[s_i][c_i][1] + 1)
350
            
351
        samples[s_i] = imgData[cMin[0]:cMax[0],
352
                               cMin[1]:cMax[1],
353
                               cMin[2]:cMax[2]]
354
                                                                    
355
    return [samples]