Diff of /4x/batch2.py [000000] .. [171cba]

Switch to unified view

a b/4x/batch2.py
1
# Files
2
import os
3
import pickle
4
import sys
5
6
# Basic
7
import numpy as np
8
9
# Image Processing
10
import skimage as ski
11
from skimage import io, feature, morphology, filter, exposure, color, transform, measure
12
import scipy.signal as sig
13
from scipy.ndimage import maximum_filter, minimum_filter, binary_fill_holes
14
15
# Stats
16
import scipy.stats as st
17
18
# Visualisation
19
#import matplotlib
20
#import matplotlib.pyplot as plt
21
#import seaborn
22
23
24
25
26
# Results : sheep ID, entropy, entropic variance, lacunarity, directionality
27
# IDs
28
name = []
29
imageid = []
30
31
# Entropy
32
ent = []
33
entstd = []
34
35
# Lacunarity
36
lac = []
37
nonlac = []
38
39
# Gabor filtering
40
direc = []
41
scale = []
42
43
# Inflammatory foci count and size
44
inflcount = []
45
inflsize = []
46
inflstd = []
47
48
# Tissue to sinusoid ratio
49
ratio = []
50
51
52
53
54
55
56
57
58
# Image files to process
59
60
# If we're running on all files
61
if len(sys.argv) == 1 :
62
63
    # Read files
64
    files = []
65
    directory = "data/"
66
    for i in os.listdir(directory) :
67
        if i.endswith(".jpg") : # if it's a jpg
68
            if not i.endswith("_processed.jpg") : # and isn't a processed image
69
                files.append(directory + i) # then add it to the list to be processed
70
71
72
# Otherwise, we ran launcher.py and need to do select images only
73
else :
74
75
    F = open("filelist%i.p" % int(sys.argv[1]), "r")
76
    files = pickle.load(F)
77
    F.close()
78
79
80
81
82
83
84
85
86
87
88
89
# Iterate over files
90
for f in files :
91
92
    print "Thread %s. Opening file : %s" % (sys.argv[1], f)
93
94
95
    # If it's not been processed before
96
    if not os.path.exists(f + "_processed.jpg") :
97
98
        ### PROCESSING
99
        
100
        # Read the image
101
        A = io.imread(f)
102
        
103
        # Constrast enhancement
104
        print "Thread %s. Sigmoid transform for contrast." % sys.argv[1]
105
        B = exposure.adjust_sigmoid(A, gain=12)
106
        
107
        # Extract luminosity
108
        print "Thread %s. Generating luminosity." % sys.argv[1]
109
        C = color.rgb2xyz(B)[:, :, 1]
110
        
111
        # Apply adaptive thresholding
112
        print "Thread %s. Performing adaptive thresholding." % sys.argv[1]
113
        D = filter.threshold_adaptive(C, 301)
114
        
115
        # Clean
116
        print "Thread %s. Cleaning image." % sys.argv[1]
117
        E = morphology.remove_small_objects(~morphology.remove_small_objects(~D, 100), 100)
118
119
        # Save to disk
120
        io.imsave(f + "_processed.jpg", ski.img_as_float(E))
121
        
122
        # Downsample for Gabor filtering
123
        print "Thread %s. Downsampling image." % sys.argv[1]
124
        Es = ski.img_as_float(transform.rescale(E, 0.25))
125
126
127
    else :
128
129
        # Otherwise, we've processed it before, so read it in for speed
130
        A = io.imread(f)
131
        E = ski.img_as_float(io.imread(f + "_processed.jpg"))
132
        Es = ski.img_as_float(transform.rescale(E, 0.25))
133
    print "Thread %s. Image already processed, downsampled." % sys.argv[1]
134
        
135
136
    ## ID
137
    name.append(int(f.split("data/Sheep")[1].split("-")[0].split(".jpg")[0]))
138
    imageid.append(int(f.split("data/Sheep")[1].split("-")[2].split(".jpg")[0]))
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    
154
155
    
156
157
    ### GABOR FILTERING
158
159
    # Define scales and orientations to compute over
160
    pixelscales = np.arange(15, 55, 2)
161
    gaborscales = 4. / pixelscales # 2 to 20 pixels
162
163
    orientations = np.linspace(0, np.pi * 11./12., 12) # 0 to 180 degrees in 15 degree increments
164
165
    # Results array
166
    gabor = np.zeros((len(orientations), len(gaborscales)))
