insituTEM 0.1.6__tar.gz → 0.1.8__tar.gz
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-0.1.8/PKG-INFO +20 -0
- insitutem-0.1.8/insituTEM/__ init __.py +14 -0
- insitutem-0.1.8/insituTEM/insitu_DENS.py +318 -0
- insitutem-0.1.8/insituTEM/insitu_DP.py +381 -0
- insitutem-0.1.8/insituTEM/insitu_Diff.py +359 -0
- insitutem-0.1.8/insituTEM/insitu_EMraw.py +460 -0
- insitutem-0.1.8/insituTEM/insitu_IO.py +793 -0
- insitutem-0.1.8/insituTEM/insitu_Preprocess.py +405 -0
- {insituTEM-0.1.6 → insitutem-0.1.8}/insituTEM/insitu_alignment.py +219 -44
- insitutem-0.1.8/insituTEM.egg-info/PKG-INFO +20 -0
- {insituTEM-0.1.6 → insitutem-0.1.8}/insituTEM.egg-info/SOURCES.txt +4 -0
- insitutem-0.1.8/insituTEM.egg-info/requires.txt +14 -0
- insitutem-0.1.8/pyproject.toml +26 -0
- insitutem-0.1.8/setup.py +3 -0
- insituTEM-0.1.6/PKG-INFO +0 -12
- insituTEM-0.1.6/insituTEM/__ init __.py +0 -0
- insituTEM-0.1.6/insituTEM/insitu_DP.py +0 -161
- insituTEM-0.1.6/insituTEM/insitu_IO.py +0 -392
- insituTEM-0.1.6/insituTEM/insitu_Preprocess.py +0 -201
- insituTEM-0.1.6/insituTEM.egg-info/PKG-INFO +0 -12
- insituTEM-0.1.6/insituTEM.egg-info/requires.txt +0 -7
- insituTEM-0.1.6/setup.py +0 -18
- {insituTEM-0.1.6 → insitutem-0.1.8}/insituTEM.egg-info/dependency_links.txt +0 -0
- {insituTEM-0.1.6 → insitutem-0.1.8}/insituTEM.egg-info/top_level.txt +0 -0
- {insituTEM-0.1.6 → insitutem-0.1.8}/setup.cfg +0 -0
insitutem-0.1.8/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: insituTEM
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: A toolbox for processing in situ TEM data
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: numpy
|
|
8
|
+
Requires-Dist: scipy
|
|
9
|
+
Requires-Dist: matplotlib
|
|
10
|
+
Requires-Dist: pandas
|
|
11
|
+
Requires-Dist: tifffile
|
|
12
|
+
Requires-Dist: tqdm
|
|
13
|
+
Requires-Dist: opencv-python
|
|
14
|
+
Requires-Dist: pillow
|
|
15
|
+
Requires-Dist: scikit-image
|
|
16
|
+
Requires-Dist: moviepy
|
|
17
|
+
Requires-Dist: ncempy
|
|
18
|
+
Requires-Dist: easygui
|
|
19
|
+
Requires-Dist: pystackreg
|
|
20
|
+
Requires-Dist: pyside6
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
__version__ = '1.0.0'
|
|
3
|
+
__author__ = 'Meng Li'
|
|
4
|
+
|
|
5
|
+
import insitu_alignment as AL
|
|
6
|
+
import insitu_DP as DP
|
|
7
|
+
import insitu_IO as IO
|
|
8
|
+
import insitu_Preprocess as PP
|
|
9
|
+
import insitu_Diff as Diff
|
|
10
|
+
import insitu_EMraw as EM
|
|
11
|
+
import insitu_DENS as DENS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ['function1', 'Class2']
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
in-situ TEM Toolbox - DENS holder data process
|
|
3
|
+
|
|
4
|
+
Assembles of functions related to process raw data by DENS holders
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
|
|
8
|
+
from insitu_TEM import insitu_DENS as DENS
|
|
9
|
+
DENS.XXX
|
|
10
|
+
|
|
11
|
+
Created on Tue May 13 2025
|
|
12
|
+
@author: Meng Li mli4@bnl.gov
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Codes for Stream data process
|
|
18
|
+
"""
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
def read_pstrace_csv(filepath):
|
|
22
|
+
# Try several encodings until one works
|
|
23
|
+
for encoding in ['utf-16', 'utf-8', 'ISO-8859-1']:
|
|
24
|
+
try:
|
|
25
|
+
with open(filepath, 'r', encoding=encoding) as f:
|
|
26
|
+
lines = [line.strip() for line in f if line.strip()]
|
|
27
|
+
break
|
|
28
|
+
except UnicodeDecodeError:
|
|
29
|
+
continue
|
|
30
|
+
else:
|
|
31
|
+
raise UnicodeDecodeError("Failed to decode file using common encodings.")
|
|
32
|
+
|
|
33
|
+
# Detect measurement type (e.g., CV I vs E Scan 1)
|
|
34
|
+
measurement_type = None
|
|
35
|
+
for line in lines:
|
|
36
|
+
if "vs" in line and not line.lower().startswith("date"):
|
|
37
|
+
measurement_type = line
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
# Detect actual column header (like "V,µA" or "s,V")
|
|
41
|
+
header_index = None
|
|
42
|
+
for i, line in enumerate(lines):
|
|
43
|
+
if ',' in line and all(any(c.isalpha() for c in token) for token in line.split(',')):
|
|
44
|
+
header_index = i
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
if header_index is None:
|
|
48
|
+
raise ValueError("Could not find a valid data header (e.g., 'V,µA' or 's,V')")
|
|
49
|
+
|
|
50
|
+
headers = [h.strip() for h in lines[header_index].split(',')]
|
|
51
|
+
data_lines = lines[header_index + 1:]
|
|
52
|
+
|
|
53
|
+
# Read and convert numeric data only
|
|
54
|
+
data = []
|
|
55
|
+
for line in data_lines:
|
|
56
|
+
try:
|
|
57
|
+
row = [float(val) for val in line.split(',')]
|
|
58
|
+
data.append(row)
|
|
59
|
+
except ValueError:
|
|
60
|
+
continue # skip non-numeric lines
|
|
61
|
+
|
|
62
|
+
df = pd.DataFrame(data, columns=headers)
|
|
63
|
+
return measurement_type, df
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
import matplotlib.pyplot as plt
|
|
67
|
+
|
|
68
|
+
def plot_multiple_xy(datasets, labels=None, xlabel=None, ylabel=None, title=None):
|
|
69
|
+
"""
|
|
70
|
+
Plot multiple datasets (each as a 2-column DataFrame) on one plot.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
datasets: list of DataFrames with 2 columns
|
|
74
|
+
labels: list of labels for each line
|
|
75
|
+
xlabel, ylabel: optional axis labels
|
|
76
|
+
title: optional plot title
|
|
77
|
+
"""
|
|
78
|
+
fig, ax = plt.subplots(figsize=(6, 4), dpi=300)
|
|
79
|
+
|
|
80
|
+
colors = ['blue', 'green', 'red', 'orange', 'purple']
|
|
81
|
+
|
|
82
|
+
for i, df in enumerate(datasets):
|
|
83
|
+
x = df.iloc[:, 0]
|
|
84
|
+
y = df.iloc[:, 1]
|
|
85
|
+
label = labels[i] if labels else None
|
|
86
|
+
color = colors[i % len(colors)]
|
|
87
|
+
ax.plot(x, y, label=label, color=color, linewidth=1)
|
|
88
|
+
|
|
89
|
+
# X and Y = 0 guide lines
|
|
90
|
+
ax.axhline(0, color='gray', linestyle='-', linewidth=0.5)
|
|
91
|
+
ax.axvline(0, color='gray', linestyle='-', linewidth=0.5)
|
|
92
|
+
|
|
93
|
+
# Axis style: boxed
|
|
94
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
95
|
+
ax.spines[side].set_linewidth(1)
|
|
96
|
+
ax.spines[side].set_color('black')
|
|
97
|
+
|
|
98
|
+
# Axis labels
|
|
99
|
+
ax.set_xlabel(xlabel if xlabel else datasets[0].columns[0], fontsize=12, fontweight='bold', labelpad=10)
|
|
100
|
+
ax.set_ylabel(ylabel if ylabel else datasets[0].columns[1], fontsize=12, fontweight='bold', labelpad=10)
|
|
101
|
+
|
|
102
|
+
# Axis limits
|
|
103
|
+
# Extract x/y and lengths
|
|
104
|
+
x_list = [df.iloc[:, 0].values for df in datasets]
|
|
105
|
+
y_list = [df.iloc[:, 1].values for df in datasets]
|
|
106
|
+
all_x = np.concatenate(x_list)
|
|
107
|
+
all_y = np.concatenate(y_list)
|
|
108
|
+
# ax.set_xlim(all_x.min()*1.1, all_x.max()*1.1)
|
|
109
|
+
# ax.set_ylim(all_y.min()*1.2, all_y.max()*1.1)
|
|
110
|
+
# Add 10% margin to x and y limits
|
|
111
|
+
x_margin = (all_x.max() - all_x.min()) * 0.05
|
|
112
|
+
y_margin = (all_y.max() - all_y.min()) * 0.05
|
|
113
|
+
|
|
114
|
+
ax.set_xlim(all_x.min() - x_margin, all_x.max() + x_margin)
|
|
115
|
+
ax.set_ylim(all_y.min() - y_margin, all_y.max() + y_margin)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Ticks and layout
|
|
119
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
120
|
+
ax.grid(False)
|
|
121
|
+
|
|
122
|
+
# Title and legend
|
|
123
|
+
if title:
|
|
124
|
+
ax.set_title(title, fontsize=12,fontweight='bold', pad=10)
|
|
125
|
+
if labels:
|
|
126
|
+
ax.legend()
|
|
127
|
+
|
|
128
|
+
plt.tight_layout()
|
|
129
|
+
plt.show()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
mport matplotlib.pyplot as plt
|
|
133
|
+
|
|
134
|
+
def plot_xy(df, measurement_type=None):
|
|
135
|
+
"""
|
|
136
|
+
Plot single xy curve
|
|
137
|
+
"""
|
|
138
|
+
x_col = df.columns[0]
|
|
139
|
+
y_col = df.columns[1]
|
|
140
|
+
x = df[x_col]
|
|
141
|
+
y = df[y_col]
|
|
142
|
+
|
|
143
|
+
fig, ax = plt.subplots(figsize=(6,4),dpi=300) # square aspect ratio
|
|
144
|
+
|
|
145
|
+
# Plot data
|
|
146
|
+
ax.plot(x, y, linewidth=1, color='blue')
|
|
147
|
+
|
|
148
|
+
# Keep left and bottom axis
|
|
149
|
+
ax.spines['left'].set_visible(True)
|
|
150
|
+
ax.spines['bottom'].set_visible(True)
|
|
151
|
+
|
|
152
|
+
# Show top and right spine to make a box
|
|
153
|
+
ax.spines['top'].set_visible(True)
|
|
154
|
+
ax.spines['right'].set_visible(True)
|
|
155
|
+
|
|
156
|
+
# Draw all 4 spines in black (like a square)
|
|
157
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
158
|
+
ax.spines[side].set_linewidth(1)
|
|
159
|
+
ax.spines[side].set_color('black')
|
|
160
|
+
|
|
161
|
+
# Add x=0 and y=0 guide lines
|
|
162
|
+
ax.axhline(0, color='orange', linestyle='-', linewidth=0.5)
|
|
163
|
+
ax.axvline(0, color='orange', linestyle='-', linewidth=0.5)
|
|
164
|
+
|
|
165
|
+
# Axis label styling
|
|
166
|
+
ax.set_xlabel('Voltage (V)', fontsize=12, fontweight='bold', labelpad=10)
|
|
167
|
+
ax.set_ylabel('Current (uA)' , fontsize=12, fontweight='bold', labelpad=10)
|
|
168
|
+
|
|
169
|
+
# Title
|
|
170
|
+
if measurement_type:
|
|
171
|
+
ax.set_title(measurement_type, fontsize=12, fontweight='bold', pad=10)
|
|
172
|
+
|
|
173
|
+
# Tick styling
|
|
174
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
175
|
+
|
|
176
|
+
# Turn off grid
|
|
177
|
+
ax.grid(False)
|
|
178
|
+
|
|
179
|
+
plt.tight_layout()
|
|
180
|
+
plt.show()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Convert the plot to movie
|
|
184
|
+
|
|
185
|
+
import matplotlib.pyplot as plt
|
|
186
|
+
import matplotlib.animation as animation
|
|
187
|
+
import numpy as np
|
|
188
|
+
import cv2
|
|
189
|
+
import tifffile
|
|
190
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
191
|
+
import tqdm
|
|
192
|
+
|
|
193
|
+
def animate_CVplots(datasets, labels=["Scan 1", "Scan 2", "Scan 3"], xlabel="Voltage (V)", ylabel="Current (µA)", title="CV Scan",
|
|
194
|
+
fps=60, save_basename="cv_animation"):
|
|
195
|
+
"""
|
|
196
|
+
Function to animate the plot
|
|
197
|
+
datasets, # List of DataFrames, each with 2 columns (x, y) for a scan
|
|
198
|
+
labels=None, # Optional list of labels for each scan (used in legend)
|
|
199
|
+
xlabel=None, # Optional label for x-axis (default: first column name)
|
|
200
|
+
ylabel=None, # Optional label for y-axis (default: second column name)
|
|
201
|
+
title=None, # Optional plot title
|
|
202
|
+
fps=50, # fps of the input data. Original speed: 4 fps
|
|
203
|
+
save_basename="cv_animation" # Prefix for saved output files (AVI + TIFF stack)
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
# Setup plot and canvas
|
|
207
|
+
fig, ax = plt.subplots(figsize=(6, 4), dpi=150)
|
|
208
|
+
fig.subplots_adjust(left=0.15, right=0.95, top=0.9, bottom=0.15)
|
|
209
|
+
canvas = FigureCanvasAgg(fig)
|
|
210
|
+
|
|
211
|
+
# Extract x/y and lengths
|
|
212
|
+
x_list = [df.iloc[:, 0].values for df in datasets]
|
|
213
|
+
y_list = [df.iloc[:, 1].values for df in datasets]
|
|
214
|
+
lengths = [len(x) for x in x_list]
|
|
215
|
+
cum_lengths = np.cumsum(lengths)
|
|
216
|
+
total_frames = cum_lengths[-1]
|
|
217
|
+
|
|
218
|
+
# Axis formatting
|
|
219
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
220
|
+
ax.spines[side].set_linewidth(1)
|
|
221
|
+
ax.spines[side].set_color('black')
|
|
222
|
+
|
|
223
|
+
ax.axhline(0, color='gray', linewidth=0.5)
|
|
224
|
+
ax.axvline(0, color='gray', linewidth=0.5)
|
|
225
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
226
|
+
ax.set_xlabel(xlabel or datasets[0].columns[0], fontsize=12, fontweight='bold', labelpad=10)
|
|
227
|
+
ax.set_ylabel(ylabel or datasets[0].columns[1], fontsize=12, fontweight='bold', labelpad=10)
|
|
228
|
+
if title:
|
|
229
|
+
ax.set_title(title, fontsize=10)
|
|
230
|
+
|
|
231
|
+
# Prepare plot elements
|
|
232
|
+
colors = ['blue', 'green', 'red', 'purple']
|
|
233
|
+
lines, tips = [], []
|
|
234
|
+
for i in range(len(datasets)):
|
|
235
|
+
line, = ax.plot([], [], color=colors[i % len(colors)], label=(labels[i] if labels else None), linewidth=1)
|
|
236
|
+
tip, = ax.plot([], [], color=colors[i % len(colors)], marker='o', markersize=4)
|
|
237
|
+
lines.append(line)
|
|
238
|
+
tips.append(tip)
|
|
239
|
+
|
|
240
|
+
# Add live current value in top-left
|
|
241
|
+
value_text = ax.text(0.02, 0.95, '', transform=ax.transAxes,
|
|
242
|
+
fontsize=10, color='black', ha='left', va='top')
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if labels:
|
|
246
|
+
ax.legend()
|
|
247
|
+
|
|
248
|
+
# Axis limits
|
|
249
|
+
all_x = np.concatenate(x_list)
|
|
250
|
+
all_y = np.concatenate(y_list)
|
|
251
|
+
|
|
252
|
+
# Add 10% margin to x and y limits
|
|
253
|
+
x_margin = (all_x.max() - all_x.min()) * 0.05
|
|
254
|
+
y_margin = (all_y.max() - all_y.min()) * 0.05
|
|
255
|
+
|
|
256
|
+
ax.set_xlim(all_x.min() - x_margin, all_x.max() + x_margin)
|
|
257
|
+
ax.set_ylim(all_y.min() - y_margin, all_y.max() + y_margin)
|
|
258
|
+
# ax.set_xlim(all_x.min()*1.1, all_x.max()*1.1)
|
|
259
|
+
# ax.set_ylim(all_y.min()*1.2, all_y.max()*1.1)
|
|
260
|
+
|
|
261
|
+
# Video writer setup
|
|
262
|
+
canvas.draw()
|
|
263
|
+
width, height = canvas.get_width_height()
|
|
264
|
+
# fps=int(1000/interval)
|
|
265
|
+
aviout=save_basename+"_"+str(fps)+".avi"
|
|
266
|
+
out = cv2.VideoWriter(aviout, cv2.VideoWriter_fourcc(*'MJPG'), fps, (width, height))
|
|
267
|
+
# tiff_frames = []
|
|
268
|
+
|
|
269
|
+
# Setup TIFF writer with BigTIFF support
|
|
270
|
+
tif_writer = tifffile.TiffWriter(f"{save_basename}.tif", bigtiff=True)
|
|
271
|
+
|
|
272
|
+
# Render frames
|
|
273
|
+
print("Plotting the curves into movies~")
|
|
274
|
+
for frame in tqdm.tqdm(range(total_frames)):
|
|
275
|
+
ax.set_title(title, fontsize=12,fontweight='bold') # reset title in case of matplotlib bugs
|
|
276
|
+
|
|
277
|
+
for i, (x, y, line, tip) in enumerate(zip(x_list, y_list, lines, tips)):
|
|
278
|
+
start = cum_lengths[i - 1] if i > 0 else 0
|
|
279
|
+
end = cum_lengths[i]
|
|
280
|
+
if frame < start:
|
|
281
|
+
# Not started yet
|
|
282
|
+
line.set_data([], [])
|
|
283
|
+
tip.set_data([], [])
|
|
284
|
+
elif frame >= end:
|
|
285
|
+
# Completed
|
|
286
|
+
line.set_data(x, y)
|
|
287
|
+
tip.set_data([], [])
|
|
288
|
+
else:
|
|
289
|
+
# Active scan
|
|
290
|
+
idx = frame - start + 1
|
|
291
|
+
line.set_data(x[:idx], y[:idx])
|
|
292
|
+
tip.set_data([x[idx - 1]], [y[idx - 1]])
|
|
293
|
+
value_text.set_text(
|
|
294
|
+
f"{labels[i] if labels else f'Scan {i+1}'}\nV = {x[idx - 1]:.3f} V\nI = {y[idx - 1]:.3f} µA"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Render and convert to image
|
|
298
|
+
canvas.draw()
|
|
299
|
+
buf = np.frombuffer(canvas.tostring_rgb(), dtype=np.uint8)
|
|
300
|
+
image = buf.reshape((height, width, 3))
|
|
301
|
+
image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
|
302
|
+
out.write(image_bgr)
|
|
303
|
+
# Save TIFF frame (with compression)
|
|
304
|
+
tif_writer.write(image, photometric='rgb', compression='deflate')
|
|
305
|
+
|
|
306
|
+
out.release()
|
|
307
|
+
tif_writer.close()
|
|
308
|
+
|
|
309
|
+
# tifffile.imwrite(f"{save_basename}.tif", tiff_frames, photometric='rgb',compression='deflate')
|
|
310
|
+
print("Conversion done!")
|
|
311
|
+
print(f"Saved AVI: {save_basename}.avi")
|
|
312
|
+
print(f"Saved TIFF stack: {save_basename}.tif")
|
|
313
|
+
|
|
314
|
+
from tkinter import Tk, filedialog
|
|
315
|
+
def select_multiple_files(filetypes=(("csv files", "*.csv"), ("All files", "*.*"))):
|
|
316
|
+
Tk().withdraw() # Hide the root window
|
|
317
|
+
filepaths = filedialog.askopenfilenames(title="Select PSTrace .csv files", filetypes=filetypes)
|
|
318
|
+
return list(filepaths) # Convert from tuple to list
|