pyTEMlib 0.2025.4.1__py3-none-any.whl → 0.2025.9.1__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.
Potentially problematic release.
This version of pyTEMlib might be problematic. Click here for more details.
- build/lib/pyTEMlib/__init__.py +33 -0
- build/lib/pyTEMlib/animation.py +640 -0
- build/lib/pyTEMlib/atom_tools.py +238 -0
- build/lib/pyTEMlib/config_dir.py +31 -0
- build/lib/pyTEMlib/crystal_tools.py +1219 -0
- build/lib/pyTEMlib/diffraction_plot.py +756 -0
- build/lib/pyTEMlib/dynamic_scattering.py +293 -0
- build/lib/pyTEMlib/eds_tools.py +826 -0
- build/lib/pyTEMlib/eds_xsections.py +432 -0
- build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
- build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
- build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- build/lib/pyTEMlib/file_reader.py +274 -0
- build/lib/pyTEMlib/file_tools.py +811 -0
- build/lib/pyTEMlib/get_bote_salvat.py +69 -0
- build/lib/pyTEMlib/graph_tools.py +1153 -0
- build/lib/pyTEMlib/graph_viz.py +599 -0
- build/lib/pyTEMlib/image/__init__.py +37 -0
- build/lib/pyTEMlib/image/image_atoms.py +270 -0
- build/lib/pyTEMlib/image/image_clean.py +197 -0
- build/lib/pyTEMlib/image/image_distortion.py +299 -0
- build/lib/pyTEMlib/image/image_fft.py +277 -0
- build/lib/pyTEMlib/image/image_graph.py +926 -0
- build/lib/pyTEMlib/image/image_registration.py +316 -0
- build/lib/pyTEMlib/image/image_utilities.py +309 -0
- build/lib/pyTEMlib/image/image_window.py +421 -0
- build/lib/pyTEMlib/image_tools.py +699 -0
- build/lib/pyTEMlib/interactive_image.py +1 -0
- build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
- build/lib/pyTEMlib/microscope.py +61 -0
- build/lib/pyTEMlib/probe_tools.py +906 -0
- build/lib/pyTEMlib/sidpy_tools.py +153 -0
- build/lib/pyTEMlib/simulation_tools.py +104 -0
- build/lib/pyTEMlib/test.py +437 -0
- build/lib/pyTEMlib/utilities.py +314 -0
- build/lib/pyTEMlib/version.py +5 -0
- build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
- pyTEMlib/__init__.py +25 -3
- pyTEMlib/animation.py +31 -22
- pyTEMlib/atom_tools.py +29 -34
- pyTEMlib/config_dir.py +2 -28
- pyTEMlib/crystal_tools.py +129 -165
- pyTEMlib/eds_tools.py +559 -342
- pyTEMlib/eds_xsections.py +432 -0
- pyTEMlib/eels_tools/__init__.py +44 -0
- pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- pyTEMlib/eels_tools/eels_database.py +134 -0
- pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- pyTEMlib/file_reader.py +274 -0
- pyTEMlib/file_tools.py +260 -1130
- pyTEMlib/get_bote_salvat.py +69 -0
- pyTEMlib/graph_tools.py +101 -174
- pyTEMlib/graph_viz.py +150 -0
- pyTEMlib/image/__init__.py +37 -0
- pyTEMlib/image/image_atoms.py +270 -0
- pyTEMlib/image/image_clean.py +197 -0
- pyTEMlib/image/image_distortion.py +299 -0
- pyTEMlib/image/image_fft.py +277 -0
- pyTEMlib/image/image_graph.py +926 -0
- pyTEMlib/image/image_registration.py +316 -0
- pyTEMlib/image/image_utilities.py +309 -0
- pyTEMlib/image/image_window.py +421 -0
- pyTEMlib/image_tools.py +154 -915
- pyTEMlib/kinematic_scattering.py +1 -1
- pyTEMlib/probe_tools.py +1 -1
- pyTEMlib/test.py +437 -0
- pyTEMlib/utilities.py +314 -0
- pyTEMlib/version.py +2 -3
- pyTEMlib/xrpa_x_sections.py +14 -10
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
- pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
- pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
- pyTEMlib/core_loss_widget.py +0 -721
- pyTEMlib/eels_dialog.py +0 -754
- pyTEMlib/eels_dialog_utilities.py +0 -1199
- pyTEMlib/eels_tools.py +0 -2359
- pyTEMlib/file_tools_qt.py +0 -193
- pyTEMlib/image_dialog.py +0 -158
- pyTEMlib/image_dlg.py +0 -146
- pyTEMlib/info_widget.py +0 -1086
- pyTEMlib/info_widget3.py +0 -1120
- pyTEMlib/low_loss_widget.py +0 -479
- pyTEMlib/peak_dialog.py +0 -1129
- pyTEMlib/peak_dlg.py +0 -286
- pytemlib-0.2025.4.1.dist-info/RECORD +0 -38
- pytemlib-0.2025.4.1.dist-info/top_level.txt +0 -1
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
""" Convert Bote and Salvat cross sections to json format
|
|
2
|
+
Data from: Bote and Salvat (1998) Atomic Data and Nuclear Data Tables 71, 1-15
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, './')
|
|
13
|
+
import pyTEMlib
|
|
14
|
+
print(pyTEMlib.__version__)
|
|
15
|
+
|
|
16
|
+
def write_bote_salvat_json():
|
|
17
|
+
""" Convert Bote and Salvat cross sections to json format"""
|
|
18
|
+
line = 'not empty'
|
|
19
|
+
x_sec = {}
|
|
20
|
+
with open('.//data//Bote_Salvat.txt', 'r', encoding='utf-8') as f:
|
|
21
|
+
while line:
|
|
22
|
+
line = f.readline()
|
|
23
|
+
if 'BoteSalvatElementDatum' in line:
|
|
24
|
+
ele = line.split('[')
|
|
25
|
+
z = int(ele[0].split('(')[1][:-2])
|
|
26
|
+
be = []
|
|
27
|
+
for value in ele[1].split(']')[0].split(','):
|
|
28
|
+
be.append(float(value))
|
|
29
|
+
x_sec[z]= {'Be': be}
|
|
30
|
+
current_x = x_sec[z]
|
|
31
|
+
else:
|
|
32
|
+
if 'Anlj' not in current_x.keys():
|
|
33
|
+
alij = []
|
|
34
|
+
for value in line.split('[')[1].split(']')[0].split(','):
|
|
35
|
+
alij.append(float(value))
|
|
36
|
+
current_x['Anlj'] = alij
|
|
37
|
+
elif 'G' not in current_x.keys():
|
|
38
|
+
g = []
|
|
39
|
+
gg = []
|
|
40
|
+
g_lines =line.split('[')[1].split(']')[0].split(';')
|
|
41
|
+
for g_line in g_lines:
|
|
42
|
+
g = []
|
|
43
|
+
for value in g_line.split(' '):
|
|
44
|
+
if value != '':
|
|
45
|
+
g.append(float(value))
|
|
46
|
+
gg.append(g)
|
|
47
|
+
current_x['G'] = gg
|
|
48
|
+
elif 'edge' not in current_x.keys():
|
|
49
|
+
edge = []
|
|
50
|
+
for value in line.split('[')[1].split(']')[0].split(','):
|
|
51
|
+
edge.append(float(value))
|
|
52
|
+
current_x['edge'] = edge
|
|
53
|
+
elif 'A' not in current_x.keys():
|
|
54
|
+
a = []
|
|
55
|
+
aa = []
|
|
56
|
+
a_lines =line.split('[')[1].split(']')[0].split(';')
|
|
57
|
+
for a_line in a_lines:
|
|
58
|
+
a = []
|
|
59
|
+
for value in a_line.split(' '):
|
|
60
|
+
if value != '':
|
|
61
|
+
a.append(float(value))
|
|
62
|
+
aa.append(a)
|
|
63
|
+
current_x['A'] = aa
|
|
64
|
+
for key, value in x_sec[3].items():
|
|
65
|
+
print(key, np.array(value))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
file_name_out = os.path.join(pyTEMlib.config_path, 'Bote_Salvat.json')
|
|
69
|
+
json.dump(x_sec, open(file_name_out, 'w', encoding='utf-8'), indent=4)
|
pyTEMlib/graph_tools.py
CHANGED
|
@@ -2,17 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
4
|
import numpy as np
|
|
5
|
-
|
|
6
|
-
import
|
|
5
|
+
import scipy
|
|
6
|
+
import skimage
|
|
7
7
|
|
|
8
|
-
import scipy.spatial
|
|
9
|
-
import scipy.optimize
|
|
10
|
-
import scipy.interpolate
|
|
11
|
-
|
|
12
|
-
from skimage.measure import grid_points_in_poly, points_in_poly
|
|
13
|
-
|
|
14
|
-
# import plotly.graph_objects as go
|
|
15
|
-
# import plotly.express as px
|
|
16
8
|
import matplotlib.patches as patches
|
|
17
9
|
|
|
18
10
|
import pyTEMlib.crystal_tools
|
|
@@ -546,137 +538,6 @@ def get_bonds(crystal, shift= 0., verbose = False, cheat=1.0):
|
|
|
546
538
|
atoms.info.update({'supercell': crystal})
|
|
547
539
|
return atoms
|
|
548
540
|
|
|
549
|
-
def plot_atoms(atoms: ase.Atoms, polyhedra_indices=None, plot_bonds=False, color='', template=None, atom_size=None, max_size=35) -> go.Figure:
|
|
550
|
-
"""
|
|
551
|
-
Plot structure in a ase.Atoms object with plotly
|
|
552
|
-
|
|
553
|
-
If the info dictionary of the atoms object contains bond or polyedra information, these can be set tobe plotted
|
|
554
|
-
|
|
555
|
-
Partameter:
|
|
556
|
-
-----------
|
|
557
|
-
atoms: ase.Atoms object
|
|
558
|
-
structure of supercell
|
|
559
|
-
polyhedra_indices: list of integers
|
|
560
|
-
indices of polyhedra to be plotted
|
|
561
|
-
plot_bonds: boolean
|
|
562
|
-
whether to plot bonds or not
|
|
563
|
-
|
|
564
|
-
Returns:
|
|
565
|
-
--------
|
|
566
|
-
fig: plotly figure object
|
|
567
|
-
handle to figure needed to modify appearance
|
|
568
|
-
"""
|
|
569
|
-
energies = np.zeros(len(atoms))
|
|
570
|
-
if 'bonds' in atoms.info:
|
|
571
|
-
if 'atom_energy' in atoms.info['bonds']:
|
|
572
|
-
energies = np.round(np.array(atoms.info['bonds']['atom_energy'] - 12 * atoms.info['bonds']['ideal_bond_energy']) *1000,0)
|
|
573
|
-
|
|
574
|
-
for atom in atoms:
|
|
575
|
-
if atom.index not in atoms.info['bonds']['super_cell_atoms']:
|
|
576
|
-
energies[atom.index] = 0.
|
|
577
|
-
if color == 'coordination':
|
|
578
|
-
colors = atoms.info['bonds']['coordination']
|
|
579
|
-
elif color == 'layer':
|
|
580
|
-
colors = atoms.positions[:, 2]
|
|
581
|
-
elif color == 'energy':
|
|
582
|
-
colors = energies
|
|
583
|
-
colors[colors>50] = 50
|
|
584
|
-
colors = np.log(1+ energies)
|
|
585
|
-
|
|
586
|
-
else:
|
|
587
|
-
colors = atoms.get_atomic_numbers()
|
|
588
|
-
|
|
589
|
-
if atom_size is None:
|
|
590
|
-
atom_size = atoms.get_atomic_numbers()*4
|
|
591
|
-
elif isinstance(atom_size, float):
|
|
592
|
-
atom_size = atoms.get_atomic_numbers()*4*atom_size
|
|
593
|
-
atom_size[atom_size>max_size] = max_size
|
|
594
|
-
elif isinstance(atom_size, int):
|
|
595
|
-
atom_size = [atom_size]*len(atoms)
|
|
596
|
-
if len(atom_size) != len(atoms):
|
|
597
|
-
atom_size = [10]*len(atoms)
|
|
598
|
-
print('wrong length of atom_size parameter')
|
|
599
|
-
plot_polyhedra = False
|
|
600
|
-
data = []
|
|
601
|
-
if polyhedra_indices is not None:
|
|
602
|
-
if 'polyhedra' in atoms.info:
|
|
603
|
-
if polyhedra_indices == -1:
|
|
604
|
-
data = plot_polyhedron(atoms.info['polyhedra']['polyhedra'], range(len(atoms.info['polyhedra']['polyhedra'])))
|
|
605
|
-
plot_polyhedra = True
|
|
606
|
-
elif isinstance(polyhedra_indices, list):
|
|
607
|
-
data = plot_polyhedron(atoms.info['polyhedra']['polyhedra'], polyhedra_indices)
|
|
608
|
-
plot_polyhedra = True
|
|
609
|
-
text = []
|
|
610
|
-
if 'bonds' in atoms.info:
|
|
611
|
-
coord = atoms.info['bonds']['coordination']
|
|
612
|
-
for atom in atoms:
|
|
613
|
-
if atom.index in atoms.info['bonds']['super_cell_atoms']:
|
|
614
|
-
|
|
615
|
-
text.append(f'Atom {atom.index}: coordination={coord[atom.index]}' +
|
|
616
|
-
f'x:{atom.position[0]:.2f} \n y:{atom.position[1]:.2f} \n z:{atom.position[2]:.2f}')
|
|
617
|
-
if 'atom_energy' in atoms.info['bonds']:
|
|
618
|
-
text[-1] += f"\n energy: {energies[atom.index]:.0f} meV"
|
|
619
|
-
else:
|
|
620
|
-
text.append('')
|
|
621
|
-
else:
|
|
622
|
-
text = [''] * len(atoms)
|
|
623
|
-
|
|
624
|
-
if plot_bonds:
|
|
625
|
-
data += get_plot_bonds(atoms)
|
|
626
|
-
if plot_polyhedra or plot_bonds:
|
|
627
|
-
fig = go.Figure(data=data)
|
|
628
|
-
else:
|
|
629
|
-
fig = go.Figure()
|
|
630
|
-
if color=='energy':
|
|
631
|
-
fig.add_trace(go.Scatter3d(
|
|
632
|
-
mode='markers',
|
|
633
|
-
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
634
|
-
hovertemplate='<b>%{text}</b><extra></extra>',
|
|
635
|
-
text = text,
|
|
636
|
-
marker=dict(
|
|
637
|
-
color=colors,
|
|
638
|
-
size=atom_size,
|
|
639
|
-
sizemode='diameter',
|
|
640
|
-
colorscale='Rainbow', #px.colors.qualitative.Light24,
|
|
641
|
-
colorbar=dict(thickness=10, orientation='h'))))
|
|
642
|
-
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
643
|
-
|
|
644
|
-
elif 'bonds' in atoms.info:
|
|
645
|
-
fig.add_trace(go.Scatter3d(
|
|
646
|
-
mode='markers',
|
|
647
|
-
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
648
|
-
hovertemplate='<b>%{text}</b><extra></extra>',
|
|
649
|
-
text = text,
|
|
650
|
-
marker=dict(
|
|
651
|
-
color=colors,
|
|
652
|
-
size=atom_size,
|
|
653
|
-
sizemode='diameter',
|
|
654
|
-
colorscale= px.colors.qualitative.Light24)))
|
|
655
|
-
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
656
|
-
|
|
657
|
-
else:
|
|
658
|
-
fig.add_trace(go.Scatter3d(
|
|
659
|
-
mode='markers',
|
|
660
|
-
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
661
|
-
marker=dict(
|
|
662
|
-
color=colors,
|
|
663
|
-
size=atom_size,
|
|
664
|
-
sizemode='diameter',
|
|
665
|
-
colorbar=dict(thickness=10),
|
|
666
|
-
colorscale= px.colors.qualitative.Light24)))
|
|
667
|
-
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
668
|
-
fig.update_layout(width=1000, height=700, showlegend=False, template=template)
|
|
669
|
-
fig.update_layout(scene_aspectmode='data',
|
|
670
|
-
scene_aspectratio=dict(x=1, y=1, z=1))
|
|
671
|
-
|
|
672
|
-
camera = {'up': {'x': 0, 'y': 1, 'z': 0},
|
|
673
|
-
'center': {'x': 0, 'y': 0, 'z': 0},
|
|
674
|
-
'eye': {'x': 0, 'y': 0, 'z': 1}}
|
|
675
|
-
fig.update_coloraxes(showscale=True)
|
|
676
|
-
fig.update_layout(scene_camera=camera, title=r"Al-GB $")
|
|
677
|
-
fig.update_scenes(camera_projection_type="orthographic" )
|
|
678
|
-
fig.show()
|
|
679
|
-
return fig
|
|
680
541
|
|
|
681
542
|
|
|
682
543
|
|
|
@@ -780,7 +641,7 @@ def sort_polyhedra_by_vertices(polyhedra, visible=range(4, 100), z_lim=[0, 100],
|
|
|
780
641
|
##########################
|
|
781
642
|
# New Graph Stuff
|
|
782
643
|
##########################
|
|
783
|
-
def
|
|
644
|
+
def breadth_first_search(graph, initial_node_index, projected_crystal):
|
|
784
645
|
""" breadth first search of atoms viewed as a graph
|
|
785
646
|
|
|
786
647
|
the projection dictionary has to contain the following items
|
|
@@ -808,7 +669,7 @@ def breadth_first_search2(graph, initial, projected_crystal):
|
|
|
808
669
|
a_lattice_vector = projection_tags['lattice_vector']['a']
|
|
809
670
|
b_lattice_vector = projection_tags['lattice_vector']['b']
|
|
810
671
|
main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
|
|
811
|
-
near =
|
|
672
|
+
near = main
|
|
812
673
|
else:
|
|
813
674
|
# get lattice vectors to hopp along through graph
|
|
814
675
|
projected_unit_cell = projected_crystal.cell[:2, :2]
|
|
@@ -816,19 +677,17 @@ def breadth_first_search2(graph, initial, projected_crystal):
|
|
|
816
677
|
b_lattice_vector = projected_unit_cell[1]
|
|
817
678
|
main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
|
|
818
679
|
near = projection_tags['near_base'] # all nearest atoms
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
near = np.append(main, near, axis=0)
|
|
822
|
-
|
|
680
|
+
near = np.append(main, near, axis=0)
|
|
681
|
+
|
|
823
682
|
neighbour_tree = scipy.spatial.KDTree(graph)
|
|
824
|
-
distances, indices = neighbour_tree.query(graph, # let's get all neighbours
|
|
825
|
-
|
|
826
|
-
|
|
683
|
+
distances, indices = neighbour_tree.query(graph, k=50) # let's get all neighbours
|
|
684
|
+
|
|
685
|
+
|
|
827
686
|
visited = [] # the atoms we visited
|
|
828
687
|
ideal = [] # atoms at ideal lattice
|
|
829
688
|
sub_lattice = [] # atoms in base and disregarded
|
|
830
|
-
queue = [
|
|
831
|
-
ideal_queue = [graph[
|
|
689
|
+
queue = [initial_node_index]
|
|
690
|
+
ideal_queue = [graph[initial_node_index]]
|
|
832
691
|
|
|
833
692
|
while queue:
|
|
834
693
|
node = queue.pop(0)
|
|
@@ -842,7 +701,6 @@ def breadth_first_search2(graph, initial, projected_crystal):
|
|
|
842
701
|
for i, neighbour in enumerate(neighbors):
|
|
843
702
|
if neighbour not in visited:
|
|
844
703
|
distance_to_ideal = np.linalg.norm(near + graph[node] - graph[neighbour], axis=1)
|
|
845
|
-
|
|
846
704
|
if np.min(distance_to_ideal) < projection_tags['allowed_variation']:
|
|
847
705
|
direction = np.argmin(distance_to_ideal)
|
|
848
706
|
if direction > 3: # counting starts at 0
|
|
@@ -854,11 +712,93 @@ def breadth_first_search2(graph, initial, projected_crystal):
|
|
|
854
712
|
return graph[visited], ideal
|
|
855
713
|
|
|
856
714
|
|
|
715
|
+
def get_base_atoms(graph, origins, base, tolerance=3):
|
|
716
|
+
""" get sublattices of atoms in a graph
|
|
717
|
+
This function returns the indices of atoms in a graph that are close to the base atoms.
|
|
718
|
+
Parameters
|
|
719
|
+
----------
|
|
720
|
+
graph: numpy array (Nx2)
|
|
721
|
+
the atom positions
|
|
722
|
+
origins: numpy array (Nx2)
|
|
723
|
+
the origin positions
|
|
724
|
+
base: numpy array (Mx2)
|
|
725
|
+
the base atom positions
|
|
726
|
+
tolerance: float
|
|
727
|
+
the distance tolerance for finding base atoms
|
|
728
|
+
Returns
|
|
729
|
+
-------
|
|
730
|
+
sublattices: list of numpy arrays
|
|
731
|
+
list of indices of atoms in the graph that are close to each base atom
|
|
732
|
+
"""
|
|
733
|
+
sublattices = []
|
|
734
|
+
neighbour_tree = scipy.spatial.KDTree(graph)
|
|
735
|
+
for base_atom in base:
|
|
736
|
+
distances, indices = neighbour_tree.query(origins+base_atom[:2], k=50)
|
|
737
|
+
sublattices.append(indices[distances < tolerance])
|
|
738
|
+
return sublattices
|
|
739
|
+
|
|
740
|
+
def analyze_atomic_structure(dataset, crystal, start_atom_index, tolerance=1.5):
|
|
741
|
+
""" Analyze atomic structure of a crystal and return sublattices of atoms
|
|
742
|
+
|
|
743
|
+
Parameters
|
|
744
|
+
----------
|
|
745
|
+
dataset: pyTEMlib.Dataset
|
|
746
|
+
dataset containing the atomic structure information
|
|
747
|
+
crystal: ase.Atoms
|
|
748
|
+
crystal structure to analyze
|
|
749
|
+
start_atom_index: int
|
|
750
|
+
index of the starting atom for the breadth-first search
|
|
751
|
+
tolerance: float
|
|
752
|
+
tolerance for determining the allowed variation in atom positions
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
sublattices: list of numpy arrays
|
|
756
|
+
list of indices of atoms in the graph that are close to each base atom
|
|
757
|
+
"""
|
|
758
|
+
if 'atoms' not in dataset.metadata:
|
|
759
|
+
TypeError('dataset.metadata needs to contain atoms information')
|
|
760
|
+
|
|
761
|
+
graph = dataset.metadata['atoms']['positions']
|
|
762
|
+
|
|
763
|
+
layer = pyTEMlib.crystal_tools.get_projection(crystal)
|
|
764
|
+
gamma = np.radians(layer.cell.angles()[2])
|
|
765
|
+
rotation_angle = np.radians(crystal.info['experimental']['angle']
|
|
766
|
+
)
|
|
767
|
+
length = (layer.cell.lengths() /10/dataset.x.slope)[:2]
|
|
768
|
+
print(length, rotation_angle, gamma)
|
|
769
|
+
a = np.array([np.cos(rotation_angle)*length[0], np.sin(rotation_angle)*length[0]])
|
|
770
|
+
b = np.array([np.cos(rotation_angle+gamma)*length[1], np.sin(rotation_angle+gamma)*length[1]])
|
|
771
|
+
base = layer.get_scaled_positions()
|
|
772
|
+
base[:, :2] = np.dot(base[:, :2],[a,b])
|
|
773
|
+
projection_tags = {'lattice_vector': {'a': a, 'b': b},
|
|
774
|
+
'allowed_variation': tolerance,
|
|
775
|
+
'distance_unit_cell': np.max(length)*1.04,
|
|
776
|
+
'start_atom_index': start_atom_index,
|
|
777
|
+
'base': base}
|
|
778
|
+
layer.info['projection'] = projection_tags
|
|
779
|
+
|
|
780
|
+
origins, ideal = pyTEMlib.graph_tools.breadth_first_search(graph[:,:2], start_atom_index, layer)
|
|
781
|
+
print(len(origins), 'origins found')
|
|
782
|
+
dataset.metadata['atoms']['projection'] = layer
|
|
783
|
+
sublattices = pyTEMlib.graph_tools.get_base_atoms(graph[:, :2], origins, base[:, :2], tolerance=3)
|
|
784
|
+
|
|
785
|
+
dataset.metadata['atoms']['origins'] = origins
|
|
786
|
+
dataset.metadata['atoms']['ideal_origins'] = ideal
|
|
787
|
+
dataset.metadata['atoms']['sublattices'] = sublattices
|
|
788
|
+
dataset.metadata['atoms']['projection_tags'] = projection_tags
|
|
789
|
+
|
|
790
|
+
return sublattices
|
|
791
|
+
|
|
857
792
|
|
|
858
|
-
def breadth_first_search_felxible(graph,
|
|
793
|
+
def breadth_first_search_felxible(graph, initial_node_index, lattice_parameter, tolerance=1):
|
|
859
794
|
""" breadth first search of atoms viewed as a graph
|
|
860
795
|
This is a rotational invariant search of atoms in a lattice, and returns the angles of unit cells.
|
|
861
796
|
We only use the ideal lattice parameter to determine the lattice.
|
|
797
|
+
|
|
798
|
+
Parameters
|
|
799
|
+
----------
|
|
800
|
+
graph: numpy array (Nx2)
|
|
801
|
+
|
|
862
802
|
"""
|
|
863
803
|
if isinstance(lattice_parameter, ase.Atoms):
|
|
864
804
|
lattice_parameter = lattice_parameter.cell.lengths()[:2]
|
|
@@ -867,12 +807,11 @@ def breadth_first_search_felxible(graph, initial, lattice_parameter, tolerance=1
|
|
|
867
807
|
lattice_parameter = np.array(lattice_parameter)
|
|
868
808
|
|
|
869
809
|
neighbour_tree = scipy.spatial.KDTree(graph)
|
|
870
|
-
distances, indices = neighbour_tree.query(graph, # let's get all neighbours
|
|
871
|
-
k=50) # projection_tags['number_of_nearest_neighbours']*2 + 1)
|
|
810
|
+
distances, indices = neighbour_tree.query(graph, k=50) # let's get all neighbours
|
|
872
811
|
visited = [] # the atoms we visited
|
|
873
812
|
angles = [] # atoms at ideal lattice
|
|
874
813
|
sub_lattice = [] # atoms in base and disregarded
|
|
875
|
-
queue = [
|
|
814
|
+
queue = [initial_node_index]
|
|
876
815
|
queue_angles=[0]
|
|
877
816
|
|
|
878
817
|
while queue:
|
|
@@ -921,12 +860,7 @@ def get_distortion_matrix(atoms, ideal_lattice):
|
|
|
921
860
|
ideal_vertices = get_significant_vertices(ideal_vertices - np.average(ideal_vertices, axis=0))
|
|
922
861
|
|
|
923
862
|
distortion_matrix = []
|
|
924
|
-
for index in
|
|
925
|
-
done = int((index + 1) / vor.points.shape[0] * 50)
|
|
926
|
-
sys.stdout.write('\r')
|
|
927
|
-
# progress output :
|
|
928
|
-
sys.stdout.write("[%-50s] %d%%" % ('=' * done, 2 * done))
|
|
929
|
-
sys.stdout.flush()
|
|
863
|
+
for index in trange(vor.points.shape[0]):
|
|
930
864
|
|
|
931
865
|
# determine vertices of Voronoi polygons of an atom with number index
|
|
932
866
|
poly_point = vor.points[index]
|
|
@@ -1015,7 +949,7 @@ def transform_voronoi(vertices, ideal_voronoi):
|
|
|
1015
949
|
polygon_grid = np.swapaxes(polygon_grid, 0, 2)
|
|
1016
950
|
polygon_array = polygon_grid.reshape(-1, polygon_grid.shape[-1])
|
|
1017
951
|
|
|
1018
|
-
p = points_in_poly(polygon_array, vertices2)
|
|
952
|
+
p = skimage.measure.points_in_poly(polygon_array, vertices2)
|
|
1019
953
|
uncorrected = polygon_array[p]
|
|
1020
954
|
|
|
1021
955
|
corrected = np.dot(uncorrected, aa)
|
|
@@ -1102,7 +1036,7 @@ def transform_voronoi(vertices, ideal_voronoi):
|
|
|
1102
1036
|
polygon_grid = np.swapaxes(polygon_grid, 0, 2)
|
|
1103
1037
|
polygon_array = polygon_grid.reshape(-1, polygon_grid.shape[-1])
|
|
1104
1038
|
|
|
1105
|
-
p = points_in_poly(polygon_array, vertices2)
|
|
1039
|
+
p = skimage.measure.points_in_poly(polygon_array, vertices2)
|
|
1106
1040
|
uncorrected = polygon_array[p]
|
|
1107
1041
|
|
|
1108
1042
|
corrected = np.dot(uncorrected, aa)
|
|
@@ -1211,16 +1145,9 @@ def undistort_stack(distortion_matrix, data):
|
|
|
1211
1145
|
nimages = data.shape[0]
|
|
1212
1146
|
done = 0
|
|
1213
1147
|
|
|
1214
|
-
|
|
1215
|
-
for i in range(nimages):
|
|
1216
|
-
done = int((i + 1) / nimages * 50)
|
|
1217
|
-
sys.stdout.write('\r')
|
|
1218
|
-
# progress output :
|
|
1219
|
-
sys.stdout.write("[%-50s] %d%%" % ('=' * done, 2 * done))
|
|
1220
|
-
sys.stdout.flush()
|
|
1221
|
-
|
|
1148
|
+
for i in trange(nimages):
|
|
1222
1149
|
interpolated[i, :, :] = griddata(corrected, intensity_values[i, :], (grid_x, grid_y), method='linear')
|
|
1223
|
-
|
|
1150
|
+
|
|
1224
1151
|
print(':-)')
|
|
1225
1152
|
print('You have successfully completed undistortion of image stack')
|
|
1226
1153
|
return interpolated
|
pyTEMlib/graph_viz.py
CHANGED
|
@@ -19,6 +19,156 @@ import pyTEMlib.crystal_tools
|
|
|
19
19
|
import pyTEMlib.graph_tools
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def get_plot_bonds(atoms):
|
|
23
|
+
"""Get plotly data for bonds."""
|
|
24
|
+
connectivity_matrix = atoms.info['bonds']['connectivity_matrix']
|
|
25
|
+
data = []
|
|
26
|
+
for row in range(1, connectivity_matrix.shape[0]):
|
|
27
|
+
for column in range(row):
|
|
28
|
+
if connectivity_matrix[column, row]:
|
|
29
|
+
lines = dict(type='scatter3d',
|
|
30
|
+
x=atoms.positions[[column, row],0],
|
|
31
|
+
y=atoms.positions[[column, row],1],
|
|
32
|
+
z=atoms.positions[[column, row],2],
|
|
33
|
+
mode='lines',
|
|
34
|
+
name='',
|
|
35
|
+
line=dict(color='rgb(70,70,70)', width=1.5))
|
|
36
|
+
data.append(lines)
|
|
37
|
+
return data
|
|
38
|
+
|
|
39
|
+
def plot_atoms(atoms: ase.Atoms, polyhedra_indices=None, plot_bonds=False, color='', template=None, atom_size=None, max_size=35) -> go.Figure:
|
|
40
|
+
"""
|
|
41
|
+
Plot structure in a ase.Atoms object with plotly
|
|
42
|
+
|
|
43
|
+
If the info dictionary of the atoms object contains bond or polyedra information, these can be set tobe plotted
|
|
44
|
+
|
|
45
|
+
Partameter:
|
|
46
|
+
-----------
|
|
47
|
+
atoms: ase.Atoms object
|
|
48
|
+
structure of supercell
|
|
49
|
+
polyhedra_indices: list of integers
|
|
50
|
+
indices of polyhedra to be plotted
|
|
51
|
+
plot_bonds: boolean
|
|
52
|
+
whether to plot bonds or not
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
--------
|
|
56
|
+
fig: plotly figure object
|
|
57
|
+
handle to figure needed to modify appearance
|
|
58
|
+
"""
|
|
59
|
+
energies = np.zeros(len(atoms))
|
|
60
|
+
if 'bonds' in atoms.info:
|
|
61
|
+
if 'atom_energy' in atoms.info['bonds']:
|
|
62
|
+
energies = np.round(np.array(atoms.info['bonds']['atom_energy'] - 12 * atoms.info['bonds']['ideal_bond_energy']) *1000,0)
|
|
63
|
+
|
|
64
|
+
for atom in atoms:
|
|
65
|
+
if atom.index not in atoms.info['bonds']['super_cell_atoms']:
|
|
66
|
+
energies[atom.index] = 0.
|
|
67
|
+
if color == 'coordination':
|
|
68
|
+
colors = atoms.info['bonds']['coordination']
|
|
69
|
+
elif color == 'layer':
|
|
70
|
+
colors = atoms.positions[:, 2]
|
|
71
|
+
elif color == 'energy':
|
|
72
|
+
colors = energies
|
|
73
|
+
colors[colors>50] = 50
|
|
74
|
+
colors = np.log(1+ energies)
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
colors = atoms.get_atomic_numbers()
|
|
78
|
+
|
|
79
|
+
if atom_size is None:
|
|
80
|
+
atom_size = atoms.get_atomic_numbers()*4
|
|
81
|
+
elif isinstance(atom_size, float):
|
|
82
|
+
atom_size = atoms.get_atomic_numbers()*4*atom_size
|
|
83
|
+
atom_size[atom_size>max_size] = max_size
|
|
84
|
+
elif isinstance(atom_size, int):
|
|
85
|
+
atom_size = [atom_size]*len(atoms)
|
|
86
|
+
if len(atom_size) != len(atoms):
|
|
87
|
+
atom_size = [10]*len(atoms)
|
|
88
|
+
print('wrong length of atom_size parameter')
|
|
89
|
+
plot_polyhedra = False
|
|
90
|
+
data = []
|
|
91
|
+
if polyhedra_indices is not None:
|
|
92
|
+
if 'polyhedra' in atoms.info:
|
|
93
|
+
if polyhedra_indices == -1:
|
|
94
|
+
data = plot_polyhedron(atoms.info['polyhedra']['polyhedra'], range(len(atoms.info['polyhedra']['polyhedra'])))
|
|
95
|
+
plot_polyhedra = True
|
|
96
|
+
elif isinstance(polyhedra_indices, list):
|
|
97
|
+
data = plot_polyhedron(atoms.info['polyhedra']['polyhedra'], polyhedra_indices)
|
|
98
|
+
plot_polyhedra = True
|
|
99
|
+
text = []
|
|
100
|
+
if 'bonds' in atoms.info:
|
|
101
|
+
coord = atoms.info['bonds']['coordination']
|
|
102
|
+
for atom in atoms:
|
|
103
|
+
if atom.index in atoms.info['bonds']['super_cell_atoms']:
|
|
104
|
+
|
|
105
|
+
text.append(f'Atom {atom.index}: coordination={coord[atom.index]}' +
|
|
106
|
+
f'x:{atom.position[0]:.2f} \n y:{atom.position[1]:.2f} \n z:{atom.position[2]:.2f}')
|
|
107
|
+
if 'atom_energy' in atoms.info['bonds']:
|
|
108
|
+
text[-1] += f"\n energy: {energies[atom.index]:.0f} meV"
|
|
109
|
+
else:
|
|
110
|
+
text.append('')
|
|
111
|
+
else:
|
|
112
|
+
text = [''] * len(atoms)
|
|
113
|
+
|
|
114
|
+
if plot_bonds:
|
|
115
|
+
data += get_plot_bonds(atoms)
|
|
116
|
+
if plot_polyhedra or plot_bonds:
|
|
117
|
+
fig = go.Figure(data=data)
|
|
118
|
+
else:
|
|
119
|
+
fig = go.Figure()
|
|
120
|
+
if color=='energy':
|
|
121
|
+
fig.add_trace(go.Scatter3d(
|
|
122
|
+
mode='markers',
|
|
123
|
+
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
124
|
+
hovertemplate='<b>%{text}</b><extra></extra>',
|
|
125
|
+
text = text,
|
|
126
|
+
marker=dict(
|
|
127
|
+
color=colors,
|
|
128
|
+
size=atom_size,
|
|
129
|
+
sizemode='diameter',
|
|
130
|
+
colorscale='Rainbow', #px.colors.qualitative.Light24,
|
|
131
|
+
colorbar=dict(thickness=10, orientation='h'))))
|
|
132
|
+
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
133
|
+
|
|
134
|
+
elif 'bonds' in atoms.info:
|
|
135
|
+
fig.add_trace(go.Scatter3d(
|
|
136
|
+
mode='markers',
|
|
137
|
+
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
138
|
+
hovertemplate='<b>%{text}</b><extra></extra>',
|
|
139
|
+
text = text,
|
|
140
|
+
marker=dict(
|
|
141
|
+
color=colors,
|
|
142
|
+
size=atom_size,
|
|
143
|
+
sizemode='diameter',
|
|
144
|
+
colorscale= px.colors.qualitative.Light24)))
|
|
145
|
+
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
146
|
+
|
|
147
|
+
else:
|
|
148
|
+
fig.add_trace(go.Scatter3d(
|
|
149
|
+
mode='markers',
|
|
150
|
+
x=atoms.positions[:,0], y=atoms.positions[:,1], z=atoms.positions[:,2],
|
|
151
|
+
marker=dict(
|
|
152
|
+
color=colors,
|
|
153
|
+
size=atom_size,
|
|
154
|
+
sizemode='diameter',
|
|
155
|
+
colorbar=dict(thickness=10),
|
|
156
|
+
colorscale= px.colors.qualitative.Light24)))
|
|
157
|
+
#hover_name = colors))) # ["blue", "green", "red"])))
|
|
158
|
+
fig.update_layout(width=1000, height=700, showlegend=False, template=template)
|
|
159
|
+
fig.update_layout(scene_aspectmode='data',
|
|
160
|
+
scene_aspectratio=dict(x=1, y=1, z=1))
|
|
161
|
+
|
|
162
|
+
camera = {'up': {'x': 0, 'y': 1, 'z': 0},
|
|
163
|
+
'center': {'x': 0, 'y': 0, 'z': 0},
|
|
164
|
+
'eye': {'x': 0, 'y': 0, 'z': 1}}
|
|
165
|
+
fig.update_coloraxes(showscale=True)
|
|
166
|
+
fig.update_layout(scene_camera=camera, title=r"Al-GB $")
|
|
167
|
+
fig.update_scenes(camera_projection_type="orthographic" )
|
|
168
|
+
fig.show()
|
|
169
|
+
return fig
|
|
170
|
+
|
|
171
|
+
|
|
22
172
|
def plot_super_cell(super_cell, shift_x=0.):
|
|
23
173
|
""" make a super_cell to plot with extra atoms at periodic boundaries"""
|
|
24
174
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image Module
|
|
3
|
+
|
|
4
|
+
Should contain
|
|
5
|
+
- general feature extraction
|
|
6
|
+
- geometry feature extraction
|
|
7
|
+
- atom finding
|
|
8
|
+
- denoising
|
|
9
|
+
- windowing
|
|
10
|
+
- transforms (e.g., radon, hough)
|
|
11
|
+
|
|
12
|
+
Submodules
|
|
13
|
+
----------
|
|
14
|
+
.. autosummary::
|
|
15
|
+
:toctree: _autosummary
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .image_window import ImageWindowing
|
|
20
|
+
from .image_utilities import crop_image, flatten_image, inpaint_image, warp, rebin
|
|
21
|
+
from .image_clean import decon_lr, clean_svd, background_correction
|
|
22
|
+
from .image_atoms import find_atoms, atom_refine, intensity_area, atoms_clustering
|
|
23
|
+
from .image_graph import find_polyhedra, breadth_first_search, breadth_first_search_flexible
|
|
24
|
+
from .image_graph import get_base_atoms
|
|
25
|
+
from .image_distortion import get_distortion_matrix, undistort, undistort_sitk
|
|
26
|
+
from .image_registration import complete_registration, rigid_registration, demon_registration
|
|
27
|
+
from .image_fft import power_spectrum, diffractogram_spots, adaptive_fourier_filter
|
|
28
|
+
from .image_fft import rotational_symmetry_diffractogram
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ['ImageWindowing', 'crop_image', 'decon_lr', 'clean_svd', 'background_correction',
|
|
32
|
+
'find_atoms', 'atom_refine', 'intensity_area', 'atoms_clustering',
|
|
33
|
+
'find_polyhedra', 'breadth_first_search', 'breadth_first_search_flexible', 'get_base_atoms',
|
|
34
|
+
'get_distortion_matrix', 'undistort', 'undistort_sitk',
|
|
35
|
+
'complete_registration', 'demon_registration', 'rigid_registration',
|
|
36
|
+
'flatten_image', 'inpaint_image', 'warp', 'rebin',
|
|
37
|
+
'power_spectrum', 'diffractogram_spots', 'adaptive_fourier_filter', 'rotational_symmetry_diffractogram']
|