167
168
    # Perform Gabor filtering
169
    for i, iv in enumerate(orientations) :
170
        for j, jv in enumerate(gaborscales) :
171
            gaborReal, gaborImag = filter.gabor_filter(Es, jv, iv)
172
            gabor[i, j] = np.sqrt(np.sum(np.abs(gaborReal) ** 2) + np.sum(np.abs(gaborImag) ** 2)) # Return energy
173
        print "Thread %s. Gabor filtering. Completion : %f" % (sys.argv[1], (i / float(len(orientations))))
174
175
    # Determine orientation-independent scale which fits best
176
    optimalscale = np.argmax(np.sum(gabor, axis = 0))
177
178
    # At this scale, calculate directionality coefficient
179
    g = gabor[:, optimalscale]
180
    directionality = (g.max() - g.min()) / g.max()
181
182
    # Results
183
    scale.append(pixelscales[optimalscale])
184
    direc.append(directionality)
185
186
187
188
    
189
190
191
192
193
194
195
196
197
198
199
200
201
    ### LACUNARITY AND ENTROPY
202
203
    # Define scales over which to compute lacunarity and entropy
204
    
205
    scaleent = []
206
    scaleentstd = []
207
    #scalelac = []
208
    roylac = []
209
210
211
    # Characteristic scale
212
    s = pixelscales[optimalscale]
213
214
    # Generate a disk at this scale
215
    circle = morphology.disk(s)
216
    circlesize = circle.sum()
217
218
    # Convolve with image
219
    Y = sig.fftconvolve(E, circle, "valid")
220
221
    # Compute information entropy
222
    px = Y.ravel() / circlesize
223
    #px = np.reshape(Y[int(i) : int(E.shape[0]-i), int(i) : int(E.shape[1]-i)] / circlesize, (E.shape[0]-2*i)*(E.shape[1]-2*i),)
224
    py = 1. - px
225
    idx = np.logical_and(px > 1. / circlesize, px < 1.)
226
    entropy = - ( np.mean(px[idx] * np.log(px[idx])) + np.mean(py[idx] * np.log(py[idx])) )
227
    entropystd = np.std(px[idx] * np.log(px[idx]) + py[idx] * np.log(py[idx]))
228
229
    # Compute normalised lacunarity
230
    lx = np.var(Y) / (np.mean(Y) ** 2) + 1
231
    ly = np.var(1. - Y) / (np.mean(1. - Y) ** 2) + 1
232
    # lacunarity = 2. - (1. / lx + 1. / ly)
233
234
    # Roy et al, J. Struct. Geol. 2010
235
236
    # Results
237
    roylac.append((lx - 1.) / (1./np.mean(E) - 1.))
238
    scaleent.append(entropy)
239
    scaleentstd.append(entropystd)
240
    # scalelac.append(lacunarity)
241
242
    print "Thread %s. Calculated entropy and lacunarity." % sys.argv[1]
243
244
245
246
247
    # Old code evaluates lacunarity and entropy at all scales
248
    # We only need it at the image's characteristic scale
249
    """
250
    # Iterate over scales
251
    for i, s in enumerate(lacscales) :
252
253
        # Generate a disk at this scale
254
        circle = morphology.disk(s)
255
        circlesize = circle.sum()
256
257
        # Convolve with image
258
        Y = sig.fftconvolve(E, circle, "valid")
259
260
        # Compute information entropy
261
        px = Y.ravel() / circlesize
262
        #px = np.reshape(Y[int(i) : int(E.shape[0]-i), int(i) : int(E.shape[1]-i)] / circlesize, (E.shape[0]-2*i)*(E.shape[1]-2*i),)
263
        py = 1. - px
264
        idx = np.logical_and(px > 1. / circlesize, px < 1.)
265
        entropy = - ( np.mean(px[idx] * np.log(px[idx])) + np.mean(py[idx] * np.log(py[idx])) )
266
        entropystd = np.std(px[idx] * np.log(px[idx]) + py[idx] * np.log(py[idx]))
267
268
        # Compute normalised lacunarity
269
        lx = np.var(Y) / (np.mean(Y) ** 2) + 1
270
        ly = np.var(1. - Y) / (np.mean(1. - Y) ** 2) + 1
271
        # lacunarity = 2. - (1. / lx + 1. / ly)
272
273
        # Roy et al, J. Struct. Geol. 2010
274
275
        # Results
276
        roylac.append((lx - 1.) / (1./np.mean(E) - 1.))
277
        scaleent.append(entropy)
278
        scaleentstd.append(entropystd)
279
        # scalelac.append(lacunarity)
280
281
        print "Thread %s. Calculating entropy and lacunarity. Completion : %f" % (sys.argv[1], (float(i) / len(lacscales)))
282
    """
