-
Notifications
You must be signed in to change notification settings - Fork 0
/
image_analysis.py
404 lines (317 loc) · 13.2 KB
/
image_analysis.py
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
# ============================================================================ #
# #
# Image analysis #
# A Python code snippets for doing image analysis #
# #
# Copyright (c) 2019-present Marco A. Lopez-Sanchez #
# #
# This Source Code Form is subject to the terms of the Mozilla Public #
# License, v. 2.0. If a copy of the MPL was not distributed with this #
# file, You can obtain one at http://mozilla.org/MPL/2.0/. #
# #
# Covered Software is provided under this License on an “AS IS” BASIS, #
# WITHOUT WARRANTY OF ANY KIND, either expressed, implied, or statutory, #
# including, without limitation, warranties that the Covered Software is #
# FREE OF DEFECTS, merchantable, fit for a particular purpose or #
# non-infringing. The entire risk as to the quality and performance #
# of the Covered Software is with You. Should any Covered Software prove #
# defective in any respect, You (not any Contributor) assume the cost of #
# any necessary servicing, repair, or correction. This disclaimer of #
# warranty constitutes an essential part of this License. No use of any #
# Covered Software is authorized under this License except under this #
# disclaimer. #
# #
# Version alpha #
# For details see: https://github.com/marcoalopez/image_analysis #
# #
# Requirements: #
# Python version 3.5 or higher #
# Numpy version 1.11 or higher #
# Matplotlib version 2.0 or higher #
# Pillow version #
# dcraw installed in the system (for the raw2tiff function) #
# #
# ============================================================================ #
# Import neccesary libraries
import os
import subprocess
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
def raw2tiff(path='auto',
dcraw_path='C:/Users/marco/Documents/dcraw/dcraw64.exe',
dcraw_arg='-v -w -H 0 -o 0 -q 3 -T',
raw_format='.NEF'):
""" Automate the conversion from RAW to other image format using the
script dcraw by Dave Coffin's.
Parameters
----------
path : string
the file path where the images to be converted are. If 'auto',
the default, the function will ask you for the folder location
through a file selection dialog.
dcraw_path : string
the file path where the dcraw file is located. If 'auto',
the default, the function will ask you for the dcraw file
location through a file selection dialog.
dcraw_arg : string
dcraw arguments. For details on this call dcraw in the
console or go to:
https://www.cybercom.net/~dcoffin/dcraw/dcraw.1.html
raw_format : string
the format of the raw images. Default: '.NEF'
Returns
-------
None
Requirements
------------
dcraw binary in the system
"""
if path == 'auto':
path = get_path()
if dcraw_path == 'auto':
pass
for filename in os.listdir(path):
if filename.endswith(raw_format):
print('converting {}' .format(filename))
subprocess.run(dcraw_path + ' ' + dcraw_arg + ' ' + path + filename, shell=False)
print(' ')
print('Done!')
return None
def RGB2gray(input_path='auto',
output_path='auto',
input_format='.tiff',
output_format='.tif'):
""" Automatically convert RGB images to 8-bit grayscale images
cointained in a specific folder.
Parameters
----------
input_path : string
the file path where the images to be converted are stored.
If 'auto', the default, the function will ask you for the
folder location through a file selection dialog.
output_path : string
the file path where the grayscale images will be saved.
If 'auto', the default, the function will ask you for the
folder location through a file selection dialog.
input_format : string
the format of the input images. Default: '.tiff'
output_format : string
the format of the input images. Default: '.tif'
Returns
-------
None
"""
if input_path == 'auto':
input_path = get_path()
if output_path == 'auto':
output_path = get_path()
for filename in os.listdir(input_path):
if filename.endswith(input_format):
print('converting {} and saving' .format(filename))
img = Image.open(input_path + filename).convert('L')
fn, ext = os.path.splitext(filename) # separate name and ext
img.save(output_path + fn + output_format)
print(' ')
print('Done!')
return None
def denoising_img_avg(save_as='denoise_img.tif',
path='auto',
file_type='.tif',
robust=True,
noise_floor=False):
""" Noise reduction by image averaging. Images should be aligned.
By noise we refer here to random fluctuations in brighness produced
by the CCD and CMOS sensors.
Parameters
----------
save_as : string
the name of the generated image, the format to use is determined from
the filename extension
path : string
the path to the folder containing the images to average. If 'auto',
the default, the function will ask you for the folder location
through a file selection dialog.
file_type : string
the image format to read
robust : bool, default True
if True the averaging method use the median. If false the mean.
noise_floor : bool, default False
if True, the noise floor is calculated.
Returns
-------
a numpy array with denoised image (and the std values of each pixel
if noise_floor=True). The latter is useful is you want to locate pixels
that yield large errors.
Examples
--------
>>> denoising_img_avg(path='C:/Users/name/Documents/my_images/')
>>> denoising_img_avg(save_as='new_image.tif', path='C:/Users/name/Documents/my_images/')
# For estimate and visualize the noise floor of the image do
>>> denoise_img, std_px_vals = denoising_img_avg(noise_floor=True)
>>> fig, ax = plt.subplots()
>>> im = ax.imshow(std_px_vals, cmap='cividis')
>>> fig.colorbar(im, ax=ax)
"""
if path == 'auto':
path = get_path()
# open and stack all images
print(' ')
print('Stacking images...')
count = 0
for filename in os.listdir(path):
if filename.endswith(file_type):
img = np.array(Image.open(path + filename).convert('L'))
if count == 0:
img_stack = img
else:
img_stack = np.dstack((img_stack, img))
count += 1
# denoise by averaging
print(' ')
print('Denoising image...')
if robust is True:
denoise_img = np.median(img_stack, axis=2)
else:
denoise_img = np.mean(img_stack, axis=2)
# convert from float to integer
denoise_img = np.rint(denoise_img)
# Estimate the noise floor if proceed
if noise_floor is True:
# TODO: ask whether the the noise floor is for a ROI
crop = input("Do you want to define a region of interest (ROI) to estimate the noise floor? (type 'y' or 'n'): ")
if crop == 'y':
x, y, xp, yp = input("Define the coordinates (x, y, x', y') (e.g. (1014, 192, 4692, 4644): ")
px_std_vals = np.std(img_stack[x - 1:y, xp - 1:yp, :], axis=2)
print(' ')
print('Estimating the noise floor...')
px_std_vals = np.std(img_stack, axis=2)
mean_std = np.mean(px_std_vals)
print(' ')
print('Noise floor:')
print('Mean of SD values =', round(mean_std, 2))
print('max, min =', round(np.max(px_std_vals), 2),
',', round(np.min(px_std_vals)))
# plot the image using matplotlib
fig, ax = plt.subplots()
im = ax.imshow(denoise_img, cmap='bone')
fig.colorbar(im, ax=ax)
fig.tight_layout()
# save the denoised image in the same path
img = Image.fromarray(denoise_img)
img.save(path + save_as)
if noise_floor is True:
return denoise_img, px_std_vals
else:
return denoise_img
def img_translation(ref_image, image, correct=False):
""" It uses registrer translation from scikit-image to estimate
and correct the...TODO
"""
# import nedded skimage libraries
try:
from skimage import io
from skimage.feature import register_translation
from scipy.ndimage import fourier_shift
except ImportError:
print('This function requires scikit-image installed in the system!')
# estimate shift
shift, error, diffphase = register_translation(ref_image, image)
print('Image shift is (x, y): ', shift)
print('Error =', error)
if correct is True:
offset_image = fourier_shift(np.fft.fftn(ref_image), shift)
corrected_image = np.fft.ifftn(offset_image)
io.imsave() # TODO
return shift, error, diffphase
def img_autocorrelation(image, plot=True):
""" Compute the autocorrelation of a image via Fourier transform
Parameters
----------
image : ndarray
Reference 8-bit gray image stored as ndarray
plot : bool
If true (default), make the plots
Returns
-------
TODO
Call functions
--------------
auto_corr_plot()
TODO
Example
-------
>>> image = np.array(Image.open('image.tif').convert('L'))
>>> img_autocorrelation(image)
"""
# check type and proceed TODO
if type(image) is not np.ndarray:
raise ValueError('Error: image must be entered as numpy.ndarray')
# generate an image of square size (nm * nm)
nm = np.min(np.shape(image))
data = image[:nm, :nm]
# Estimate the mean gray value
mean_gray = np.mean(data)
# Pad the array with zeros. This allows to obtain a smoother spectrum
# whenplotting the Fourier transform. It adds zero to the edges (last
# column and row) of the array/image/matrix. TODO-> Use np.pad instead
data_padded = np.zeros([nm + 1, nm + 1])
data_padded[:nm, :nm] = data
# Compute the 2D discrete Fourier transform of the image/matrix
fft_image = np.fft.fft2(data_padded)
abs_fft = np.abs(fft_image)**2
ifft_image = np.fft.ifft2(abs_fft)
# Compute autocorrelation
autocorrelation = np.abs(np.fft.fftshift(ifft_image / np.nanmax(ifft_image)))
c_inf = mean_gray**2 / np.mean(data_padded**2) # TODO: this is why others pad with the mean gray!
# extract radial profiles
y, x = np.indices(autocorrelation.shape)
centre = np.array([(x.max() - x.min()) / 2,
(y.max() - y.min()) / 2])
# Note: The autocorrelation function is the inverse Fourier Transform of the power spectrum
# plot the 2D power spectrum (if apply)
if plot is True:
spectrum_plot(autocorrelation, profile) # TODO
pass
def spectrum_plot():
pass
def make_video(path='auto',
ffmpeg_path='C:/Users/Marco/Documents/ffmpeg/bin/',
ffmpeg_arg='ffmpeg -r 1 -f image2 -s 1280x720 -i DSC_%04d.tif -vcodec libx264 -crf 25 movie.mp4'):
""" Make a video from a sequence of images using the the ffmpeg converter
Parameters
----------
path : string
the path whre the sequence of images are
ffmpeg_path : string
the path where the ffmpeg executable are
ffmpeg_arg : string
the ffmpeg arguments. For example:
http://hamelot.io/visualization/using-ffmpeg-to-convert-a-set-of-images-into-a-video/
Returns
-------
None
Requirements
------------
ffmpeg installed in the system
"""
if path == 'auto':
path = get_path()
os.chdir(path)
subprocess.run(ffmpeg_path + ffmpeg_arg, shell=False)
print(' ')
print('Done!')
return None
def get_path():
""" Get the folder path through a file selection dialog.
It uses tkinter"""
try:
import tkinter as tk
from tkinter import filedialog
root = tk.Tk()
root.withdraw()
root.attributes("-topmost", True)
file_path = filedialog.askdirectory()
except ImportError:
print('The script requires Python 3.5 or higher')
return file_path + '/'