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