283
284
    nonlac.append(roylac)
285
    ent.append(scaleent)
286
    entstd.append(scaleentstd)
287
    # lac.append(scalelac)
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    # Inflammatory focus count
309
310
    qstain = np.array([[.26451728, .5205347, .81183386], [.9199094, .29797825, .25489032], [.28947765, .80015373, .5253158]])
311
312
    deconv = ski.img_as_float(color.separate_stains(transform.rescale(A, 0.25), np.linalg.inv(qstain)))
313
314
315
316
    subveins1 = \
317
    morphology.remove_small_objects(
318
        filter.threshold_adaptive(
319
            filter.gaussian_filter(
320
                deconv[:, :, 2] / deconv[:, :, 0],
321
                11),
322
            250, offset = -0.13),
323
        60)
324
325
    subveins2 = \
326
    morphology.remove_small_objects(
327
        filter.threshold_adaptive(
328
            filter.gaussian_filter(
329
                    maximum_filter(
330
                    deconv[:, :, 2] / deconv[:, :, 0],
331
                    5),
332
                11),
333
            250, offset = -0.13),
334
        60)
335
336
    veins = \
337
    maximum_filter(
338
        morphology.remove_small_objects(
339
            binary_fill_holes(
340
                morphology.binary_closing(
341
                    np.logical_or(subveins1, subveins2),
342
                    morphology.disk(25)),
343
                ),
344
            250),
345
        27)
346
347
348
349
350
351
352
    rawinflammation = \
353
    morphology.remove_small_objects(
354
    filter.threshold_adaptive(
355
        exposure.adjust_sigmoid(
356
            filter.gaussian_filter(
357
                exposure.equalize_adapthist(
358
                    exposure.rescale_intensity(
359
                        deconv[:, :, 1],
360
                        out_range = (0, 1)),
361
                    ntiles_y = 1),
362
                    5),
363
            cutoff = 0.6),
364
            75, offset = -0.12),
365
        250)
366
367
    
368
    inflammation = \
369
    maximum_filter(
370
        rawinflammation,
371
        29)
372
373
   
374
375
    print "Thread %s. Image segmentation complete." % sys.argv[1]
376
377
378
379
380
    total = veins + inflammation
381
    coloured = np.zeros_like(deconv)
382
    coloured[:, :, 1] = veins
383
    coloured[:, :, 2] = inflammation
384
385
386
    labelled, regions = measure.label(total, return_num = True)
387
    
388
389
    inflammationcount = 0
390
    inflammationsize = []
391
392
393
394
    for b in range(1, regions) :
395
396
        if (inflammation * (labelled == b)).sum() / ((veins * (labelled == b)).sum() + 1) > 0.5 :
397
            inflammationcount += 1
398
            inflammationsize.append((rawinflammation * labelled == b).sum())
399
400
401
402
    inflcount.append(inflammationcount)
403
    inflsize.append(np.mean(inflammationsize))
404
    inflstd.append(np.std(inflammationsize))
405
406
407
408
409
410
411
412
413
414
415
    # Tissue to sinusoid ratio
416
417
    tissue = np.sum(ski.img_as_bool(Es) * (1 - ski.img_as_bool(total)))
418
    sinusoids = np.sum(Es.shape[0] * Es.shape[1] - np.sum(ski.img_as_bool(total)))
419
    ratio.append(tissue.astype(float) / sinusoids)
420
421
422
423
424
425
426
427
428
# Pickle results
429
430
results = {}
431
results["ratio"] = ratio
432
results["inflcount"] = inflcount
433
results["inflsize"] = inflsize
434
results["inflstd"] = inflstd
435
results["entropy"] = ent
436
results["entstd"] = entstd
437
results["directionality"] = direc
438
results["scale"] = scale
439
results["name"] = name
440
results["ID"] = imageid
441
results["roylac"] = nonlac
442
443
F = open("results/thread_%s" % sys.argv[1], "w")
444
pickle.dump(results, F)
445
F.close()
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460