-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImage_Analysis.py
More file actions
445 lines (335 loc) · 16.1 KB
/
Image_Analysis.py
File metadata and controls
445 lines (335 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# BEWARE ALL YE WHO ENTER
# This program stores all data in ram, meaning that if you're analysing a lot of data, you need a lot of ram
# Resets all variables
from IPython import get_ipython
get_ipython().magic('reset -sf')
# Import libraries
import numpy as np
import scipy.ndimage
import matplotlib.pyplot as plt
import cv2
import skimage.color
import skimage.filters
import skimage.viewer
import skimage.io
import skvideo.io
import os
import gc
import math as m
import statistics as stat
import sys
from itertools import compress
#Change working directory
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
print("This is the cwd: ",os.getcwd())
def loadDataAsGrey(fileName,allFrames,firstFrame,lastFrame):
# Function by Johannes Koblitz Aaen
# This function loads a RGB video file and converts it to a greyscale image without weighting
# fileName is the name of the video file in the same folder as the script
# If allFrames!=False then the funciton uses all frames from the file
# firstFrame defines the first frame in the desired interval, while lastFrame defines the last
# Load data
videodata = skvideo.io.vread("{}".format(fileName))
# Define colour weights. [1,1,1] for 1:1 conversion.
rgb_weights = [1, 1, 1]
# Find image dimensions, and define initial array (this could be optimized to one line. See later funcitons.)
vShape=videodata.shape
greyarray = np.zeros((vShape[1],vShape[2]))
# Define frames to be converted
if lastFrame<=firstFrame:
raise Exception("lastFrame must be higher than firstFrame. Your firstFrame was {} and lastFrame was {}.".format(firstFrame,lastFrame))
if firstFrame<=0 or lastFrame<=0:
raise Exception("Your defined frames must be higher than 0. Your firstFrame was {} and lastFrame was {}.".format(firstFrame,lastFrame))
if firstFrame>vShape[0] or lastFrame>vShape[0]:
raise Exception("Your defined frames must not be higher than {}. Your firstFrame was {} and lastFrame was {}.".format((vShape[0]),firstFrame,lastFrame))
if allFrames==False:
imrange=range(firstFrame-1,lastFrame)
else:
imrange=range(vShape[0])
for i in imrange:
# Load one frame, then convert with weights
oneimage=videodata[i,:,:]
greyscale_image = np.dot(oneimage[...,:3],rgb_weights)/(9)
# Add greyscale frame to array
greyarray=np.dstack((greyarray,greyscale_image))
# Remove the first frame, then clear unused memory
greyarray=np.delete(greyarray,0,axis=2)
gc.collect()
return greyarray
def removeaverage(array):
# Function by Johanna Neumann Sørensen & Johannes Koblitz Aaen
# Removes the average background of the image
#Calculates average 2D array of 3D array
average=np.mean(array,axis=2)
# Removes the average of all the layers and stacks the new layers on top of each other
result = []
for i in range(array.shape[2]):
new = array[:,:,i]-average
result.append(new)
result = np.array(np.dstack(result))
# Removes values under 0
result[result<0]=0
return result
def gaussianfilter(array, sigma):
# Gaussian blurs the image according to input sigma value
blurred = scipy.ndimage.gaussian_filter(array, sigma)
return blurred
def threshold(array):
# Function by Johanna Neumann Sørensen
# Puts adaptive threshold on the image
#Finds the adaptive threshhold
t=skimage.filters.threshold_otsu(array)
#Binary threshhold
mask = array > t
#Applies the threshhold
sel = np.zeros_like(array)
sel[mask] = array[mask]
return sel
# import required for slideShow function
from matplotlib.widgets import Slider
def slideShow(Movie_stack,mapput):
# Original code by Murat Nulati Yesibolati and Anders Brostrøm
# Modified by Johannes Koblitz Aaen
# note: mapput should be changed to an optional argument
# This function takes in a 3D array (must be numpy), and plots it as a 'slideshow', where it's possible to update the shown frame
# The input called Movie_stack is the required 3D array, mapput is a string corresponding to a cmap
# Beware that the slideshow may freeze
# Remember to change backline to 'Automatic' under Tools->Preferences->IPython Console->Graphics
# The Slider_val used later is defined as a global variable, to prevent freezing
global Slider_val
# Axes and stuff is defined, together with the slider and the shown image
fig1, ax = plt.subplots()
plt.subplots_adjust(bottom=0.20)
try:
test = ax.imshow(Movie_stack[:,:,0],cmap=mapput)
except:
test = ax.imshow(Movie_stack[:,:,0],cmap="Blues")
ax_slider = plt.axes([0.1, 0.1, 0.80, 0.04], facecolor="red")
# Defines slider value
Slider_val = Slider(ax_slider, 'Frame', 1, Movie_stack.shape[2], valinit=10, valstep=1)
def update(val):
# Updates the shown frame, accordingly
frame_val = int(Slider_val.val)-1
test.set_data(Movie_stack[:,:,frame_val])
plt.draw()
# Uncomment print statement to print current frame if desired
# WARNING: THIS WILL SPAM THE CONSOLE
#print(frame_val)
#If the slider value changes, call function above
Slider_val.on_changed(update)
def rotate(video,x1,y1,x2,y2,orientation):
# Function by Johanna Neumann Sørensen
# This function can rotate an video so that a tilted line in the video becomes vertical or horizontal
# The input requires two sets of coordinates that the tilted line passes through
# Slope of the tilted line
a = (y1-y2)/(x2-x1)
# Rotation angles depending on the desired orientation
angleh = -m.degrees(m.atan(a))
anglev = angleh - 90
# Rotates the video
if orientation == 'h':
rotatedvideo = scipy.ndimage.rotate(video,angleh)
if orientation == 'v':
rotatedvideo = scipy.ndimage.rotate(video,anglev)
return rotatedvideo
def templateMatch(imageStack,template,threshold):
# Function by Johannes Koblitz Aaen
# For best results, ensure the streak is in the middle of the template
# The lowest threshold value which should be used is ~0.4-0.5
# An empty list, in which the streak images are stored
streakImages = []
for frame in range(imageStack.shape[2]):
# workingCoords is apparently y, then x. Watch out for this.
workingCoords=cv2.matchTemplate(imageStack[:,:,frame].astype(np.float32),template.astype(np.float32),cv2.TM_CCOEFF_NORMED)
workingCoords=np.where(workingCoords >= threshold)
# Due to the method used, all coordinates have an offset which depends upon the dimensions of the used template
# Below is a fix, which solves the coordinate issue
templateW = int(round(template.shape[0]/2))
templateH = int(round(template.shape[1]/2))
workingCoords=np.array((workingCoords[1]+templateH,workingCoords[0]+templateW))
# Create binary image of identified points
binaryPoints = np.zeros_like(imageStack[:,:,frame])
binaryPoints[workingCoords[1],workingCoords[0]]=255
# Use the binary contours to find center coordinates
contours = cv2.findContours(binaryPoints.astype(np.uint8), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_TC89_L1)[0]
xCent = np.array([])
yCent = np.array([])
for c in contours:
x,y,w,h = cv2.boundingRect(c)
# If the height-to-width ratio if high enough, add the center value
if h/w >= 1.2:
centX = x + w/2
centY = y + h/2
xCent = np.append(xCent,centX)
yCent = np.append(yCent,centY)
# Crop images, and add to list streakImages
# First determine the variables used to cut out the image (from the center)
xMov = int(round((template.shape[1]+8)/2))
yMov = int(round((template.shape[0]+8)/2))
for i in range(len(xCent)):
# Extract and convert the center coordinates to nearest integer
xCenter = int(round(xCent[i]))
yCenter = int(round(yCent[i]))
# Add cutout to output if conditions are true
if imageStack[yCenter-yMov:yCenter+yMov,xCenter-xMov:xCenter+xMov,frame].shape[0] == 2*yMov:
if imageStack[yCenter-yMov:yCenter+yMov,xCenter-xMov:xCenter+xMov,frame].shape[1] == 2*xMov:
if imageStack[yCenter-yMov:yCenter+yMov,xCenter-xMov:xCenter+xMov,frame].shape[0] > 0:
streakImages.append(imageStack[yCenter-yMov:yCenter+yMov,xCenter-xMov:xCenter+xMov,frame])
# Shows the macthes' coordinates with red dots, and center with blue dots
# May not be a great idea to uncomment without removing the loop, and defining 'frame', but great for visualisation
# plt.figure()
# plt.imshow(imageStack[:,:,frame-1],cmap='Blues')
# plt.scatter(x=workingCoords[0], y=workingCoords[1], c='r', s=0.2)
# plt.scatter(x=xCent, y=yCent, c='b', s=0.4)
# plt.show()
return streakImages
def streakLength(streakImages,pixelSize):
# Function by Johannes Koblitz Aaen
# Takes a list of images in the same size, and determines the streak length
# Output is a tuple where first element is an array of lengths, and second is a list of bools used for sorting purposes
# False means an image has been removed, True means it has not
# Additionally, the profiles of the streaks are also given as a third element in a list of np arrays
# This is purely for visualisation, but can also be used for other purposes
# Remember: Specifically for the 50X lens, each pixel had a size of 132 nm.
# Make a check whether there are any matches at all
if len(streakImages)==0:
sys.exit("Something went wrong. streakLength received an empty list. Please check your template for templateMatch using plt.imshow().")
# Find approximate middle of image
imWidth = streakImages[0].shape[1]
midPoint = int(round(imWidth/2))
# Define value used for width of cutout, three empty lists and an array
if int(round(imWidth/6))!=0:
cutVal = int(round(imWidth/6))
else:
cutVal = 1
meanStrips = []
streakLen = np.array([])
removedImgs = []
cutOuts = []
# Create a list of cutouts to be used in analysis
for elem in streakImages:
cutOut = elem[:,midPoint-cutVal:midPoint+cutVal]
cutOuts.append(cutOut)
# Take a cutout of each image from middle of image to a set value, then find the mean on second axis
for elem in cutOuts:
# Mean of cutout along second axis is found, and added to a list
meanAxis = np.mean(elem,axis=1)
# Normalize for ease of analysis, and add to list
# meanStrips isn't used in the code, but can be output if a visualisation is needed
meanAxis = meanAxis/np.max(meanAxis)
meanStrips.append(meanAxis)
# Find the peaks of the strip, and add to list
peakvar = scipy.signal.find_peaks(meanAxis,height=np.max(meanAxis)*0.5,prominence=np.max(meanAxis)*0.1)
# Now for some sorting
# If there's less than one peak, do not add it, and go to next value
if len(peakvar[0])<2:
removedImgs.append(False)
streakLen = np.append(streakLen,0)
continue
else:
removedImgs.append(True)
# If the loop is still running, calculate the streak length from outer peaks
lenvar = np.max(peakvar[0])-np.min(peakvar[0])
streakLen = np.append(streakLen,lenvar)
# Due to the image analysis method there are going to be outliers
# These outliers are known false positives, and can therefore be removed
# This is done using the std Dev
# Create array without zero values
noZeros = list(compress(streakLen,removedImgs))
# Calculate mean and std of streaklengths
lenMean = stat.mean(noZeros)
stdDev = stat.pstdev(noZeros)
# Sort based on mean and stdDev
for i in range(len(streakLen)):
if (abs(streakLen[i]-lenMean)>2*stdDev):
removedImgs[i] = False
#streakSort = streakLen
streakSort = list(compress(streakLen,removedImgs))
# Convert list by multiplying each element with the pixelsize
# I really should had used numpy for this
# Hindsight is 20/20
for i in range(len(streakSort)):
streakSort[i] = streakSort[i]*pixelSize
# Clear unused memory
gc.collect()
return streakSort,removedImgs,meanStrips
def viewStreaks(list1,columns):
# Function by Johanna Neumann Sørensen
# This function takes a list of arrays (images) and displays them as one image
# The input requires a list of arrays and a number of wanted columns
# Empty figure with size
fig=plt.figure(figsize=(8, 8))
# Number of columns and rows in the final figure
columns = 4
rows = m.ceil(len(list1)/columns)
# Loop that adds each image to the final figure and displays it
for k in range(1,len(list1)+1):
fig.add_subplot(rows, columns, k)
plt.imshow(list1[k-1])
plt.show()
def acceptedStreaks(list1,boolean,columns):
# Function by Johanna Neumann Sørensen
# This function takes a list of arrays (images), sorts them and displays them in one figure
# The input requires a list of arrays, a boolean list containing True and False and a number of wanted columns
# Empty figure with specfied size
fig=plt.figure(figsize=(8, 8))
streaks = []
# Loop that filters out the declined streaks
for i in range(1,len(list1)+1):
if boolean[i-1] == True:
streaks.append(list1[i-1])
# Calculating number of rows needed
rows = m.ceil(len(streaks)/4)
# Loop that adds each image to the final figure and displays it
for k in range(1,len(streaks)+1):
fig.add_subplot(rows, columns, k)
plt.imshow(streaks[k-1])
plt.suptitle('Accepted Streaks')
plt.show()
def declinedStreaks(list1,boolean,columns):
# Function by Johanna Neumann Sørensen
# This function takes a list of arrays (images), sorts them and displays them in one figure
# The input requires a list of arrays, a boolean list containing True and False and a number of wanted columns
# Empty figure with specfied size
fig=plt.figure(figsize=(8, 8))
streaks = []
# Loop that filters out the accepted streaks
for i in range(1,len(list1)+1):
if boolean[i-1] == False:
streaks.append(list1[i-1])
# Calculating number of rows needed
rows = m.ceil(len(streaks)/4)
# Loop that adds each image to the final figure and displays it
for k in range(1,len(streaks)+1):
fig.add_subplot(rows, columns, k)
plt.imshow(streaks[k-1])
plt.suptitle('Declined Streaks')
plt.show()
# Actual Image Processing
# Just type in whatever you need underneath
# Example of data processing, using the example file:
# Load data
greyedVid = loadDataAsGrey("60 Hz 15 fps 0.8 V_Exp1.avi",False,60,410)
# Rotate and image process
greyedVid = rotate(greyedVid, 45, 22, 455, 14, 'h')
noAvg=removeaverage(greyedVid)
gaussBlurr=gaussianfilter(noAvg,1.0)
# Find a suitable template by using slideShow. Remember that frames in slideShow count from 1, while python generally counts from 0
#slideShow(gaussBlurr, 'gray')
tempG = gaussBlurr[38:64,174:189,209]
# Use template to get matches
Frames = templateMatch(gaussBlurr, tempG, 0.75)
# Use matches to find length of streaks
streakLengths=streakLength(Frames,132)
# Beregning af gennemsnitslig længde og standardafvigelse
lenmean=stat.mean(streakLengths[0])
lenStd=stat.pstdev(streakLengths[0])
# Print statements for automatisering
print('\nHow many matches: ',len(Frames))
print("N: ",sum(streakLengths[1]))
print('lengthMean: ',lenmean)
print('lengthStd: {} \n'.format(lenStd))