insituTEM 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- insituTEM/__ init __.py +14 -0
- insituTEM/insitu_DENS.py +318 -0
- insituTEM/insitu_DP.py +228 -8
- insituTEM/insitu_Diff.py +359 -0
- insituTEM/insitu_EMraw.py +460 -0
- insituTEM/insitu_IO.py +514 -111
- insituTEM/insitu_Preprocess.py +229 -25
- insituTEM/insitu_alignment.py +297 -11
- insitutem-0.1.7.dist-info/METADATA +20 -0
- insitutem-0.1.7.dist-info/RECORD +12 -0
- {insituTEM-0.1.5.dist-info → insitutem-0.1.7.dist-info}/WHEEL +1 -1
- insituTEM-0.1.5.dist-info/METADATA +0 -13
- insituTEM-0.1.5.dist-info/RECORD +0 -9
- {insituTEM-0.1.5.dist-info → insitutem-0.1.7.dist-info}/top_level.txt +0 -0
insituTEM/insitu_Diff.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""
|
|
2
|
+
in-situ TEM Toolbox - Diffraction pattern analysis
|
|
3
|
+
|
|
4
|
+
Assembles of functions related to process FFT and diffraction patterns
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
|
|
8
|
+
import insitu_Diff as Diff
|
|
9
|
+
Diff.f2tif(path,1)
|
|
10
|
+
|
|
11
|
+
Created on Aug 8 2024
|
|
12
|
+
@author: Meng Li
|
|
13
|
+
"""
|
|
14
|
+
import numpy as np
|
|
15
|
+
import cv2
|
|
16
|
+
# from skimage.feature import blob_dog
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
# from scipy import ndimage
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
# import pandas as pd
|
|
22
|
+
|
|
23
|
+
def getFFT(im):
|
|
24
|
+
"""
|
|
25
|
+
Function to calculate FFT of the image
|
|
26
|
+
input: im- np array of image data
|
|
27
|
+
output: fft image
|
|
28
|
+
"""
|
|
29
|
+
# Get the original dimensions of the image
|
|
30
|
+
h, w = im.shape
|
|
31
|
+
|
|
32
|
+
size = min(h,w) #in case h w don't match
|
|
33
|
+
im_crop=im[:size,:size] #crop image
|
|
34
|
+
fft_image = np.fft.fft2(im_crop)
|
|
35
|
+
fft_shifted = np.fft.fftshift(fft_image)
|
|
36
|
+
fft = 20 * np.log(np.abs(fft_shifted) + 1e-10) # Add small constant to avoid log(0)
|
|
37
|
+
|
|
38
|
+
return fft
|
|
39
|
+
|
|
40
|
+
def spotFFT(fft, sigmaMax=2.5, sigmaMin=2.45, sigma_ratio=20, thrs=0.98, R_tol=2, fftfilter=3, tolerance=5, display=True):
|
|
41
|
+
"""
|
|
42
|
+
Function to get diffraction spot positions in FFT
|
|
43
|
+
Input: fft- FFT image
|
|
44
|
+
sigmaMax, sigmaMin, sigma_ratio, thrs - Parameters for blob detection
|
|
45
|
+
R_tol - tolerance of center spot
|
|
46
|
+
fftfilter- filtersize to denoise FFT to make diffraction spots more visible
|
|
47
|
+
tolerance - tolerance for symmetry matching
|
|
48
|
+
|
|
49
|
+
Output:
|
|
50
|
+
Data of all detected spots: X(px) Y(px)
|
|
51
|
+
Image labeling the spots
|
|
52
|
+
"""
|
|
53
|
+
from skimage.feature import blob_dog
|
|
54
|
+
import matplotlib.pyplot as plt
|
|
55
|
+
from scipy import ndimage
|
|
56
|
+
import math
|
|
57
|
+
|
|
58
|
+
import numpy as np
|
|
59
|
+
import pandas as pd
|
|
60
|
+
|
|
61
|
+
h, w = fft.shape
|
|
62
|
+
size = max(h, w)
|
|
63
|
+
scale_fft = 1 / (size)
|
|
64
|
+
|
|
65
|
+
x_c, y_c = int(w / 2), int(h / 2)
|
|
66
|
+
image = ndimage.maximum_filter(fft, size=fftfilter, mode='wrap')
|
|
67
|
+
blobs_dog = blob_dog(image, max_sigma=sigmaMax, min_sigma=sigmaMin, threshold=thrs, sigma_ratio=sigma_ratio, exclude_border=True)
|
|
68
|
+
blobs_dog[:, 2] *= math.sqrt(2)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
data = []
|
|
73
|
+
blob_set = set()
|
|
74
|
+
|
|
75
|
+
for j in range(len(blobs_dog)):
|
|
76
|
+
y, x = blobs_dog[j, 1] - y_c, blobs_dog[j, 0] - x_c
|
|
77
|
+
distance = math.sqrt(x**2 + y**2)
|
|
78
|
+
if distance < R_tol:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
data.append([blobs_dog[j, 1], blobs_dog[j, 0]])
|
|
82
|
+
blob_set.add((blobs_dog[j, 1], blobs_dog[j, 0]))
|
|
83
|
+
|
|
84
|
+
# Ensure symmetry
|
|
85
|
+
for x, y in data:
|
|
86
|
+
sym_x = 2 * x_c - x
|
|
87
|
+
sym_y = 2 * y_c - y
|
|
88
|
+
|
|
89
|
+
if not any(math.isclose(sym_x, bx, abs_tol=tolerance) and math.isclose(sym_y, by, abs_tol=tolerance) for bx, by in blob_set):
|
|
90
|
+
data.append([sym_x, sym_y])
|
|
91
|
+
blob_set.add((sym_x, sym_y))
|
|
92
|
+
|
|
93
|
+
data = np.array(data)
|
|
94
|
+
|
|
95
|
+
if display == True:
|
|
96
|
+
fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharex=True, sharey=True)
|
|
97
|
+
ax = axes.ravel()
|
|
98
|
+
ax[0].set_title('FFT')
|
|
99
|
+
ax[0].imshow(fft)
|
|
100
|
+
ax[1].set_title('DP detection')
|
|
101
|
+
ax[1].imshow(image)
|
|
102
|
+
plt.tight_layout()
|
|
103
|
+
plt.scatter(data[:, 0], data[:, 1], marker='+', color='red')
|
|
104
|
+
for i, (x_i, y_i) in enumerate(zip(data[:, 0], data[:, 1])):
|
|
105
|
+
plt.annotate(str(i+1), (x_i, y_i), textcoords="offset points", xytext=(0,10), ha='center', color='white')
|
|
106
|
+
|
|
107
|
+
return data
|
|
108
|
+
|
|
109
|
+
def DP_mask(fft,circle_radius=10, center_rad=1,sigmaMax=2.5, sigmaMin=2.45, sigma_ratio=20, thrs=0.98, R_tol=2, fftfilter=3, tolerance=5):
|
|
110
|
+
"""
|
|
111
|
+
Create a mask image with circles drawn at detected diffraction points.
|
|
112
|
+
|
|
113
|
+
Parameters:
|
|
114
|
+
image_shape (tuple): Shape of the image (height, width).
|
|
115
|
+
points (np.ndarray): Array of detected points [[x1, y1], [x2, y2], ...].
|
|
116
|
+
circle_radius (int): Radius of the circles to draw.
|
|
117
|
+
center_rad: radius of center circle
|
|
118
|
+
sigma...: parameters for detect DP
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
np.ndarray: Mask image with drawn circles.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
mask = np.zeros(fft.shape, dtype=np.uint8)
|
|
125
|
+
points= spotFFT(fft,sigmaMax,sigmaMin,sigma_ratio,thrs,R_tol,fftfilter,tolerance,display=False)
|
|
126
|
+
for x, y in points:
|
|
127
|
+
cv2.circle(mask, (int(x), int(y)), circle_radius, 255, thickness=-1)
|
|
128
|
+
|
|
129
|
+
if center_rad != 0:
|
|
130
|
+
center=int(fft.shape[0]/2)
|
|
131
|
+
cv2.circle(mask, (center, center), center_rad, 255, thickness=-1)
|
|
132
|
+
return mask
|
|
133
|
+
|
|
134
|
+
def maskedIFFT(image,mask,mix_ratio=0.3):
|
|
135
|
+
"""
|
|
136
|
+
generate masked image from FFT
|
|
137
|
+
Input: original image; mask; mix_ratio for IFFT filtering
|
|
138
|
+
Output: FFT_image, IFFT image, IFFT filterd image
|
|
139
|
+
Example: fft_image, ifft_image,filtered_image=maskedIFFT(image,mask,mix_ratio)
|
|
140
|
+
"""
|
|
141
|
+
h,w=image.shape
|
|
142
|
+
|
|
143
|
+
if h !=w:
|
|
144
|
+
d=min(h,w)
|
|
145
|
+
image=image[0:d,0:d]
|
|
146
|
+
|
|
147
|
+
# Perform FFT on the image
|
|
148
|
+
fft = cv2.dft(np.float32(image), flags=cv2.DFT_COMPLEX_OUTPUT)
|
|
149
|
+
fft_shifted = np.fft.fftshift(fft)
|
|
150
|
+
fft_image=np.log(1 + cv2.magnitude(fft_shifted[:, :, 0], fft_shifted[:, :, 1])) #using fft_image to apply mask results in empty image
|
|
151
|
+
|
|
152
|
+
# Ensure the mask image has the same size as the FFT
|
|
153
|
+
mask_image = cv2.resize(mask,(fft_shifted.shape[1], fft_shifted.shape[0]))
|
|
154
|
+
|
|
155
|
+
# Apply the mask to the FFT
|
|
156
|
+
masked_fft_shifted = cv2.bitwise_and(fft_shifted, fft_shifted, mask=mask_image)
|
|
157
|
+
|
|
158
|
+
# Perform IFFT on the masked FFT
|
|
159
|
+
masked_fft = np.fft.ifftshift(masked_fft_shifted)
|
|
160
|
+
masked_image = cv2.idft(masked_fft)
|
|
161
|
+
masked_image = cv2.magnitude(masked_image[:, :, 0], masked_image[:, :, 1])
|
|
162
|
+
|
|
163
|
+
#convert to 8bit images
|
|
164
|
+
fft_image=cv2.normalize(fft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
|
|
165
|
+
ifft_image=cv2.normalize(masked_image, None, 0, 128, cv2.NORM_MINMAX, cv2.CV_8U) #cv2.normalize(src, dst, alpha, beta, norm_type, dtype)
|
|
166
|
+
|
|
167
|
+
# # Normalize the pixel values to the range of 0-255
|
|
168
|
+
ifft_image_8 = cv2.normalize(ifft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Add IFFT to original image with mix_ratio
|
|
172
|
+
masked_image_normalized = cv2.normalize(ifft_image, None, 0, int(255*mix_ratio), cv2.NORM_MINMAX, cv2.CV_8U)
|
|
173
|
+
image_normalized = cv2.normalize(image, None, 0, int(255*(1-mix_ratio*0.5)), cv2.NORM_MINMAX, cv2.CV_8U)
|
|
174
|
+
|
|
175
|
+
filtered_image=cv2.add(image_normalized,masked_image_normalized)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
return fft_image, ifft_image, filtered_image
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def measureFFT(fft,path,scale,j=0,sigmaMax=2.5,sigmaMin=2.4,thrs=0.97,R_tol=2,fftfilter=2,savefig=True,displayfig=True):
|
|
182
|
+
"""
|
|
183
|
+
Function to measure diffraction spots in FFT
|
|
184
|
+
Input: fft- FFT image
|
|
185
|
+
path: path to save data
|
|
186
|
+
sigmaMax, sigmaMin, thrs - Parameters for blob detection
|
|
187
|
+
R_tol - tolerance of center spot
|
|
188
|
+
|
|
189
|
+
Output:
|
|
190
|
+
Data of all detected spots: Point X(px) Y(px) distance(px) angle(˚) 1/d (1/nm) d (nm)
|
|
191
|
+
Image labeling the spots
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
from skimage.feature import blob_dog
|
|
195
|
+
import matplotlib.pyplot as plt
|
|
196
|
+
from scipy import ndimage
|
|
197
|
+
import math
|
|
198
|
+
|
|
199
|
+
import numpy as np
|
|
200
|
+
import pandas as pd
|
|
201
|
+
|
|
202
|
+
h,w = fft.shape
|
|
203
|
+
size=max(h,w)
|
|
204
|
+
scale_fft=1/(scale*size)
|
|
205
|
+
|
|
206
|
+
x_c=int(w/2)
|
|
207
|
+
y_c=int(h/2)
|
|
208
|
+
center=x_c/2 #center of cropped_fft
|
|
209
|
+
|
|
210
|
+
fft_crop=fft[x_c-int(center):x_c+int(center),y_c-int(center):y_c+int(center)]
|
|
211
|
+
image= ndimage.maximum_filter(fft_crop, size=fftfilter, mode='wrap')
|
|
212
|
+
blobs_dog = blob_dog(image, max_sigma=sigmaMax,min_sigma=sigmaMin, threshold=thrs,sigma_ratio=10,exclude_border=True)
|
|
213
|
+
blobs_dog[:, 2] = blobs_dog[:, 2] * math.sqrt(2)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
title = 'DP detection'
|
|
217
|
+
title1= 'FFT_'+str(j)
|
|
218
|
+
fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharex=True, sharey=True)
|
|
219
|
+
ax = axes.ravel()
|
|
220
|
+
|
|
221
|
+
ax[0].set_title(title1)
|
|
222
|
+
ax[0].imshow(fft_crop)
|
|
223
|
+
ax[1].set_title(title)
|
|
224
|
+
ax[1].imshow(image)
|
|
225
|
+
|
|
226
|
+
plt.tight_layout()
|
|
227
|
+
|
|
228
|
+
pathout=path[:-4]+'_'+str(j)+'_FFT.csv'
|
|
229
|
+
pathout_img=path[:-4]+'_'+str(j)+'_DP.tif'
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
data=np.zeros((len(blobs_dog)-1,8))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
idx=0
|
|
236
|
+
for j in range(len(blobs_dog)):
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
y=blobs_dog[j,1]-center
|
|
240
|
+
x=blobs_dog[j,0]-center
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
distance=math.sqrt(x**2+y**2)#px distance to center
|
|
244
|
+
if distance<R_tol: #skip center point
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
theta=math.atan2(x,y)#atan gives +-90˚ values, atan2 gives 180
|
|
248
|
+
angle=0-math.degrees(theta)
|
|
249
|
+
data[idx,:7] = [j , blobs_dog[j, 1], blobs_dog[j, 0], distance, angle, distance * scale_fft, 1 / (distance * scale_fft)]
|
|
250
|
+
idx += 1
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
plt.scatter(data[:,1],data[:,2],marker='+',color='red')
|
|
254
|
+
for i, (x_i, y_i) in enumerate(zip(data[:,1],data[:,2])):
|
|
255
|
+
plt.annotate(str(i+1), (x_i, y_i), textcoords="offset points", xytext=(0,10), ha='center',color='white')
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
data[:,7]=data[:,4]-data[0,4]
|
|
259
|
+
|
|
260
|
+
columns= ["Point","X(px)","Y(px)","distance(px)","angle to X(˚)","1/d (1/nm)","d (nm)","angle to P1(˚)"]
|
|
261
|
+
# Set pandas global display format for floats
|
|
262
|
+
pd.options.display.float_format = '{:.3f}'.format
|
|
263
|
+
# Create a DataFrame
|
|
264
|
+
df = pd.DataFrame(data, columns=columns)
|
|
265
|
+
df.to_csv(pathout,index=False)
|
|
266
|
+
if savefig:
|
|
267
|
+
plt.savefig(pathout_img,bbox_inches='tight', pad_inches=0.1,dpi=150)
|
|
268
|
+
if displayfig:
|
|
269
|
+
plt.show()
|
|
270
|
+
|
|
271
|
+
return df
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def FFT2Polar(fft):
|
|
277
|
+
"""
|
|
278
|
+
Function to convert fft image to polar image
|
|
279
|
+
input: fft pimage
|
|
280
|
+
output: polar FFt image
|
|
281
|
+
"""
|
|
282
|
+
import cv2
|
|
283
|
+
#function to convert fft image to polar coordinates from the center from 0-180 deg
|
|
284
|
+
center = (fft.shape[1] // 2, fft.shape[0] // 2)
|
|
285
|
+
polar_fft = cv2.linearPolar(fft, center, center[0], cv2.WARP_FILL_OUTLIERS)
|
|
286
|
+
# polar_fft= polar_fft[:,:center[1]]
|
|
287
|
+
#resize the image to realistic size
|
|
288
|
+
resized_polar_fft = cv2.resize(polar_fft, (center[0], 360))
|
|
289
|
+
# plt.imshow(resized_polar_fft)
|
|
290
|
+
|
|
291
|
+
p_fft1=resized_polar_fft[:180,:]
|
|
292
|
+
p_fft2=resized_polar_fft[180:,:]
|
|
293
|
+
p_fft=p_fft1+p_fft2
|
|
294
|
+
return p_fft
|
|
295
|
+
|
|
296
|
+
def maskedFFT(image,mask):
|
|
297
|
+
"""
|
|
298
|
+
generate masked image from FFT
|
|
299
|
+
Input: original image; mask
|
|
300
|
+
Output: FFT_image, filtered IFFT image
|
|
301
|
+
"""
|
|
302
|
+
h,w=image.shape
|
|
303
|
+
|
|
304
|
+
if h !=w:
|
|
305
|
+
d=min(h,w)
|
|
306
|
+
image=image[0:d,0:d]
|
|
307
|
+
|
|
308
|
+
# Perform FFT on the image
|
|
309
|
+
fft = cv2.dft(np.float32(image), flags=cv2.DFT_COMPLEX_OUTPUT)
|
|
310
|
+
fft_shifted = np.fft.fftshift(fft)
|
|
311
|
+
fft_image=np.log(1 + cv2.magnitude(fft_shifted[:, :, 0], fft_shifted[:, :, 1]))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# Ensure the mask image has the same size as the FFT
|
|
315
|
+
mask_image = cv2.resize(mask,(fft_shifted.shape[1], fft_shifted.shape[0]))
|
|
316
|
+
|
|
317
|
+
# Apply the mask to the FFT
|
|
318
|
+
masked_fft_shifted = cv2.bitwise_and(fft_shifted, fft_shifted, mask=mask_image)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# Perform inverse FFT on the masked FFT
|
|
322
|
+
masked_fft = np.fft.ifftshift(masked_fft_shifted)
|
|
323
|
+
masked_image = cv2.idft(masked_fft)
|
|
324
|
+
masked_image = cv2.magnitude(masked_image[:, :, 0], masked_image[:, :, 1])
|
|
325
|
+
|
|
326
|
+
#convert to 8bit images
|
|
327
|
+
fft_image=cv2.normalize(fft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
|
|
328
|
+
masked_image=cv2.normalize(masked_image, None, 0, 128, cv2.NORM_MINMAX, cv2.CV_8U) #cv2.normalize(src, dst, alpha, beta, norm_type, dtype)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
return fft_image, masked_image
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def FFTstack(stackpath,ndfilersize=3,fps=15):
|
|
336
|
+
import tqdm
|
|
337
|
+
from scipy import ndimage
|
|
338
|
+
import tifffile
|
|
339
|
+
from insituTEM import insitu_IO as IO
|
|
340
|
+
"""
|
|
341
|
+
Apply FFT to the entire stack.
|
|
342
|
+
"""
|
|
343
|
+
pathout=stackpath[:-4]+'_FFT.tif'
|
|
344
|
+
stack =tifffile.imread(stackpath)
|
|
345
|
+
|
|
346
|
+
nFrames, h,w = stack.shape
|
|
347
|
+
print("------------------------------------------")
|
|
348
|
+
print("Getting FFT to the stack:")
|
|
349
|
+
with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
|
|
350
|
+
for i in tqdm.tqdm(range(nFrames)):
|
|
351
|
+
fr=stack[i]
|
|
352
|
+
fft=Diff.getFFT(fr)
|
|
353
|
+
if ndfilersize!=0:
|
|
354
|
+
fft = ndimage.maximum_filter(fft, size=ndfilersize, mode='wrap')
|
|
355
|
+
fft_image=cv2.normalize(fft, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
|
|
356
|
+
tif.write(fft_image, contiguous=True)
|
|
357
|
+
IO.tiff2avi(pathout,fps=15)
|
|
358
|
+
|
|
359
|
+
print("Conversion done!")
|