insituTEM 0.1.6__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 +512 -111
- insituTEM/insitu_Preprocess.py +229 -25
- insituTEM/insitu_alignment.py +219 -44
- insitutem-0.1.7.dist-info/METADATA +20 -0
- insitutem-0.1.7.dist-info/RECORD +12 -0
- {insituTEM-0.1.6.dist-info → insitutem-0.1.7.dist-info}/WHEEL +1 -1
- insituTEM-0.1.6.dist-info/METADATA +0 -13
- insituTEM-0.1.6.dist-info/RECORD +0 -9
- {insituTEM-0.1.6.dist-info → insitutem-0.1.7.dist-info}/top_level.txt +0 -0
insituTEM/insitu_Preprocess.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"""
|
|
3
3
|
|
|
4
4
|
in-situ TEM Toolbox - PreProcess
|
|
5
|
-
By: Meng and Michael
|
|
6
|
-
Assembles of functions related to movie preprocess
|
|
5
|
+
By: Meng Li and Michael
|
|
6
|
+
Assembles of functions related to movie preprocess
|
|
7
7
|
Example:
|
|
8
8
|
|
|
9
9
|
import insitu_Preprocess as PP
|
|
@@ -18,7 +18,8 @@ import cv2
|
|
|
18
18
|
from scipy import signal
|
|
19
19
|
from PIL import Image
|
|
20
20
|
import matplotlib.pyplot as plt
|
|
21
|
-
|
|
21
|
+
import tifffile
|
|
22
|
+
import tqdm
|
|
22
23
|
|
|
23
24
|
def estimate_dup(A,B,threshold=1):
|
|
24
25
|
"""
|
|
@@ -66,44 +67,76 @@ def median_blur (score_list, i,thrs=150,wid=21):
|
|
|
66
67
|
def detect_blur_fft(image, size=20, thresh=15,vis=False):
|
|
67
68
|
"""
|
|
68
69
|
Function to detect if one frame is blurred using FFT
|
|
70
|
+
Example:
|
|
71
|
+
score,is_blur = PP.detect_blur_fft(image, size=20, thresh=15,vis=False)
|
|
72
|
+
Input:
|
|
69
73
|
image: grayscale image, default size: 128*128 px
|
|
70
|
-
|
|
74
|
+
size: filter size to get the background of FFT
|
|
71
75
|
thresh: threshold value for blur scores, smaller score, more blurry
|
|
72
|
-
|
|
76
|
+
Output:
|
|
77
|
+
mean: mean value of the frame
|
|
78
|
+
is_blur: whether the frame is blur
|
|
73
79
|
"""
|
|
80
|
+
from insituTEM import insitu_DP as DP
|
|
74
81
|
# grab the dimensions of the image and use the dimensions to derive the center (x, y)-coordinates
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
# h, w = image.shape
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
magnitude=DP.getFFT(image)
|
|
86
|
+
h,w = magnitude.shape
|
|
87
|
+
cX, cY = w //2, h//2
|
|
88
|
+
bar=int(h/100)
|
|
89
|
+
center=bar*10
|
|
90
|
+
#use only the center of the FFT
|
|
91
|
+
crop_h,crop_w=(h-cX)//2, (w-cY)//2
|
|
92
|
+
|
|
93
|
+
|
|
84
94
|
# compute the background of FFT to make the diffraaction spots more obvious
|
|
85
95
|
bg = cv2.blur(magnitude,(size,size))
|
|
86
96
|
|
|
87
97
|
enhanced =cv2.subtract(magnitude, bg)
|
|
98
|
+
#remove noise by mean
|
|
99
|
+
# enhanced=enhanced[crop_h:(crop_h+cY),crop_w:(crop_w+cX)]
|
|
100
|
+
|
|
88
101
|
#remove white noise from the substracted image
|
|
89
|
-
enhanced[enhanced<
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
enhanced[enhanced<20]=0
|
|
103
|
+
|
|
104
|
+
# Check the data type and convert if necessary
|
|
105
|
+
if enhanced.dtype != np.uint8:
|
|
106
|
+
# Normalize the image to the range [0, 255] if it has a different range
|
|
107
|
+
enhanced = cv2.normalize(enhanced, None, 0, 255, cv2.NORM_MINMAX)
|
|
108
|
+
# Convert the image to uint8
|
|
109
|
+
enhanced = enhanced.astype(np.uint8)
|
|
110
|
+
|
|
111
|
+
# Apply median filter
|
|
112
|
+
enhanced = cv2.medianBlur(enhanced, 3)
|
|
113
|
+
|
|
114
|
+
# enhanced = cv2.medianBlur(enhanced, 3)
|
|
115
|
+
# #remove center spot
|
|
116
|
+
enhanced[(cY - center): (cY + center), cX - center:cX + center] =0
|
|
117
|
+
enhanced[(cY - bar): (cY + bar), :]=0
|
|
118
|
+
enhanced[:, (cX - bar): (cX + bar)]=0
|
|
119
|
+
# enhanced[
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
croppedFFT=enhanced[crop_h:(crop_h+cY),crop_w:(crop_w+cX)]
|
|
92
123
|
|
|
93
124
|
if vis:
|
|
94
|
-
(fig, ax) = plt.subplots(1, 2, )
|
|
125
|
+
(fig, ax) = plt.subplots(1, 2,figsize=(10, 5) )
|
|
95
126
|
ax[0].imshow(image, cmap="gray")
|
|
96
127
|
ax[0].set_title("Input")
|
|
97
|
-
ax[0].set_xticks([])
|
|
98
|
-
ax[0].set_yticks([])
|
|
128
|
+
# ax[0].set_xticks([])
|
|
129
|
+
# ax[0].set_yticks([])
|
|
99
130
|
# display the magnitude image
|
|
100
|
-
ax[1].imshow(
|
|
131
|
+
ax[1].imshow(croppedFFT,cmap="viridis")
|
|
101
132
|
ax[1].set_title("Filtered FFT")
|
|
102
|
-
ax[1].set_xticks([])
|
|
103
|
-
ax[1].set_yticks([])
|
|
133
|
+
# ax[1].set_xticks([])
|
|
134
|
+
# ax[1].set_yticks([])
|
|
135
|
+
|
|
104
136
|
# show our plots
|
|
105
137
|
plt.show()
|
|
106
|
-
mean = np.mean(enhanced)*1000
|
|
138
|
+
# mean = np.mean(enhanced)*1000
|
|
139
|
+
mean = np.mean(croppedFFT)*1000
|
|
107
140
|
# print(mean)
|
|
108
141
|
# the image will be considered "blurry" if the mean value of the magnitudes is less than the threshold value
|
|
109
142
|
return (mean,mean<thresh)
|
|
@@ -198,4 +231,175 @@ def removeDPs(img,DP):
|
|
|
198
231
|
else:
|
|
199
232
|
for j in range(nDP):
|
|
200
233
|
position=DP[j]
|
|
201
|
-
removeDP(img,position)
|
|
234
|
+
removeDP(img,position)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def BlurDetection(path,wid=31):
|
|
238
|
+
"""
|
|
239
|
+
Function to remove blurry frames based on the blur score
|
|
240
|
+
Input: tif file path
|
|
241
|
+
Output: deblurred tif stack
|
|
242
|
+
"""
|
|
243
|
+
from insituTEM import insitu_IO as IO
|
|
244
|
+
|
|
245
|
+
indexout=path[:-4]+'_DB-index.csv'
|
|
246
|
+
DBout=path[:-4]+'_DB.csv'
|
|
247
|
+
DBout2=path[:-4]+'_Blur.csv'
|
|
248
|
+
|
|
249
|
+
stack = tifffile.imread(path)
|
|
250
|
+
nFrames,h,w = stack.shape #make sure the order of h and w!
|
|
251
|
+
index = []#index of remaining frames
|
|
252
|
+
score = []
|
|
253
|
+
|
|
254
|
+
print("------------------------------------------")
|
|
255
|
+
print("Calculating blur scores!")
|
|
256
|
+
|
|
257
|
+
for i in tqdm.tqdm(range(nFrames)):
|
|
258
|
+
fr=stack[i]
|
|
259
|
+
s = blur_score(fr)
|
|
260
|
+
score.append(s)
|
|
261
|
+
index.append(i)
|
|
262
|
+
|
|
263
|
+
length = len(score)
|
|
264
|
+
Blur=np.zeros((length,3))#[i,blur_score,blur_th]
|
|
265
|
+
Blur[:,0]=index
|
|
266
|
+
Blur[:,1]=score
|
|
267
|
+
|
|
268
|
+
plt.plot(score,'gray')
|
|
269
|
+
plt.show()
|
|
270
|
+
|
|
271
|
+
print("------------------------------------------")
|
|
272
|
+
print("Check blur detection!")
|
|
273
|
+
|
|
274
|
+
thrs=int(input( 'Input blur threshold(default: 70): '))
|
|
275
|
+
while thrs!=0:
|
|
276
|
+
index2=[]
|
|
277
|
+
|
|
278
|
+
for i in tqdm.tqdm(range(len(score))):
|
|
279
|
+
medthres,is_blur=median_blur (score, i,thrs,wid=wid)#wid must be odd
|
|
280
|
+
# DB[index[i],3]=is_blur
|
|
281
|
+
Blur[i,2]=medthres
|
|
282
|
+
if is_blur == 0:
|
|
283
|
+
index2.append(index[i])
|
|
284
|
+
|
|
285
|
+
plt.plot(Blur[:,0],Blur[:,1],'gray',Blur[:,0],Blur[:,2]-thrs,'r',Blur[:,0],Blur[:,2]+thrs,'r')
|
|
286
|
+
plt.show()
|
|
287
|
+
thrs=int(input( 'Press 0 if you are satisfied, press corrected threshold if you want to change: '))
|
|
288
|
+
|
|
289
|
+
IO.writeCSV(DBout2,Blur)
|
|
290
|
+
|
|
291
|
+
result= np.zeros((len(index2),2))
|
|
292
|
+
for i in range(len(index2)):
|
|
293
|
+
result[i,0]=i
|
|
294
|
+
result[i,1]=index2[i]
|
|
295
|
+
|
|
296
|
+
IO.writeCSV(indexout,result)
|
|
297
|
+
|
|
298
|
+
print("------------------------------------------")
|
|
299
|
+
print("Save blur removed file")
|
|
300
|
+
pathout2=path[:-4]+'_DBlur.tif'
|
|
301
|
+
|
|
302
|
+
with tifffile.TiffWriter(pathout2,bigtiff=True) as tif:
|
|
303
|
+
for j in tqdm.tqdm(range(len(index2))):
|
|
304
|
+
i=int(result[j,1])
|
|
305
|
+
fr=stack[i]
|
|
306
|
+
|
|
307
|
+
tif.write(fr, contiguous=True)
|
|
308
|
+
print("------------------------------------------")
|
|
309
|
+
print("Blur removed!")
|
|
310
|
+
print("==========================================")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def movie_preprocess(path,remove_DP=True,DP=[[0, 0, 1, 1],[0,0,2,2]],Crop=True,cropboarder=[0,0,0,0],Trim=False,start_time_mmss="00:00",end_time_mmss="00:00",grayscale=True,exp_time=0):
|
|
314
|
+
"""
|
|
315
|
+
Function to preprocess the raw TEM movie to tiff
|
|
316
|
+
Input:
|
|
317
|
+
path: file path of the movie
|
|
318
|
+
remove DP: if there are constant dead point in the movie to remove,
|
|
319
|
+
if so, input the DP value.
|
|
320
|
+
Crop: if crop the movie to remove the boarders
|
|
321
|
+
if so, input the cropboarder: [left, top, right, bottom]
|
|
322
|
+
Trim: if trim the movie length
|
|
323
|
+
if so, input the start and end time in mm:ss
|
|
324
|
+
grayscale: if convert the movie to grayscale
|
|
325
|
+
exp_time: if remove the duplicated frame based on the real exposure time.
|
|
326
|
+
if no need to remove duplicates, put exp_time = 0
|
|
327
|
+
"""
|
|
328
|
+
# print("=========================================")
|
|
329
|
+
print("ETEM movie preprocessor start!")
|
|
330
|
+
|
|
331
|
+
import moviepy.editor as mp
|
|
332
|
+
import moviepy.video.fx.all as vfx
|
|
333
|
+
from insituTEM import insitu_IO as IO
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
video = mp.VideoFileClip(path)
|
|
337
|
+
if Trim == True:
|
|
338
|
+
# Convert mm:ss to seconds
|
|
339
|
+
start_time_seconds = sum(x * int(t) for x, t in zip([60, 1], start_time_mmss.split(":")))
|
|
340
|
+
end_time_seconds = sum(x * int(t) for x, t in zip([60, 1], end_time_mmss.split(":")))
|
|
341
|
+
|
|
342
|
+
video = video.subclip(start_time_seconds, end_time_seconds)# cut the clip between t=0 and 28 secs.
|
|
343
|
+
|
|
344
|
+
fps = int(video.fps)
|
|
345
|
+
# fps= 15
|
|
346
|
+
w = int(video.w)
|
|
347
|
+
h = int(video.h)
|
|
348
|
+
nFrames = int(fps*video.duration)
|
|
349
|
+
|
|
350
|
+
print("fps=", fps, " w =",w, " h=", h, "duration= ",video.duration)
|
|
351
|
+
print("Total number of Frames=", nFrames)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
#Preview first frame
|
|
356
|
+
print("------------------------------------------")
|
|
357
|
+
print("Preview the processed frame: press 0 if you want to continue: ")
|
|
358
|
+
|
|
359
|
+
if Crop == True:
|
|
360
|
+
video = video.fx(vfx.crop, x1=cropboarder[0], y1=cropboarder[1], x2=w-cropboarder[2], y2=h-cropboarder[3])#crop movie snapshot
|
|
361
|
+
|
|
362
|
+
fr = video.get_frame(mp.cvsecs(1/fps))
|
|
363
|
+
|
|
364
|
+
if grayscale==True:
|
|
365
|
+
fr = cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)
|
|
366
|
+
|
|
367
|
+
if remove_DP==True:
|
|
368
|
+
removeDPs(fr,DP)
|
|
369
|
+
|
|
370
|
+
IO.showFr(fr)
|
|
371
|
+
prompt = input("Input 0 if you want to continue, other keys to break :")
|
|
372
|
+
|
|
373
|
+
if prompt != "0":
|
|
374
|
+
print("Stopped by user. Modify the cropboarder values and restart.")
|
|
375
|
+
return
|
|
376
|
+
else:
|
|
377
|
+
#continue to apply to the full movie
|
|
378
|
+
#remove duplicate frames with known fps conversion
|
|
379
|
+
if exp_time==0:
|
|
380
|
+
dup=1
|
|
381
|
+
else:
|
|
382
|
+
dup= fps*exp_time
|
|
383
|
+
|
|
384
|
+
nFramesOut=int(nFrames/dup)
|
|
385
|
+
print ('nFramesOut= ', nFramesOut)
|
|
386
|
+
print('nFramesIn= ',nFrames)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
pathout=path[:-4]+'_CutDB.tif'
|
|
390
|
+
|
|
391
|
+
print("------------------------------------------")
|
|
392
|
+
print("Saving duplicate removed processed frames~")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
|
|
396
|
+
for j in tqdm.tqdm(range(nFramesOut)):
|
|
397
|
+
i=j*dup
|
|
398
|
+
fr = video.get_frame(mp.cvsecs(i/fps))
|
|
399
|
+
if grayscale==True:
|
|
400
|
+
fr = cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)
|
|
401
|
+
if remove_DP==True:
|
|
402
|
+
removeDPs(fr,DP)
|
|
403
|
+
tif.write(fr, contiguous=True)
|
|
404
|
+
|
|
405
|
+
print("Conversion done!")
|
insituTEM/insitu_alignment.py
CHANGED
|
@@ -10,7 +10,7 @@ Example:
|
|
|
10
10
|
Al.get_matrix()
|
|
11
11
|
|
|
12
12
|
Created on Tue Jun 11 17:22:45 2019
|
|
13
|
-
By:
|
|
13
|
+
By: Meng Li and Michel
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
|
|
@@ -20,9 +20,9 @@ import cv2
|
|
|
20
20
|
from skimage import io #package for TIFF stack reading
|
|
21
21
|
import tifffile
|
|
22
22
|
from scipy import signal
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
import tqdm
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
from insituTEM import insitu_IO as IO
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def get_path():
|
|
@@ -59,7 +59,7 @@ Please choose the csv you want to use
|
|
|
59
59
|
return dx, dy, csvpath
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True, TMdenoise_window=5,extend=True, bg = 150):
|
|
62
|
+
def Adaptive_template_match(path, NOF = 50, csvout=True, TMout=True, TMdenoise=True, TMdenoise_window=5,extend=True, bg = 150):
|
|
63
63
|
'''
|
|
64
64
|
Function to produce adaptive template matching on tiff image stack based on ROI selection
|
|
65
65
|
input:
|
|
@@ -85,8 +85,8 @@ def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True,
|
|
|
85
85
|
import tifffile
|
|
86
86
|
import easygui
|
|
87
87
|
import tqdm
|
|
88
|
-
import insitu_IO as IO
|
|
89
|
-
import insitu_alignment as AL
|
|
88
|
+
from insituTEM import insitu_IO as IO
|
|
89
|
+
# from insituTEM import insitu_alignment as AL
|
|
90
90
|
|
|
91
91
|
###############################################################
|
|
92
92
|
## 1. Inital settings
|
|
@@ -109,7 +109,10 @@ def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True,
|
|
|
109
109
|
# Display cropped image
|
|
110
110
|
cv2.imshow("Template, press ENTER to confirm", template)
|
|
111
111
|
cv2.waitKey(0)
|
|
112
|
-
cv2.destroyWindow("Template, press ENTER to confirm")
|
|
112
|
+
# cv2.destroyWindow("Template, press ENTER to confirm")
|
|
113
|
+
cv2.destroyAllWindows()
|
|
114
|
+
for _ in range(5):
|
|
115
|
+
cv2.waitKey(1)
|
|
113
116
|
|
|
114
117
|
|
|
115
118
|
|
|
@@ -118,30 +121,28 @@ def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True,
|
|
|
118
121
|
meth='cv2.TM_CCOEFF_NORMED'
|
|
119
122
|
method = eval(meth)
|
|
120
123
|
w, h = template.shape[::-1]
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
124
|
+
|
|
125
|
+
print("------------------------------------------")
|
|
126
|
+
print("Calculating drift~")
|
|
127
|
+
with tifffile.TiffWriter(pathout2,bigtiff=True) as tif:
|
|
128
|
+
for i in tqdm.tqdm(range(nFrames)):
|
|
129
|
+
fr=stack[i]
|
|
130
|
+
# Apply template Matching
|
|
131
|
+
res = cv2.matchTemplate(fr,template,method)
|
|
132
|
+
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
|
|
133
|
+
top_left = max_loc
|
|
134
|
+
TM[i,0]=i
|
|
135
|
+
TM[i,1]=top_left[0]
|
|
136
|
+
TM[i,2]=top_left[1]
|
|
137
|
+
bottom_right = (top_left[0] + w, top_left[1] + h)
|
|
138
|
+
cv2.rectangle(fr,top_left, bottom_right, 255, 2)
|
|
139
|
+
|
|
140
|
+
if i%NOF==NOF-1:
|
|
141
|
+
template = fr[int(top_left[1]):int(top_left[1] + h), int(top_left[0]):int(top_left[0] + w)]#opencv coordinate: Y,x , from upper left corner of the image
|
|
142
|
+
if TMout==True:
|
|
143
|
+
tif.write(fr, contiguous=True)
|
|
144
|
+
|
|
145
|
+
|
|
145
146
|
TM[:,1]=TM[0,1]-TM[:,1]
|
|
146
147
|
TM[:,2]=TM[0,2]-TM[:,2]
|
|
147
148
|
|
|
@@ -149,7 +150,7 @@ def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True,
|
|
|
149
150
|
#4. Apply TM to movie
|
|
150
151
|
if TMdenoise==True:
|
|
151
152
|
#Get average to minimize total movement
|
|
152
|
-
TM=
|
|
153
|
+
TM=denoiseTM(TM,TMdenoise_window)
|
|
153
154
|
Mx=int(np.mean(TM[:,1]))
|
|
154
155
|
My=int(np.mean(TM[:,2]))
|
|
155
156
|
TM[:,1]=TM[:,1]-Mx
|
|
@@ -162,9 +163,13 @@ def Adaptive_template_match(path, NOF, csvout=True, TMout=True, TMdenoise=True,
|
|
|
162
163
|
if csvout==True:
|
|
163
164
|
IO.writeCSV(pathout3,TM,fmt='%1.1d')
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
TranslateStack(path,TM,bg=bg, extend = extend)
|
|
166
167
|
print("Adaptive template matching done!")
|
|
167
|
-
plt.show()
|
|
168
|
+
plt.show(block=False)
|
|
169
|
+
plt.pause(0.1)
|
|
170
|
+
plt.close()
|
|
171
|
+
|
|
172
|
+
|
|
168
173
|
|
|
169
174
|
|
|
170
175
|
def TranslateStack(movie_path,matrix,bg=150,extend=True):
|
|
@@ -201,7 +206,7 @@ def TranslateStack(movie_path,matrix,bg=150,extend=True):
|
|
|
201
206
|
|
|
202
207
|
#2d matrix flipped order of width and height to work with warpaffine method
|
|
203
208
|
print("\nApplying translation to stack\n")
|
|
204
|
-
with tifffile.TiffWriter(movie_pathout) as tif:
|
|
209
|
+
with tifffile.TiffWriter(movie_pathout,bigtiff=True) as tif:
|
|
205
210
|
|
|
206
211
|
for num in tqdm(range(nframes)):
|
|
207
212
|
temparray = img[num,:,:]
|
|
@@ -211,13 +216,6 @@ def TranslateStack(movie_path,matrix,bg=150,extend=True):
|
|
|
211
216
|
|
|
212
217
|
extend_img = cv2.copyMakeBorder(temparray, top, bottom, left, right, cv2.BORDER_CONSTANT, value=bg)
|
|
213
218
|
translated_img = cv2.warpAffine(extend_img,TM,(width,height),borderValue =bg)#fill with gray
|
|
214
|
-
#Generates an image translated by dx and dy
|
|
215
|
-
# if num == 0:
|
|
216
|
-
# tifffile.imwrite(movie_pathout, translated_img, append=False, bigtiff=True)
|
|
217
|
-
# #Overrides any previous files saved to the same path
|
|
218
|
-
# else:
|
|
219
|
-
# tifffile.imwrite(movie_pathout, translated_img, append=True, bigtiff=True)
|
|
220
|
-
# #appends onto the tiff stack
|
|
221
219
|
tif.write(translated_img, contiguous=True)
|
|
222
220
|
|
|
223
221
|
|
|
@@ -357,4 +355,181 @@ def template_match(imgpath, w=24):
|
|
|
357
355
|
for num in iterate:
|
|
358
356
|
TM[num,0] = dx[num]
|
|
359
357
|
TM[num,1] = dy[num]
|
|
360
|
-
IO.writeCSV(csvpathout,TM)
|
|
358
|
+
IO.writeCSV(csvpathout,TM)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def ATM_locglobal(path, NOF=50, csvout=True, TMout=True, TMdenoise=True, TMdenoise_window=5, extend=True, bg=150,search_margin_local=1, match_threshold=0.8):
|
|
362
|
+
'''
|
|
363
|
+
Function to produce adaptive template matching on TIFF image stack based on ROI selection with local and global selections
|
|
364
|
+
|
|
365
|
+
Input:
|
|
366
|
+
path: Image path for grayscale TIFF stack
|
|
367
|
+
NOF: Number of frames for template to grow
|
|
368
|
+
csvout: Whether to write the translation matrix
|
|
369
|
+
TMout: Whether to write the template match found feature
|
|
370
|
+
TMdenoise: Whether to denoise the measured matrix to remove sudden change
|
|
371
|
+
TMdenoise_window: Window size for TMdenoise
|
|
372
|
+
bg: Background color (0-255)
|
|
373
|
+
extend: Whether to extend the image for the translation (avoid crop)
|
|
374
|
+
match_threshold: Threshold for considering the new ROI position close to the previous one
|
|
375
|
+
|
|
376
|
+
Output:
|
|
377
|
+
Translated image stack
|
|
378
|
+
CSV file of translation matrix
|
|
379
|
+
Captured ROI location
|
|
380
|
+
|
|
381
|
+
Written by Meng Li (mona.mengli@gmail.com) 2024.07.06
|
|
382
|
+
'''
|
|
383
|
+
|
|
384
|
+
###############################################################
|
|
385
|
+
# 1. Initial settings
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
import sys
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
stack = tifffile.imread(path)
|
|
392
|
+
except OSError as e:
|
|
393
|
+
print(f"Error reading the TIFF file: {e}")
|
|
394
|
+
sys.exit(1)
|
|
395
|
+
|
|
396
|
+
# stack = tifffile.imread(path)
|
|
397
|
+
nFrames, h, w = stack.shape
|
|
398
|
+
|
|
399
|
+
TM = np.zeros((nFrames, 3)) # frame, TX, TY, max_val
|
|
400
|
+
pathout3 = path[:-4] + '_TM.csv'
|
|
401
|
+
pathout2 = path[:-4] + '_TM.tif'
|
|
402
|
+
|
|
403
|
+
###############################################################
|
|
404
|
+
# 2. Open the first frame and select ROI for template matching
|
|
405
|
+
img = stack[0]
|
|
406
|
+
|
|
407
|
+
# Select ROI for template matching, returns x1,y1,w,h
|
|
408
|
+
r = cv2.selectROI("Select ROI, press ENTER to confirm", img, fromCenter=False, showCrosshair=False)
|
|
409
|
+
x1,y1,template_w,template_h=int(r[0]),int(r[1]),int(r[2]),int(r[3])
|
|
410
|
+
x2=x1+template_w
|
|
411
|
+
y2=y1+template_h
|
|
412
|
+
|
|
413
|
+
cv2.destroyWindow("Select ROI, press ENTER to confirm")
|
|
414
|
+
|
|
415
|
+
# Crop image to get the template
|
|
416
|
+
# template = img[int(r[1]):int(r[1] + r[3]), int(r[0]):int(r[0] + r[2])]
|
|
417
|
+
template=img[y1:y2,x1:x2]
|
|
418
|
+
# template_h, template_w = template.shape
|
|
419
|
+
|
|
420
|
+
# Display cropped image
|
|
421
|
+
cv2.imshow("Template, press ENTER to confirm", template)
|
|
422
|
+
cv2.waitKey(0)
|
|
423
|
+
cv2.destroyAllWindows()
|
|
424
|
+
cv2.waitKey(1)
|
|
425
|
+
|
|
426
|
+
###############################################################
|
|
427
|
+
# 3. Apply template matching
|
|
428
|
+
method = cv2.TM_CCOEFF_NORMED
|
|
429
|
+
|
|
430
|
+
print("------------------------------------------")
|
|
431
|
+
print("Calculating drift~")
|
|
432
|
+
|
|
433
|
+
last_top_left = (x1,y1)
|
|
434
|
+
|
|
435
|
+
last_max_val = None
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
with tifffile.TiffWriter(pathout2, bigtiff=True) as tif:
|
|
440
|
+
for i in tqdm.tqdm(range(nFrames)):
|
|
441
|
+
fr = stack[i]
|
|
442
|
+
|
|
443
|
+
search_area_global=fr
|
|
444
|
+
|
|
445
|
+
# Apply global template matching
|
|
446
|
+
res_global = cv2.matchTemplate(search_area_global, template, method)
|
|
447
|
+
min_val_global, max_val_global, min_loc_global, max_loc_global = cv2.minMaxLoc(res_global)
|
|
448
|
+
|
|
449
|
+
global_top_left = max_loc_global
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
# Define the local search area around the last known ROI position
|
|
453
|
+
# search_margin_local = 1 # Local search margin
|
|
454
|
+
x1_local=int(x1-search_margin_local)
|
|
455
|
+
y1_local=int(y1-search_margin_local)
|
|
456
|
+
x2_local=int(x1+template_w+search_margin_local)
|
|
457
|
+
y2_local=int(y1+template_h+search_margin_local)
|
|
458
|
+
search_area_local = fr[y1_local:y2_local, x1_local:x2_local]
|
|
459
|
+
# Apply local template matching
|
|
460
|
+
res_local = cv2.matchTemplate(search_area_local, template, method)
|
|
461
|
+
min_val_local, max_val_local, min_loc_local, max_loc_local = cv2.minMaxLoc(res_local)
|
|
462
|
+
|
|
463
|
+
local_top_left = (x1_local + max_loc_local[0], y1_local + max_loc_local[1])
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# # start_x_local = max(last_top_left[0] - search_margin_local, 0)
|
|
468
|
+
# # start_y_local = max(last_top_left[1] - search_margin_local, 0)
|
|
469
|
+
# # end_x_local=last_top_left[0]+template_w+search_margin_local
|
|
470
|
+
# # end_y_local = last_top_left[1] +template_h + search_margin_local
|
|
471
|
+
# # # end_x_local = min(last_top_left[0]+template_w + search_margin_local, w)
|
|
472
|
+
# # # end_y_local = min(last_top_left[1] +template_h + search_margin_local, h])
|
|
473
|
+
|
|
474
|
+
# # search_area_local = fr[start_y_local:end_y_local, start_x_local:end_x_local]
|
|
475
|
+
|
|
476
|
+
# # Apply local template matching
|
|
477
|
+
# res_local = cv2.matchTemplate(search_area_local, template, method)
|
|
478
|
+
# min_val_local, max_val_local, min_loc_local, max_loc_local = cv2.minMaxLoc(res_local)
|
|
479
|
+
|
|
480
|
+
# local_top_left = (start_x_local + max_loc_local[0], start_y_local + max_loc_local[1])
|
|
481
|
+
|
|
482
|
+
# Use the local match if the match value is within the threshold of the previous match
|
|
483
|
+
if last_max_val is not None and max_val_local >= max_val_global * match_threshold:
|
|
484
|
+
top_left = (local_top_left[0] , local_top_left[1])
|
|
485
|
+
max_val = max_val_local
|
|
486
|
+
else:
|
|
487
|
+
top_left = (global_top_left[0], global_top_left[1])
|
|
488
|
+
max_val = max_val_global
|
|
489
|
+
|
|
490
|
+
TM[i, 0] = i
|
|
491
|
+
TM[i, 1] = top_left[0]
|
|
492
|
+
TM[i, 2] = top_left[1]
|
|
493
|
+
|
|
494
|
+
# Update the last known position and match value
|
|
495
|
+
last_top_left = top_left
|
|
496
|
+
last_max_val = max_val
|
|
497
|
+
|
|
498
|
+
# Draw rectangle for visualization
|
|
499
|
+
bottom_right = (top_left[0] + template_w, top_left[1] + template_h)
|
|
500
|
+
cv2.rectangle(fr, top_left, bottom_right, 255, 2)
|
|
501
|
+
|
|
502
|
+
# Periodically update the template
|
|
503
|
+
if i % NOF == NOF - 1:
|
|
504
|
+
template = fr[top_left[1]:bottom_right[1], top_left[0]:bottom_right[0]]
|
|
505
|
+
template_h, template_w = template.shape
|
|
506
|
+
|
|
507
|
+
if TMout:
|
|
508
|
+
tif.write(fr, contiguous=True)
|
|
509
|
+
|
|
510
|
+
TM[:, 1] = TM[0, 1] - TM[:, 1]
|
|
511
|
+
TM[:, 2] = TM[0, 2] - TM[:, 2]
|
|
512
|
+
|
|
513
|
+
###############################################################
|
|
514
|
+
# 4. Apply translation matrix to movie
|
|
515
|
+
if TMdenoise:
|
|
516
|
+
TM = denoiseTM(TM, TMdenoise_window)
|
|
517
|
+
Mx = int(np.mean(TM[:, 1]))
|
|
518
|
+
My = int(np.mean(TM[:, 2]))
|
|
519
|
+
TM[:, 1] -= Mx
|
|
520
|
+
TM[:, 2] -= My
|
|
521
|
+
|
|
522
|
+
print("------------------------------------------")
|
|
523
|
+
print("Saving files~")
|
|
524
|
+
|
|
525
|
+
plt.plot(TM[:, 0], TM[:, 1], 'b', TM[:, 0], TM[:, 2], 'r')
|
|
526
|
+
|
|
527
|
+
if csvout:
|
|
528
|
+
IO.writeCSV(pathout3, TM, fmt='%1.1d')
|
|
529
|
+
|
|
530
|
+
TranslateStack(path, TM, bg=bg, extend=extend)
|
|
531
|
+
|
|
532
|
+
print("Adaptive template matching done!")
|
|
533
|
+
plt.show()
|
|
534
|
+
|
|
535
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: insituTEM
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Author: Meng Li
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Requires-Dist: numpy
|
|
7
|
+
Requires-Dist: scipy
|
|
8
|
+
Requires-Dist: matplotlib
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: tifffile
|
|
11
|
+
Requires-Dist: tqdm
|
|
12
|
+
Requires-Dist: opencv-python
|
|
13
|
+
Requires-Dist: pillow
|
|
14
|
+
Requires-Dist: scikit-image
|
|
15
|
+
Requires-Dist: moviepy
|
|
16
|
+
Requires-Dist: ncempy
|
|
17
|
+
Requires-Dist: easygui
|
|
18
|
+
Requires-Dist: pystackreg
|
|
19
|
+
Requires-Dist: pyside6
|
|
20
|
+
Dynamic: author
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
insituTEM/__ init __.py,sha256=L4AFZSmHZkQu6J-tlqBoTJaBFltRFZ5PArhk8pmDmco,283
|
|
2
|
+
insituTEM/insitu_DENS.py,sha256=q2ZGbAoPPfsWbG8XlQqTdJa5_VNUNvjR9FzG2J3KkP4,10865
|
|
3
|
+
insituTEM/insitu_DP.py,sha256=0HMGT7j9ndpYPAgXCNGHkZeg9yJ4KI4SMCim2kiG6C0,10930
|
|
4
|
+
insituTEM/insitu_Diff.py,sha256=EpCMuprigdJKXaFbg9kIGZi_msKRqIDmdqvA1yofTco,11951
|
|
5
|
+
insituTEM/insitu_EMraw.py,sha256=O7rwdQW4STkQlYr3yImnpJX_VqjIExHgFqFIoKblYYE,15347
|
|
6
|
+
insituTEM/insitu_IO.py,sha256=za4ow7DPnv_YgroD_3fH12neXwYjw717w8jPzCRQvqg,25554
|
|
7
|
+
insituTEM/insitu_Preprocess.py,sha256=xKnHnv8dc0gTHzzEPW_DivzzEJHNFSNGRD_r7UM2l6U,12779
|
|
8
|
+
insituTEM/insitu_alignment.py,sha256=duH383I-jSStKhy6l7mzy9yijSl7Elf4JDFfV8ilFzM,19595
|
|
9
|
+
insitutem-0.1.7.dist-info/METADATA,sha256=J88Jc0obYHWJp7-89Xab0ZdsSNkDRvWIc9v-RtKTgbs,438
|
|
10
|
+
insitutem-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
insitutem-0.1.7.dist-info/top_level.txt,sha256=DEgrwrdz6iV62_cukvjFXu4dnZYupwvS41QyvTfcqjs,10
|
|
12
|
+
insitutem-0.1.7.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: insituTEM
|
|
3
|
-
Version: 0.1.6
|
|
4
|
-
Summary: A package of tools for processing in situ TEM data
|
|
5
|
-
Author: Meng Li
|
|
6
|
-
Requires-Dist: tifffile
|
|
7
|
-
Requires-Dist: moviepy
|
|
8
|
-
Requires-Dist: tqdm
|
|
9
|
-
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: opencv-python
|
|
11
|
-
Requires-Dist: scipy
|
|
12
|
-
Requires-Dist: matplotlib
|
|
13
|
-
|