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.

Files changed (94) hide show
  1. build/lib/pyTEMlib/__init__.py +33 -0
  2. build/lib/pyTEMlib/animation.py +640 -0
  3. build/lib/pyTEMlib/atom_tools.py +238 -0
  4. build/lib/pyTEMlib/config_dir.py +31 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +756 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +293 -0
  8. build/lib/pyTEMlib/eds_tools.py +826 -0
  9. build/lib/pyTEMlib/eds_xsections.py +432 -0
  10. build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
  11. build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  12. build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
  13. build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  14. build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  15. build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  16. build/lib/pyTEMlib/file_reader.py +274 -0
  17. build/lib/pyTEMlib/file_tools.py +811 -0
  18. build/lib/pyTEMlib/get_bote_salvat.py +69 -0
  19. build/lib/pyTEMlib/graph_tools.py +1153 -0
  20. build/lib/pyTEMlib/graph_viz.py +599 -0
  21. build/lib/pyTEMlib/image/__init__.py +37 -0
  22. build/lib/pyTEMlib/image/image_atoms.py +270 -0
  23. build/lib/pyTEMlib/image/image_clean.py +197 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +277 -0
  26. build/lib/pyTEMlib/image/image_graph.py +926 -0
  27. build/lib/pyTEMlib/image/image_registration.py +316 -0
  28. build/lib/pyTEMlib/image/image_utilities.py +309 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +699 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
  33. build/lib/pyTEMlib/microscope.py +61 -0
  34. build/lib/pyTEMlib/probe_tools.py +906 -0
  35. build/lib/pyTEMlib/sidpy_tools.py +153 -0
  36. build/lib/pyTEMlib/simulation_tools.py +104 -0
  37. build/lib/pyTEMlib/test.py +437 -0
  38. build/lib/pyTEMlib/utilities.py +314 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +25 -3
  42. pyTEMlib/animation.py +31 -22
  43. pyTEMlib/atom_tools.py +29 -34
  44. pyTEMlib/config_dir.py +2 -28
  45. pyTEMlib/crystal_tools.py +129 -165
  46. pyTEMlib/eds_tools.py +559 -342
  47. pyTEMlib/eds_xsections.py +432 -0
  48. pyTEMlib/eels_tools/__init__.py +44 -0
  49. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  50. pyTEMlib/eels_tools/eels_database.py +134 -0
  51. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  52. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  53. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  54. pyTEMlib/file_reader.py +274 -0
  55. pyTEMlib/file_tools.py +260 -1130
  56. pyTEMlib/get_bote_salvat.py +69 -0
  57. pyTEMlib/graph_tools.py +101 -174
  58. pyTEMlib/graph_viz.py +150 -0
  59. pyTEMlib/image/__init__.py +37 -0
  60. pyTEMlib/image/image_atoms.py +270 -0
  61. pyTEMlib/image/image_clean.py +197 -0
  62. pyTEMlib/image/image_distortion.py +299 -0
  63. pyTEMlib/image/image_fft.py +277 -0
  64. pyTEMlib/image/image_graph.py +926 -0
  65. pyTEMlib/image/image_registration.py +316 -0
  66. pyTEMlib/image/image_utilities.py +309 -0
  67. pyTEMlib/image/image_window.py +421 -0
  68. pyTEMlib/image_tools.py +154 -915
  69. pyTEMlib/kinematic_scattering.py +1 -1
  70. pyTEMlib/probe_tools.py +1 -1
  71. pyTEMlib/test.py +437 -0
  72. pyTEMlib/utilities.py +314 -0
  73. pyTEMlib/version.py +2 -3
  74. pyTEMlib/xrpa_x_sections.py +14 -10
  75. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
  76. pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
  77. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
  78. pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
  79. pyTEMlib/core_loss_widget.py +0 -721
  80. pyTEMlib/eels_dialog.py +0 -754
  81. pyTEMlib/eels_dialog_utilities.py +0 -1199
  82. pyTEMlib/eels_tools.py +0 -2359
  83. pyTEMlib/file_tools_qt.py +0 -193
  84. pyTEMlib/image_dialog.py +0 -158
  85. pyTEMlib/image_dlg.py +0 -146
  86. pyTEMlib/info_widget.py +0 -1086
  87. pyTEMlib/info_widget3.py +0 -1120
  88. pyTEMlib/low_loss_widget.py +0 -479
  89. pyTEMlib/peak_dialog.py +0 -1129
  90. pyTEMlib/peak_dlg.py +0 -286
  91. pytemlib-0.2025.4.1.dist-info/RECORD +0 -38
  92. pytemlib-0.2025.4.1.dist-info/top_level.txt +0 -1
  93. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
  94. {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
- # import ase
6
- import sys
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 breadth_first_search2(graph, initial, projected_crystal):
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
- # get k next nearest neighbours for each node
820
- main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
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
- k=50) # projection_tags['number_of_nearest_neighbours']*2 + 1)
826
- # print(projection_tags['number_of_nearest_neighbours'] * 2 + 1)
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 = [initial]
831
- ideal_queue = [graph[initial]]
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, initial, lattice_parameter, tolerance=1):
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 = [initial]
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 range(vor.points.shape[0]):
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']