pyTEMlib 0.2025.2.2__py3-none-any.whl → 0.2025.4.0__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.

pyTEMlib/atom_tools.py CHANGED
@@ -61,6 +61,8 @@ def find_atoms(image, atom_size=0.1, threshold=0.):
61
61
  threshold = np.std(im)
62
62
  atoms = blob_log(im, max_sigma=atom_size/scale_x, threshold=threshold)
63
63
 
64
+
65
+
64
66
  return atoms
65
67
 
66
68
 
@@ -500,8 +500,7 @@ class CoreLoss(object):
500
500
  self.core_loss_tab[11, 0].value = edge['areal_density']
501
501
  self.core_loss_tab[11, 2].value = 'a.u.'
502
502
  else:
503
- dispersion = self.parent.energy_scale[1] - \
504
- self.parent.energy_scale[0]
503
+ dispersion = self.parent.energy_scale.slope
505
504
  self.core_loss_tab[11, 0].value = np.round(
506
505
  edge['areal_density']/self.dataset.metadata['experiment']['flux_ppm']*1e-6, 2)
507
506
  self.core_loss_tab[11, 2].value = 'atoms/nm²'
@@ -663,8 +662,7 @@ class CoreLoss(object):
663
662
 
664
663
  edge['areal_density'] = self.core_loss_tab[11, 0].value
665
664
  if self.parent.y_scale != 1.0:
666
- dispersion = self.parent.energy_scale[1] - \
667
- self.parent.energy_scale[0]
665
+ dispersion = self.parent.energy_scale.slope
668
666
  edge['areal_density'] = self.core_loss_tab[11, 0].value * \
669
667
  self.dataset.metadata['experiment']['flux_ppm']/1e-6
670
668
  if 'model' in self.edges:
pyTEMlib/eels_dialog.py CHANGED
@@ -335,7 +335,7 @@ class CompositionWidget(object):
335
335
  self.dd = (self.energy_scale[0], self.energy_scale[1])
336
336
 
337
337
  self.dataset.metadata['experiment']['offset'] = self.energy_scale[0]
338
- self.dataset.metadata['experiment']['dispersion'] = self.energy_scale[1] - self.energy_scale[0]
338
+ self.dataset.metadata['experiment']['dispersion'] = self.spec_dim.slope
339
339
  if 'edges' not in self.dataset.metadata or self.dataset.metadata['edges'] == {}:
340
340
  self.dataset.metadata['edges'] = {'0': {}, 'model': {}, 'use_low_loss': False}
341
341
 
pyTEMlib/eels_tools.py CHANGED
@@ -42,7 +42,6 @@ from pyTEMlib.xrpa_x_sections import x_sections
42
42
 
43
43
  import sidpy
44
44
  from sidpy.proc.fitter import SidFitter
45
- from sidpy.base.num_utils import get_slope
46
45
 
47
46
  # we have a function called find peaks - is it necessary?
48
47
  # or could we just use scipy.signal import find_peaks
@@ -604,7 +603,8 @@ def fit_plasmon(dataset: Union[sidpy.Dataset, np.ndarray], startFitEnergy: float
604
603
  guess_pos = np.argmax(fit_dset)
605
604
  guess_amplitude = fit_dset[guess_pos]
606
605
  guess_width =(endFitEnergy-startFitEnergy)/4
607
- guess_pos = energy[guess_pos]
606
+ guess_pos = energy[start_fit_pixel+guess_pos]
607
+
608
608
  if guess_width >8:
609
609
  guess_width=8
610
610
  try:
@@ -618,7 +618,7 @@ def fit_plasmon(dataset: Union[sidpy.Dataset, np.ndarray], startFitEnergy: float
618
618
  p0=[guess_pos, guess_width, guess_amplitude])
619
619
  except:
620
620
  popt=[0,0,0]
621
-
621
+
622
622
  plasmon = dataset.like_data(energy_loss_function(energy, popt[0], popt[1], popt[2]))
623
623
  plasmon *= anglog
624
624
  start_plasmon = np.searchsorted(energy, 0)+1
@@ -652,16 +652,16 @@ def fit_plasmon(dataset: Union[sidpy.Dataset, np.ndarray], startFitEnergy: float
652
652
  def angle_correction(spectrum):
653
653
 
654
654
  acceleration_voltage = spectrum.metadata['experiment']['acceleration_voltage']
655
- energy_scale = spectrum.get_spectral_dims(return_axis=True)[0].values
655
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
656
656
  # eff_beta = effective_collection_angle(energy_scale, spectrum.metadata['experiment']['convergence_angle'],
657
657
  # spectrum.metadata['experiment']['collection_angle'],acceleration_voltage)
658
658
 
659
659
 
660
- epc = energy_scale[1] - energy_scale[0] # input('ev per channel : ');
660
+ epc = energy_scale.slope # input('ev per channel : ');
661
661
 
662
662
  alpha = spectrum.metadata['experiment']['convergence_angle'] # input('Alpha (mrad) : ');
663
663
  beta = spectrum.metadata['experiment']['collection_angle']# input('Beta (mrad) : ');
664
- e = energy_scale
664
+ e = energy_scale.values
665
665
  e0 = acceleration_voltage/1000 # input('E0 (keV) : ');
666
666
 
667
667
  T = 1000.0*e0*(1.+e0/1022.12)/(1.0+e0/511.06)**2 # %eV # equ.5.2a or Appendix E p 427
@@ -1261,7 +1261,7 @@ def second_derivative(dataset: sidpy.Dataset, sensitivity: float=2.5) -> None:
1261
1261
  """Calculates second derivative of a sidpy.dataset"""
1262
1262
 
1263
1263
  dim = dataset.get_spectral_dims()
1264
- energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
1264
+ energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
1265
1265
  if dataset.data_type.name == 'SPECTRAL_IMAGE':
1266
1266
  spectrum = dataset.view.get_spectrum()
1267
1267
  else:
@@ -1269,7 +1269,7 @@ def second_derivative(dataset: sidpy.Dataset, sensitivity: float=2.5) -> None:
1269
1269
 
1270
1270
  spec = scipy.ndimage.gaussian_filter(spectrum, 3)
1271
1271
 
1272
- dispersion = get_slope(energy_scale)
1272
+ dispersion = energy_scale.slope
1273
1273
  second_dif = np.roll(spec, -3) - 2 * spec + np.roll(spec, +3)
1274
1274
  second_dif[:3] = 0
1275
1275
  second_dif[-3:] = 0
@@ -1405,8 +1405,9 @@ def identify_edges(dataset: sidpy.Dataset, noise_level: float=2.0):
1405
1405
 
1406
1406
  """
1407
1407
  dim = dataset.get_spectral_dims()
1408
- energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
1409
- dispersion = get_slope(energy_scale)
1408
+ energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
1409
+ dispersion = energy_scale.slope
1410
+
1410
1411
  spec = scipy.ndimage.gaussian_filter(dataset, 3/dispersion) # smooth with 3eV wideGaussian
1411
1412
 
1412
1413
  first_derivative = spec - np.roll(spec, +2)
pyTEMlib/file_tools.py CHANGED
@@ -23,6 +23,9 @@ import ase.io
23
23
  import SciFiReaders
24
24
  import pyNSID
25
25
  import sidpy
26
+ import sidpy
27
+ import xml.etree.ElementTree as ET
28
+ import collections
26
29
  import ipywidgets as widgets
27
30
  from IPython.display import display
28
31
 
@@ -33,8 +36,6 @@ import pyTEMlib.crystal_tools
33
36
  from pyTEMlib.config_dir import config_path
34
37
  from pyTEMlib.sidpy_tools import *
35
38
 
36
- from pyTEMlib.sidpy_tools import *
37
-
38
39
  Qt_available = True
39
40
  try:
40
41
  from PyQt5 import QtCore, QtWidgets, QtGui
@@ -44,8 +45,6 @@ except ModuleNotFoundError:
44
45
 
45
46
  Dimension = sidpy.Dimension
46
47
 
47
- # Austin commented the line below - it is not used anywhere in the code, and it gives import errors 9-14-2024
48
- # get_slope = sidpy.base.num_utils.get_slopes
49
48
  __version__ = '2024.9.14'
50
49
 
51
50
  from traitlets import Unicode, Bool, validate, TraitError
@@ -116,7 +115,6 @@ class FileWidget2(ipywidgets.DOMWidget):
116
115
  value='None',
117
116
  description='directory:',
118
117
  disabled=False,
119
- button_style='',
120
118
  layout=widgets.Layout(width='90%'))
121
119
 
122
120
 
@@ -203,8 +201,7 @@ class FileWidget3(FileWidget2):
203
201
  self.loaded_datasets = widgets.Dropdown(options=self.dataset_list,
204
202
  value=self.dataset_list[0],
205
203
  description='loaded datasets:',
206
- disabled=False,
207
- button_style='')
204
+ disabled=False)
208
205
 
209
206
  ui = widgets.HBox([select_button, add_button, self.loaded_datasets])
210
207
  display(ui)
@@ -313,14 +310,12 @@ class FileWidget(ipywidgets.DOMWidget):
313
310
  value='None',
314
311
  description='directory:',
315
312
  disabled=False,
316
- button_style='',
317
313
  layout=widgets.Layout(width='90%'))
318
314
  self.dataset_list = ['None']
319
315
  self.loaded_datasets = widgets.Dropdown(options=self.dataset_list,
320
316
  value=self.dataset_list[0],
321
317
  description='loaded datasets:',
322
- disabled=False,
323
- button_style='')
318
+ disabled=False)
324
319
 
325
320
  self.set_options()
326
321
  ui = widgets.VBox([self.path_choice, self.select_files, widgets.HBox([select_button, add_button,
@@ -446,8 +441,7 @@ class ChooseDataset(object):
446
441
  self.select_image = widgets.Dropdown(options=self.dataset_list,
447
442
  value=self.dataset_list[0],
448
443
  description='select dataset:',
449
- disabled=False,
450
- button_style='')
444
+ disabled=False)
451
445
  if show_dialog:
452
446
  display(self.select_image)
453
447
 
@@ -484,7 +478,7 @@ class ChooseDataset(object):
484
478
  self.dataset = self.datasets[self.key]
485
479
  self.dataset.title = self.dataset.title.split('/')[-1]
486
480
  self.dataset.title = self.dataset.title.split('/')[-1]
487
-
481
+
488
482
 
489
483
  def add_to_dict(file_dict, name):
490
484
  full_name = os.path.join(file_dict['directory'], name)
@@ -884,7 +878,6 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
884
878
  else:
885
879
  if isinstance(datasets, dict):
886
880
  dataset_dict = datasets
887
-
888
881
  else:
889
882
  dataset_dict = {}
890
883
  for index, dataset in enumerate(datasets):
@@ -926,23 +919,28 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
926
919
  dset.title = dset.title + '_SI'
927
920
  dset = dset.T
928
921
  dset.title = dset.title[11:]
922
+ dset.add_provenance('pyTEMlib', 'open_file', version=pyTEMlib.__version__, linked_data='emi_converted_by_hyperspy')
929
923
  dataset_dict[f'Channel_{index:03d}'] = dset
924
+
930
925
  return dataset_dict
931
926
  except ImportError:
932
927
  print('This file type needs hyperspy to be installed to be able to be read')
933
928
  return
934
929
  elif extension == '.emd':
935
930
  reader = SciFiReaders.EMDReader(filename, sum_frames=sum_frames)
936
-
931
+ provenance = 'SciFiReader.EMDReader'
937
932
  elif 'edax' in extension.lower():
938
933
  if 'h5' in extension:
939
934
  reader = SciFiReaders.EDAXReader(filename)
935
+ provenance = 'SciFiReader.EDAXReader'
940
936
 
941
937
  elif extension in ['.ndata', '.h5']:
942
938
  reader = SciFiReaders.NionReader(filename)
943
-
939
+ provenance = 'SciFiReader.NionReader'
940
+
944
941
  elif extension in ['.mrc']:
945
942
  reader = SciFiReaders.MRCReader(filename)
943
+ provenance = 'SciFiReader.MRCReader'
946
944
 
947
945
  else:
948
946
  raise NotImplementedError('extension not supported')
@@ -994,6 +992,7 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
994
992
  if isinstance(dset, dict):
995
993
  dataset_dict = dset
996
994
  for dataset in dataset_dict.values():
995
+ dataset.add_provenance('pyTEMlib', 'open_file', version=pyTEMlib.__version__, linked_data = 'SciFiReader')
997
996
  dataset.metadata['filename'] = filename
998
997
 
999
998
  elif isinstance(dset, list):
@@ -1431,6 +1430,97 @@ def h5_get_crystal_structure(structure_group):
1431
1430
  # ToDo: Read all of info dictionary
1432
1431
  return atoms
1433
1432
 
1433
+ import collections
1434
+ def etree_to_dict(element):
1435
+ """Recursively converts an ElementTree object into a nested dictionary."""
1436
+ d = {element.tag: {} if element.attrib else None}
1437
+ children = list(element)
1438
+ if children:
1439
+ dd = collections.defaultdict(list)
1440
+ for dc in map(etree_to_dict, children):
1441
+ for k, v in dc.items():
1442
+ dd[k].append(v)
1443
+ d = {element.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
1444
+ if element.attrib:
1445
+ d[element.tag].update(('@' + k, v) for k, v in element.attrib.items())
1446
+ if element.text:
1447
+ text = element.text.strip()
1448
+ if children or element.attrib:
1449
+ if text:
1450
+ d[element.tag]['#text'] = text
1451
+ else:
1452
+ d[element.tag] = text
1453
+ return d
1454
+
1455
+ def read_adorned_metadata(image):
1456
+ xml_str = image.metadata.metadata_as_xml
1457
+ root = ET.fromstring(xml_str)
1458
+ metadata_dict = etree_to_dict(root)
1459
+ detector = 'detector'
1460
+ if 'Detectors' in metadata_dict['Metadata']['Detectors']['ScanningDetector']:
1461
+ if 'ScanningDetector' in metadata_dict['Metadata']['Detectors']['ScanningDetector']:
1462
+ detector = metadata_dict['Metadata']['Detectors']['ScanningDetector']['DetectorName']
1463
+
1464
+ segment = ''
1465
+ if 'CustomPropertyGroup' in metadata_dict['Metadata']:
1466
+ if 'CustomProperties' in metadata_dict['Metadata']['CustomPropertyGroup']:
1467
+ for list_item in metadata_dict['Metadata']['CustomPropertyGroup']['CustomProperties']:
1468
+
1469
+ if isinstance(list_item, dict):
1470
+ for key in list_item:
1471
+ for item in list_item[key]:
1472
+ if '@name' in item:
1473
+ if item['@name']== 'DetectorCommercialName':
1474
+ detector = item['@value']
1475
+ if item['@name']== 'StemSegment':
1476
+ segment = '_'+item['@value']
1477
+ return detector+segment, metadata_dict['Metadata']
1478
+
1479
+ def adorned_to_sidpy(images):
1480
+ """
1481
+ Convert a list of adorned images to a dictionary of Sidpy datasets.
1482
+ Each dataset is created from the image data and adorned metadata.
1483
+ The datasets are stored in a dictionary with keys 'Channel_000', 'Channel_001', etc.
1484
+ The dimensions of the datasets are set based on the image data shape and pixel sizes.
1485
+ The original metadata is also stored in the dataset.
1486
+ Args:
1487
+ images (list or object): A list of adorned images or a single adorned image.
1488
+ Returns:
1489
+ dict: A dictionary of Sidpy datasets, where each dataset corresponds to an image.
1490
+ """
1491
+
1492
+ data_sets = {}
1493
+ if not isinstance(images, list):
1494
+ images = [images]
1495
+ for index, image in enumerate(images):
1496
+ name, original_metadata = read_adorned_metadata(image)
1497
+ data_sets[f'Channel_{index:03}'] = sidpy.Dataset.from_array(image.data.T, title=name)
1498
+ ds = data_sets[f'Channel_{index:03}']
1499
+
1500
+
1501
+ ds.original_metadata = original_metadata
1502
+
1503
+ pixel_size_x_m = float(ds.original_metadata['BinaryResult']['PixelSize']['X']['#text'])
1504
+ pixel_size_y_m = float(ds.original_metadata['BinaryResult']['PixelSize']['Y']['#text'])
1505
+ pixel_size_x_nm = pixel_size_x_m * 1e9
1506
+ pixel_size_y_nm = pixel_size_y_m * 1e9
1507
+ if image.data.ndim == 3:
1508
+ ds.data_type = 'image_stack'
1509
+ ds.set_dimension(0, sidpy.Dimension(np.arange(image.data.shape[0]),
1510
+ name='frame', units='frame', quantity='Length', dimension_type='temporal'))
1511
+ ds.set_dimension(1, sidpy.Dimension(np.arange(image.data.shape[1]) * pixel_size_y_nm,
1512
+ name='y', units='nm', quantity='Length', dimension_type='spatial'))
1513
+ ds.set_dimension(2, sidpy.Dimension(np.arange(image.data.shape[2]) * pixel_size_x_nm,
1514
+ name='x', units='nm', quantity='Length', dimension_type='spatial'))
1515
+ else:
1516
+ ds.data_type = 'image'
1517
+ ds.set_dimension(0, sidpy.Dimension(np.arange(image.data.shape[0]) * pixel_size_y_nm,
1518
+ name='y', units='nm', quantity='Length', dimension_type='spatial'))
1519
+ ds.set_dimension(1, sidpy.Dimension(np.arange(image.data.shape[1]) * pixel_size_x_nm,
1520
+ name='x', units='nm', quantity='Length', dimension_type='spatial'))
1521
+
1522
+ return data_sets
1523
+
1434
1524
 
1435
1525
  ###############################################
1436
1526
  # Support old pyTEM file format
pyTEMlib/graph_tools.py CHANGED
@@ -5,7 +5,6 @@ import numpy as np
5
5
  # import ase
6
6
  import sys
7
7
 
8
- # from scipy.spatial import cKDTree, Voronoi, ConvexHull
9
8
  import scipy.spatial
10
9
  import scipy.optimize
11
10
  import scipy.interpolate
@@ -20,7 +19,7 @@ import pyTEMlib.crystal_tools
20
19
  from tqdm.auto import tqdm, trange
21
20
 
22
21
  from .graph_viz import *
23
-
22
+ QT_available = False
24
23
 
25
24
  ###########################################################################
26
25
  # utility functions
@@ -212,6 +211,14 @@ def get_voronoi(tetrahedra, atoms, bond_radii=None, optimize=True):
212
211
  """
213
212
 
214
213
  extent = atoms.cell.lengths()
214
+ print('extent', extent)
215
+
216
+ if np.abs(atoms.positions[:, 2]).sum() <= 0.01:
217
+ positions = atoms.positions[:, :2]
218
+ extent = extent[:2]
219
+ else:
220
+ positions = atoms.positions
221
+
215
222
  if atoms.info is None:
216
223
  atoms.info = {}
217
224
 
@@ -232,8 +239,8 @@ def get_voronoi(tetrahedra, atoms, bond_radii=None, optimize=True):
232
239
  r_a = []
233
240
  for vert in vertices:
234
241
  r_a.append(bond_radii[vert])
235
- voronoi, radius = interstitial_sphere_center(atoms.positions[vertices], r_a, optimize=optimize)
236
-
242
+ voronoi, radius = interstitial_sphere_center(positions[vertices], r_a, optimize=optimize)
243
+
237
244
  r_a = np.average(r_a) # np.min(r_a)
238
245
  r_aa.append(r_a)
239
246
 
@@ -247,7 +254,7 @@ def get_voronoi(tetrahedra, atoms, bond_radii=None, optimize=True):
247
254
  def find_overlapping_spheres(voronoi_vertices, r_vv, r_a, cheat=1.):
248
255
  """Find overlapping spheres"""
249
256
 
250
- vertex_tree = scipy.spatial.cKDTree(voronoi_vertices)
257
+ vertex_tree = scipy.spatial.KDTree(voronoi_vertices)
251
258
 
252
259
  pairs = vertex_tree.query_pairs(r=r_a * 2)
253
260
 
@@ -424,7 +431,7 @@ def get_non_periodic_supercell(super_cell):
424
431
  return atoms
425
432
 
426
433
  def get_connectivity_matrix(crystal, atoms, polyhedra):
427
- crystal_tree = scipy.spatial.cKDTree(crystal.positions)
434
+ crystal_tree = scipy.spatial.KDTree(crystal.positions)
428
435
 
429
436
 
430
437
  connectivity_matrix = np.zeros([len(atoms),len(atoms)], dtype=int)
@@ -476,8 +483,8 @@ def get_bonds(crystal, shift= 0., verbose = False, cheat=1.0):
476
483
  other = []
477
484
  super_cell_atoms =[]
478
485
 
479
- atoms_tree = scipy.spatial.cKDTree(atoms.positions-crystal.cell.lengths())
480
- crystal_tree = scipy.spatial.cKDTree(crystal.positions)
486
+ atoms_tree = scipy.spatial.KDTree(atoms.positions-crystal.cell.lengths())
487
+ crystal_tree = scipy.spatial.KDTree(crystal.positions)
481
488
  connectivity_matrix = np.zeros([len(atoms),len(atoms)], dtype=float)
482
489
 
483
490
  for polyhedron in polyhedra.values():
@@ -699,18 +706,21 @@ def find_polyhedra(atoms, optimize=True, cheat=1.0, bond_radii=None):
699
706
  raise TypeError('This function needs an ase.Atoms object')
700
707
 
701
708
  if np.abs(atoms.positions[:, 2]).sum() <= 0.01:
702
- tetrahedra = scipy.spatial.Delaunay(atoms.positions[:, :2])
709
+ positions = atoms.positions[:, :2]
710
+ print('2D')
703
711
  else:
704
- tetrahedra = scipy.spatial.Delaunay(atoms.positions)
712
+ positions = atoms.positions
713
+ tetrahedra = scipy.spatial.Delaunay(positions)
705
714
 
706
715
  voronoi_vertices, voronoi_tetrahedrons, r_vv, r_a = get_voronoi(tetrahedra, atoms, optimize=optimize, bond_radii=bond_radii)
707
- if np.abs(atoms.positions[:, 2]).sum() <= 0.01:
708
- r_vv = np.array(r_vv)*3.
716
+
717
+ if positions.shape[1] < 3:
718
+ r_vv = np.array(r_vv)*1.
709
719
  overlapping_pairs = find_overlapping_spheres(voronoi_vertices, r_vv, r_a, cheat=cheat)
710
720
 
711
721
  clusters, visited_all = find_interstitial_clusters(overlapping_pairs)
712
722
 
713
- if np.abs(atoms.positions[:, 2]).sum() <= 0.01:
723
+ if positions.shape[1] < 3:
714
724
  rings = get_polygons(atoms, clusters, voronoi_tetrahedrons)
715
725
  return rings
716
726
  else:
@@ -770,7 +780,7 @@ def sort_polyhedra_by_vertices(polyhedra, visible=range(4, 100), z_lim=[0, 100],
770
780
  ##########################
771
781
  # New Graph Stuff
772
782
  ##########################
773
- def breadth_first_search(graph, initial, projected_crystal):
783
+ def breadth_first_search2(graph, initial, projected_crystal):
774
784
  """ breadth first search of atoms viewed as a graph
775
785
 
776
786
  the projection dictionary has to contain the following items
@@ -794,15 +804,23 @@ def breadth_first_search(graph, initial, projected_crystal):
794
804
  """
795
805
 
796
806
  projection_tags = projected_crystal.info['projection']
797
-
798
- # get lattice vectors to hopp along through graph
799
- projected_unit_cell = projected_crystal.cell[:2, :2]
800
- a_lattice_vector = projected_unit_cell[0]
801
- b_lattice_vector = projected_unit_cell[1]
802
- main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
803
- near = np.append(main, projection_tags['near_base'], axis=0) # all nearest atoms
807
+ if 'lattice_vector' in projection_tags:
808
+ a_lattice_vector = projection_tags['lattice_vector']['a']
809
+ b_lattice_vector = projection_tags['lattice_vector']['b']
810
+ main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
811
+ near = []
812
+ else:
813
+ # get lattice vectors to hopp along through graph
814
+ projected_unit_cell = projected_crystal.cell[:2, :2]
815
+ a_lattice_vector = projected_unit_cell[0]
816
+ b_lattice_vector = projected_unit_cell[1]
817
+ main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
818
+ near = projection_tags['near_base'] # all nearest atoms
804
819
  # get k next nearest neighbours for each node
805
- neighbour_tree = scipy.spatial.cKDTree(graph)
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
+
823
+ neighbour_tree = scipy.spatial.KDTree(graph)
806
824
  distances, indices = neighbour_tree.query(graph, # let's get all neighbours
807
825
  k=50) # projection_tags['number_of_nearest_neighbours']*2 + 1)
808
826
  # print(projection_tags['number_of_nearest_neighbours'] * 2 + 1)
@@ -835,6 +853,53 @@ def breadth_first_search(graph, initial, projected_crystal):
835
853
 
836
854
  return graph[visited], ideal
837
855
 
856
+
857
+
858
+ def breadth_first_search_felxible(graph, initial, lattice_parameter, tolerance=1):
859
+ """ breadth first search of atoms viewed as a graph
860
+ This is a rotational invariant search of atoms in a lattice, and returns the angles of unit cells.
861
+ We only use the ideal lattice parameter to determine the lattice.
862
+ """
863
+ if isinstance(lattice_parameter, ase.Atoms):
864
+ lattice_parameter = lattice_parameter.cell.lengths()[:2]
865
+ elif isinstance(lattice_parameter, float):
866
+ lattice_parameter = [lattice_parameter]
867
+ lattice_parameter = np.array(lattice_parameter)
868
+
869
+ 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)
872
+ visited = [] # the atoms we visited
873
+ angles = [] # atoms at ideal lattice
874
+ sub_lattice = [] # atoms in base and disregarded
875
+ queue = [initial]
876
+ queue_angles=[0]
877
+
878
+ while queue:
879
+ node = queue.pop(0)
880
+ angle = queue_angles.pop(0)
881
+ if node not in visited:
882
+ visited.append(node)
883
+ angles.append(angle)
884
+ neighbors = indices[node]
885
+ for i, neighbour in enumerate(neighbors):
886
+ if neighbour not in visited:
887
+ hopp = graph[node] - graph[neighbour]
888
+ distance_to_ideal = np.linalg.norm(hopp)
889
+ if np.min(np.abs(distance_to_ideal - lattice_parameter)) < tolerance:
890
+ queue.append(neighbour)
891
+ queue_angles.append(np.arctan2(hopp[1], hopp[0]))
892
+ angles[0] = angles[1]
893
+ out_atoms = np.stack([graph[visited][:, 0], graph[visited][:, 1], angles])
894
+ return out_atoms.T, visited
895
+
896
+ def delete_rim_atoms(atoms, extent, rim_distance):
897
+ rim = np.where(atoms[:, :2] - extent > -rim_distance)[0]
898
+ middle_atoms = np.delete(atoms, rim, axis=0)
899
+ rim = np.where(middle_atoms[:, :2].min(axis=1)<rim_distance)[0]
900
+ middle_atoms = np.delete(middle_atoms, rim, axis=0)
901
+ return middle_atoms
902
+
838
903
  ####################
839
904
  # Distortion Matrix
840
905
  ####################
@@ -992,7 +1057,7 @@ def get_significant_vertices(vertices, distance=3):
992
1057
  list of points that are all a minimum of 3 apart.
993
1058
  """
994
1059
 
995
- tt = scipy.spatial.cKDTree(np.array(vertices))
1060
+ tt = scipy.spatial.KDTree(np.array(vertices))
996
1061
  near = tt.query_ball_point(vertices, distance)
997
1062
  ideal_vertices = []
998
1063
  for indices in near:
@@ -1146,21 +1211,16 @@ def undistort_stack(distortion_matrix, data):
1146
1211
  nimages = data.shape[0]
1147
1212
  done = 0
1148
1213
 
1149
- if QT_available:
1150
- progress = ft.ProgressDialog("Correct Scan Distortions", nimages)
1214
+
1151
1215
  for i in range(nimages):
1152
- if QT_available:
1153
- progress.set_value(i)
1154
- elif done < int((i + 1) / nimages * 50):
1155
- done = int((i + 1) / nimages * 50)
1156
- sys.stdout.write('\r')
1157
- # progress output :
1158
- sys.stdout.write("[%-50s] %d%%" % ('=' * done, 2 * done))
1159
- sys.stdout.flush()
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()
1160
1221
 
1161
1222
  interpolated[i, :, :] = griddata(corrected, intensity_values[i, :], (grid_x, grid_y), method='linear')
1162
- if QT_available:
1163
- progress.set_value(nimages)
1223
+
1164
1224
  print(':-)')
1165
1225
  print('You have successfully completed undistortion of image stack')
1166
1226
  return interpolated
pyTEMlib/image_tools.py CHANGED
@@ -182,24 +182,21 @@ def fourier_transform(dset: sidpy.Dataset) -> sidpy.Dataset:
182
182
  assert isinstance(dset, sidpy.Dataset), 'Expected a sidpy Dataset'
183
183
 
184
184
  selection = []
185
- image_dim = []
186
- # image_dim = get_image_dims(sidpy.DimensionTypes.SPATIAL)
187
-
185
+ image_dims = pyTEMlib.sidpy_tools.get_image_dims(dset)
188
186
  if dset.data_type == sidpy.DataType.IMAGE_STACK:
189
- image_dim = dset.get_image_dims()
190
187
  stack_dim = dset.get_dimensions_by_type('TEMPORAL')
191
188
 
192
- if len(image_dim) != 2:
189
+ if len(image_dims) != 2:
193
190
  raise ValueError('need at least two SPATIAL dimension for an image stack')
194
191
 
195
192
  for i in range(dset.ndim):
196
- if i in image_dim:
193
+ if i in image_dims:
197
194
  selection.append(slice(None))
198
195
  if len(stack_dim) == 0:
199
- stack_dim = i
196
+ stack_dims = i
200
197
  selection.append(slice(None))
201
198
  elif i in stack_dim:
202
- stack_dim = i
199
+ stack_dims = i
203
200
  selection.append(slice(None))
204
201
  else:
205
202
  selection.append(slice(0, 1))
@@ -212,6 +209,7 @@ def fourier_transform(dset: sidpy.Dataset) -> sidpy.Dataset:
212
209
  return
213
210
 
214
211
  new_image = new_image - new_image.min()
212
+
215
213
  fft_transform = (np.fft.fftshift(np.fft.fft2(new_image)))
216
214
 
217
215
  image_dims = pyTEMlib.sidpy_tools.get_image_dims(dset)
@@ -227,12 +225,11 @@ def fourier_transform(dset: sidpy.Dataset) -> sidpy.Dataset:
227
225
  fft_dset.modality = 'fft'
228
226
 
229
227
  fft_dset.set_dimension(0, sidpy.Dimension(np.fft.fftshift(np.fft.fftfreq(new_image.shape[0],
230
- d=ft.get_slope(dset.x.values))),
231
-
228
+ d=dset.x[1]-dset.x[0])),
232
229
  name='u', units=units_x, dimension_type='RECIPROCAL',
233
230
  quantity='reciprocal_length'))
234
231
  fft_dset.set_dimension(1, sidpy.Dimension(np.fft.fftshift(np.fft.fftfreq(new_image.shape[1],
235
- d=ft.get_slope(dset.y.values))),
232
+ d=dset.y[1]- dset.y[0])),
236
233
  name='v', units=units_y, dimension_type='RECIPROCAL',
237
234
  quantity='reciprocal_length'))
238
235
 
@@ -319,7 +316,9 @@ def diffractogram_spots(dset, spot_threshold, return_center=True, eps=0.1):
319
316
  print(f'Found {spots_random.shape[0]} reflections')
320
317
 
321
318
  # Needed for conversion from pixel to Reciprocal space
322
- rec_scale = np.array([ft.get_slope(dset.u.values), ft.get_slope(dset.v.values)])
319
+ image_dims = dset.get_image_dims(return_axis=True)
320
+ rec_scale = np.array([image_dims[0].slope, image_dims[1].slope])
321
+
323
322
  spots_random[:, :2] = spots_random[:, :2]*rec_scale+[dset.u.values[0], dset.v.values[0]]
324
323
  # sort reflections
325
324
  spots_random[:, 2] = np.linalg.norm(spots_random[:, 0:2], axis=1)
@@ -516,7 +515,8 @@ def complete_registration(main_dataset, storage_channel=None):
516
515
 
517
516
  rigid_registered_dataset = rigid_registration(main_dataset)
518
517
 
519
-
518
+ print(rigid_registered_dataset)
519
+ rigid_registered_dataset.data_type = 'IMAGE_STACK'
520
520
  print('Non-Rigid_Registration')
521
521
 
522
522
  non_rigid_registered = demon_registration(rigid_registered_dataset)
@@ -580,7 +580,6 @@ def demon_registration(dataset, verbose=False):
580
580
  resampler.SetDefaultPixelValue(0)
581
581
 
582
582
  for i in trange(nimages):
583
-
584
583
  moving = sitk.GetImageFromArray(dataset[i])
585
584
  moving_f = sitk.DiscreteGaussian(moving, 2.0)
586
585
  displacement_field = demons.Execute(fixed, moving_f)
@@ -596,20 +595,21 @@ def demon_registration(dataset, verbose=False):
596
595
  demon_registered.title = 'Non-Rigid Registration'
597
596
  demon_registered.source = dataset.title
598
597
 
599
- demon_registered.metadata = {'analysis': 'non-rigid demon registration'}
600
- demon_registered.metadata['experiment'] = dataset.metadata['experiment'].copy()
601
- if 'input_crop' in dataset.metadata:
602
- demon_registered.metadata['input_crop'] = dataset.metadata['input_crop']
603
- if 'input_shape' in dataset.metadata:
604
- demon_registered.metadata['input_shape'] = dataset.metadata['input_shape']
605
- demon_registered.metadata['input_dataset'] = dataset.source
598
+ demon_registered.metadata =dataset.metadata.copy()
599
+ if 'analysis' not in demon_registered.metadata:
600
+ demon_registered.metadata['analysis'] = {}
601
+ demon_registered.metadata['analysis']['non_rigid_demon_registration'] = {'package': 'simpleITK',
602
+ 'method': 'DiscreteGaussian',
603
+ 'variance': 2,
604
+ 'input_dataset': dataset.source}
605
+ demon_registered.data_type = 'IMAGE_STACK'
606
606
  return demon_registered
607
607
 
608
608
 
609
609
  ###############################
610
610
  # Rigid Registration New 05/09/2020
611
611
 
612
- def rigid_registration(dataset, sub_pixel=True):
612
+ def rigid_registration(dataset, normalization=None):
613
613
  """
614
614
  Rigid registration of image stack with pixel accuracy
615
615
 
@@ -631,51 +631,32 @@ def rigid_registration(dataset, sub_pixel=True):
631
631
  raise TypeError('We need a sidpy.Dataset')
632
632
  if dataset.data_type.name != 'IMAGE_STACK':
633
633
  raise TypeError('Registration makes only sense for an image stack')
634
-
635
- frame_dim = []
636
- spatial_dim = []
637
- selection = []
638
-
639
- for i, axis in dataset._axes.items():
640
- if axis.dimension_type.name == 'SPATIAL':
641
- spatial_dim.append(i)
642
- selection.append(slice(None))
643
- else:
644
- frame_dim.append(i)
645
- selection.append(slice(0, 1))
646
-
647
- if len(spatial_dim) != 2:
648
- print('need two spatial dimensions')
649
- if len(frame_dim) != 1:
650
- print('need one frame dimensions')
651
-
652
- nopix = dataset.shape[spatial_dim[0]]
653
- nopiy = dataset.shape[spatial_dim[1]]
654
- nimages = dataset.shape[frame_dim[0]]
655
-
656
- print('Stack contains ', nimages, ' images, each with', nopix, ' pixels in x-direction and ', nopiy,
657
- ' pixels in y-direction')
658
-
659
- fixed = dataset[tuple(selection)].squeeze().compute()
660
- fft_fixed = np.fft.fft2(fixed)
634
+
635
+ if isinstance (normalization, str):
636
+ if normalization.lower() != 'phase':
637
+ nomralization = None
638
+ else:
639
+ normalization = None
640
+
641
+ if dataset.get_dimensions_by_type('TEMPORAL')[0] != 0:
642
+ raise TypeError('Image stack does not have correct frame dimension')
643
+
644
+ stack_dim = dataset.get_dimensions_by_type('TEMPORAL', return_axis=True)[0]
645
+ image_dim = dataset.get_image_dims(return_axis=True)
646
+ if len(image_dim) != 2:
647
+ raise ValueError('need at least two SPATIAL dimension for an image stack')
661
648
 
662
649
  relative_drift = [[0., 0.]]
663
-
664
- for i in trange(nimages):
665
- selection[frame_dim[0]] = slice(i, i+1)
666
- moving = dataset[tuple(selection)].squeeze().compute()
667
- fft_moving = np.fft.fft2(moving)
668
- if sub_pixel:
669
- shift = skimage.registration.phase_cross_correlation(fft_fixed, fft_moving, upsample_factor=1000,
670
- space='fourier')[0]
671
- else:
672
- image_product = fft_fixed * fft_moving.conj()
673
- cc_image = np.fft.fftshift(np.fft.ifft2(image_product))
674
- shift = np.array(ndimage.maximum_position(cc_image.real))-cc_image.shape[0]/2
675
- fft_fixed = fft_moving
676
- relative_drift.append(shift)
677
- rig_reg, drift = rig_reg_drift(dataset, relative_drift)
678
- crop_reg, input_crop = crop_image_stack(rig_reg, drift)
650
+ im1 = np.fft.fft2(np.array(dataset[0]))
651
+ for i in range(1, len(stack_dim)):
652
+ im2 = np.fft.fft2(np.array(dataset[i]))
653
+ shift, error, _ = skimage.registration.phase_cross_correlation(im1, im2, normalization=normalization, space='fourier')
654
+ print(shift)
655
+ im1 = im2.copy()
656
+ relative_drift.append(shift)
657
+
658
+ rig_reg, drift = pyTEMlib.image_tools.rig_reg_drift(dataset, relative_drift)
659
+ crop_reg, input_crop = pyTEMlib.image_tools.crop_image_stack(rig_reg, drift)
679
660
 
680
661
  rigid_registered = sidpy.Dataset.from_array(crop_reg,
681
662
  title='Rigid Registration',
@@ -684,21 +665,22 @@ def rigid_registration(dataset, sub_pixel=True):
684
665
  units=dataset.units)
685
666
  rigid_registered.title = 'Rigid_Registration'
686
667
  rigid_registered.source = dataset.title
687
- rigid_registered.metadata = {'analysis': 'rigid sub-pixel registration', 'drift': drift,
688
- 'input_crop': input_crop, 'input_shape': dataset.shape[1:]}
668
+ rigid_registered.metadata['analysis'] = {'rigid_registration': {'drift': drift,
669
+ 'input_crop': input_crop, 'input_shape': dataset.shape[1:]}}
689
670
  rigid_registered.metadata['experiment'] = dataset.metadata['experiment'].copy()
690
671
  rigid_registered.set_dimension(0, sidpy.Dimension(np.arange(rigid_registered.shape[0]),
691
672
  name='frame', units='frame', quantity='time',
692
673
  dimension_type='temporal'))
693
674
 
694
- array_x = dataset._axes[spatial_dim[0]][input_crop[0]:input_crop[1]].values
695
- rigid_registered.set_dimension(1, sidpy.Dimension(array_x,
696
- 'x', units='nm', quantity='Length',
697
- dimension_type='spatial'))
698
- array_y = dataset._axes[spatial_dim[1]][input_crop[2]:input_crop[3]].values
699
- rigid_registered.set_dimension(2, sidpy.Dimension(array_y,
700
- 'y', units='nm', quantity='Length',
701
- dimension_type='spatial'))
675
+ array_x = image_dim[0].values[input_crop[0]:input_crop[1]]
676
+ rigid_registered.set_dimension(1, sidpy.Dimension(array_x, name='x',
677
+ units='nm', quantity='Length',
678
+ dimension_type='spatial'))
679
+ array_y =image_dim[1].values[input_crop[2]:input_crop[3]]
680
+ rigid_registered.set_dimension(2, sidpy.Dimension(array_y, name='y',
681
+ units='nm', quantity='Length',
682
+ dimension_type='spatial'))
683
+ rigid_registered.data_type = 'IMAGE_STACK'
702
684
  return rigid_registered.rechunk({0: 'auto', 1: -1, 2: -1})
703
685
 
704
686
 
@@ -773,13 +755,12 @@ def crop_image_stack(rig_reg, drift):
773
755
  -------
774
756
  numpy array
775
757
  """
758
+ xpmax = int(rig_reg.shape[1] - -np.floor(np.min(np.array(drift)[:, 0])))
759
+ xpmin = int(np.ceil(np.max(np.array(drift)[:, 0])))
760
+ ypmax = int(rig_reg.shape[1] - -np.floor(np.min(np.array(drift)[:, 1])))
761
+ ypmin = int(np.ceil(np.max(np.array(drift)[:, 1])))
776
762
 
777
- xpmin = int(-np.floor(np.min(np.array(drift)[:, 0])))
778
- xpmax = int(rig_reg.shape[1] - np.ceil(np.max(np.array(drift)[:, 0])))
779
- ypmin = int(-np.floor(np.min(np.array(drift)[:, 1])))
780
- ypmax = int(rig_reg.shape[2] - np.ceil(np.max(np.array(drift)[:, 1])))
781
-
782
- return rig_reg[:, xpmin:xpmax, ypmin:ypmax], [xpmin, xpmax, ypmin, ypmax]
763
+ return rig_reg[:, xpmin:xpmax, ypmin:ypmax:], [xpmin, xpmax, ypmin, ypmax]
783
764
 
784
765
 
785
766
  class ImageWithLineProfile:
@@ -902,9 +883,9 @@ def get_profile(dataset, line, spline_order=-1):
902
883
  """
903
884
  xv, yv = get_line_selection_points(line)
904
885
  if dataset.data_type.name == 'IMAGE':
905
- dataset.get_image_dims()
906
- xv /= (dataset.x[1] - dataset.x[0])
907
- yv /= (dataset.y[1] - dataset.y[0])
886
+ image_dims = dataset.get_image_dims(return_axis=True)
887
+ xv /= image_dims[0].slope
888
+ yv /= image_dims[1].slope
908
889
  profile = scipy.ndimage.map_coordinates(np.array(dataset), [xv, yv])
909
890
 
910
891
  profile_dataset = sidpy.Dataset.from_array(profile.sum(axis=0))
@@ -1101,7 +1082,7 @@ def clean_svd(im, pixel_size=1, source_size=5):
1101
1082
  patch_size = int(source_size/pixel_size)
1102
1083
  if patch_size < 3:
1103
1084
  patch_size = 3
1104
- patches = image.extract_patches_2d(im, (patch_size, patch_size))
1085
+ patches = image.extract_patches_2d(np.array(im), (patch_size, patch_size))
1105
1086
  patches = patches.reshape(patches.shape[0], patches.shape[1]*patches.shape[2])
1106
1087
 
1107
1088
  num_components = 32
@@ -1110,6 +1091,8 @@ def clean_svd(im, pixel_size=1, source_size=5):
1110
1091
  u_im_size = int(np.sqrt(u.shape[0]))
1111
1092
  reduced_image = u[:, 0].reshape(u_im_size, u_im_size)
1112
1093
  reduced_image = reduced_image/reduced_image.sum()*im.sum()
1094
+ if isinstance(im, sidpy.Dataset):
1095
+ reduced_image = im.like_data(reduced_image)
1113
1096
  return reduced_image
1114
1097
 
1115
1098
 
@@ -1402,11 +1385,16 @@ def decon_lr(o_image, probe, verbose=False):
1402
1385
  error = np.ones(o_image.shape, dtype=np.complex64)
1403
1386
  est = np.ones(o_image.shape, dtype=np.complex64)
1404
1387
  source = np.ones(o_image.shape, dtype=np.complex64)
1388
+ o_image = o_image - o_image.min()
1389
+ image_mult = o_image.max()
1390
+ o_image = o_image / o_image.max()
1405
1391
  source.real = o_image
1406
1392
 
1407
1393
  response_ft = fftpack.fft2(probe_c)
1408
1394
 
1409
- ap_angle = o_image.metadata['experiment']['convergence_angle'] / 1000.0 # now in rad
1395
+ ap_angle = o_image.metadata['experiment']['convergence_angle']
1396
+ if ap_angle > .1:
1397
+ ap_angle /= 1000 # now in rad
1410
1398
 
1411
1399
  e0 = float(o_image.metadata['experiment']['acceleration_voltage'])
1412
1400
 
@@ -1444,13 +1432,10 @@ def decon_lr(o_image, probe, verbose=False):
1444
1432
  est_old = est.copy()
1445
1433
  error = source / np.real(fftpack.fftshift(fftpack.ifft2(fftpack.fft2(est) * response_ft)))
1446
1434
  est = est * np.real(fftpack.fftshift(fftpack.ifft2(fftpack.fft2(error) * np.conjugate(response_ft))))
1447
- # est = est_old * est
1448
- # est = np.real(fftpack.fftshift(fftpack.ifft2(fftpack.fft2(est)*fftpack.fftshift(aperture) )))
1449
-
1435
+
1450
1436
  error_new = np.real(np.sum(np.power(error, 2))) - error_old
1451
1437
  dest = np.sum(np.power((est - est_old).real, 2)) / np.sum(est) * 100
1452
- # print(np.sum((est.real - est_old.real)* (est.real - est_old.real) )/np.sum(est.real)*100 )
1453
-
1438
+
1454
1439
  if error_old != 0:
1455
1440
  de = error_new / error_old * 1.0
1456
1441
  else:
@@ -1466,10 +1451,10 @@ def decon_lr(o_image, probe, verbose=False):
1466
1451
  print('terminate')
1467
1452
  progress.update(1)
1468
1453
  progress.write(f"converged in {i} iterations")
1469
- # progress.close()
1470
1454
  print('\n Lucy-Richardson deconvolution converged in ' + str(i) + ' iterations')
1471
- est2 = np.real(fftpack.ifft2(fftpack.fft2(est) * fftpack.fftshift(aperture)))
1455
+ est2 = np.real(fftpack.ifft2(fftpack.fft2(est) * fftpack.fftshift(aperture)))*image_mult
1472
1456
  out_dataset = o_image.like_data(est2)
1473
1457
  out_dataset.title = 'Lucy Richardson deconvolution'
1474
1458
  out_dataset.data_type = 'image'
1475
1459
  return out_dataset
1460
+
pyTEMlib/probe_tools.py CHANGED
@@ -107,6 +107,7 @@ def get_chi(ab, size_x, size_y, verbose=False):
107
107
  return chi, aperture
108
108
 
109
109
 
110
+
110
111
  def print_aberrations(ab):
111
112
  from IPython.display import HTML, display
112
113
  output = '<html><body>'
@@ -133,6 +134,203 @@ def print_aberrations(ab):
133
134
  display(HTML(output))
134
135
 
135
136
 
137
+
138
+ def print_aberrations_polar(ab):
139
+ from IPython.display import HTML, display
140
+
141
+ ab['C12_r'], ab['C12_phi'] = cart2pol(ab['C12a'], ab['C12b'])
142
+ ab['C21_r'], ab['C21_phi'] = cart2pol(ab['C21a'], ab['C21b'])
143
+ ab['C23_r'], ab['C23_phi'] = cart2pol(ab['C23a'], ab['C23b'])
144
+ ab['C32_r'], ab['C32_phi'] = cart2pol(ab['C32a'], ab['C32b'])
145
+ ab['C34_r'], ab['C34_phi'] = cart2pol(ab['C34a'], ab['C34b'])
146
+ ab['C41_r'], ab['C41_phi'] = cart2pol(ab['C41a'], ab['C41b'])
147
+ ab['C43_r'], ab['C43_phi'] = cart2pol(ab['C43a'], ab['C43b'])
148
+ ab['C45_r'], ab['C45_phi'] = cart2pol(ab['C45a'], ab['C45b'])
149
+ ab['C52_r'], ab['C52_phi'] = cart2pol(ab['C52a'], ab['C52b'])
150
+ ab['C54_r'], ab['C54_phi'] = cart2pol(ab['C54a'], ab['C54b'])
151
+ ab['C56_r'], ab['C56_phi'] = cart2pol(ab['C56a'], ab['C56b'])
152
+
153
+ output = '<html><body>'
154
+ output += f"Aberrations [nm] for acceleration voltage: {ab['acceleration_voltage'] / 1e3:.0f} kV"
155
+ output += '<table>'
156
+ output += f"<tr><td> C10 </td><td> {ab['C10']:.1f} </tr>"
157
+ output += f"<tr><td> C12(A1): r </td><td> {ab['C12_r']:20.1f} <td> φ </td><td> {ab['C12_phi']:20.1f} </tr>"
158
+ output += f"<tr><td> C21a (B2): r</td><td> {ab['C21_r']:20.1f} <td> φ </td><td> {ab['C21_phi']:20.1f} "
159
+ output += f" <td> C23a (A2) </td><td> {ab['C23_r']:20.1f} <td> φ </td><td> {ab['C23_phi']:20.1f} </tr>"
160
+ output += f"<tr><td> C30 </td><td> {ab['C30']:.1f} </tr>"
161
+ output += f"<tr><td> C32 (S3) </td><td> {ab['C32_r']:20.1f} <td> φ </td><td> {ab['C32_phi']:20.1f} "
162
+ output += f"<td> C34a (A3) </td><td> {ab['C34a']:20.1f} <td> φ </td><td> {ab['C34_phi']:20.1f} </tr>"
163
+ output += f"<tr><td> C41 (B4) </td><td> {ab['C41_r']:.3g} <td> φ </td><td> {ab['C41_phi']:20.1f} "
164
+ output += f" <td> C43 (D4) </td><td> {ab['C43_r']:.3g} <td> φ (D4) </td><td> {ab['C43_phi']:20.1f} "
165
+ output += f" <td> C45 (A4) </td><td> {ab['C45_r']:.3g} <td> φ (A4)</td><td> {ab['C45_phi']:20.1f} </tr>"
166
+ output += f"<tr><td> C50 </td><td> {ab['C50']:.3g} </tr>"
167
+ output += f"<tr><td> C52 </td><td> {ab['C52a']:.3g} <td> φ </td><td> {ab['C52_phi']:20.1f} "
168
+ output += f"<td> C54 </td><td> {ab['C54_r']:.3g} <td> C54 φ </td><td> {ab['C54_phi']:.1f} "
169
+ output += f"<td> C56 </td><td> {ab['C56_r']:.3g} <td> C56 </td><td> {ab['C56_phi']:.1f} </tr>"
170
+ output += f"<tr><td> Cc </td><td> {ab['Cc']:.3g} </tr>"
171
+
172
+ output += '</table></body></html>'
173
+
174
+ display(HTML(output))
175
+
176
+
177
+ def pol2cart(rho, theta):
178
+ x = rho * np.cos(theta)
179
+ y = rho * np.sin(theta)
180
+ return x, y
181
+
182
+ def ceos_to_nion(ab):
183
+ aberrations = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 0, 'C21b': 0, 'C23a': 0, 'C23b': 0, 'C30': 0.,
184
+ 'C32a': 0., 'C32b': -0., 'C34a': 0., 'C34b': 0., 'C41a': 0., 'C41b': -0., 'C43a': 0.,
185
+ 'C43b': -0., 'C45a': -0., 'C45b': -0., 'C50': 0., 'C52a': -0., 'C52b': 0.,
186
+ 'C54a': -0., 'C54b': -0., 'C56a': -0., 'C56b': 0., 'C70': 0.}
187
+ aberrations['acceleration_voltage'] = 200000
188
+ for key in ab.keys():
189
+ if key == 'C1':
190
+ aberrations['C10'] = ab['C10']
191
+ elif key == 'A1-a':
192
+ x, y = pol2cart(ab['A1-a'], ab['A1-p'])
193
+ aberrations['C12a'] = x
194
+ aberrations['C12b'] = y
195
+ elif key == 'B2-a':
196
+ x, y = pol2cart(ab['B2-a'], ab['B2-p'])
197
+ aberrations['C21a'] = 3 * x
198
+ aberrations['C21b'] = 3 * y
199
+ elif key == 'A2-a':
200
+ x, y = pol2cart(ab['A2-a'], ab['A2-p'])
201
+ aberrations['C23a'] = x
202
+ aberrations['C23b'] = y
203
+ elif key == 'C3':
204
+ aberrations['C30'] = ab['C3']
205
+ elif key == 'S3-a':
206
+ x, y = pol2cart(ab['S3-a'], ab['S3-p'])
207
+ aberrations['C32a'] = 4 * x
208
+ aberrations['C32b'] = 4 * y
209
+ elif key == 'A3-a':
210
+ x, y = pol2cart(ab['A3-a'], ab['A3-p'])
211
+ aberrations['C34a'] = x
212
+ aberrations['C34b'] = y
213
+ elif key == 'B4-a':
214
+ x, y = pol2cart(ab['B4-a'], ab['B4-p'])
215
+ aberrations['C41a'] = 4 * x
216
+ aberrations['C41b'] = 4 * y
217
+ elif key == 'D4-a':
218
+ x, y = pol2cart(ab['D4-a'], ab['D4-p'])
219
+ aberrations['C43a'] = 4 * x
220
+ aberrations['C43b'] = 4 * y
221
+ elif key == 'A4-a':
222
+ x, y = pol2cart(ab['A4-a'], ab['A4-p'])
223
+ aberrations['C45a'] = x
224
+ aberrations['C45b'] = y
225
+ elif key == 'C5':
226
+ aberrations['C50'] = ab['C5']
227
+ elif key == 'A5-a':
228
+ x, y = pol2cart(ab['A5-a'], ab['A5-p'])
229
+ aberrations['C56a'] = x
230
+ aberrations['C56b'] = y
231
+ return aberrations
232
+
233
+ def ceos_carth_to_nion(ab):
234
+ aberrations = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 0, 'C21b': 0, 'C23a': 0, 'C23b': 0, 'C30': 0.,
235
+ 'C32a': 0., 'C32b': -0., 'C34a': 0., 'C34b': 0., 'C41a': 0., 'C41b': -0., 'C43a': 0.,
236
+ 'C43b': -0., 'C45a': -0., 'C45b': -0., 'C50': 0., 'C52a': -0., 'C52b': 0.,
237
+ 'C54a': -0., 'C54b': -0., 'C56a': -0., 'C56b': 0., 'C70': 0.}
238
+ aberrations['acceleration_voltage'] = 200000
239
+ for key in ab.keys():
240
+ if key == 'C1':
241
+ aberrations['C10'] = ab['C1'][0]*1e9
242
+ elif key == 'A1':
243
+ aberrations['C12a'] = ab['A1'][0]*1e9
244
+ aberrations['C12b'] = ab['A1'][1]*1e9
245
+ elif key == 'B2':
246
+ print('B2', ab['B2'])
247
+ aberrations['C21a'] = 3 * ab['B2'][0]*1e9
248
+ aberrations['C21b'] = 3 * ab['B2'][1]*1e9
249
+ elif key == 'A2':
250
+ aberrations['C23a'] = ab['A2'][0]*1e9
251
+ aberrations['C23b'] = ab['A2'][1]*1e9
252
+ elif key == 'C3':
253
+ aberrations['C30'] = ab['C3'][0]*1e9
254
+ elif key == 'S3':
255
+ aberrations['C32a'] = 4 * ab['S3'][0]*1e9
256
+ aberrations['C32b'] = 4 * ab['S3'][1]*1e9
257
+ elif key == 'A3':
258
+ aberrations['C34a'] = ab['A3'][0]*1e9
259
+ aberrations['C34b'] = ab['A3'][1]*1e9
260
+ elif key == 'B4':
261
+ aberrations['C41a'] = 4 * ab['B4'][0]*1e9
262
+ aberrations['C41b'] = 4 * ab['B4'][1]*1e9
263
+ elif key == 'D4':
264
+ aberrations['C43a'] = 4 * ab['D4'][0]*1e9
265
+ aberrations['C43b'] = 4 * ab['D4'][1]*1e9
266
+ elif key == 'A4':
267
+ aberrations['C45a'] = ab['A4'][0]*1e9
268
+ aberrations['C45b'] = ab['A4'][1]*1e9
269
+ elif key == 'C5':
270
+ aberrations['C50'] = ab['C5'][0]*1e9
271
+ elif key == 'A5':
272
+ aberrations['C56a'] = ab['A5'][0]*1e9
273
+ aberrations['C56b'] = ab['A5'][1]*1e9
274
+
275
+
276
+ return aberrations
277
+
278
+ def cart2pol(x, y):
279
+ theta = np.arctan2(y, x)
280
+ rho = np.hypot(x, y)
281
+ return theta, rho
282
+
283
+ def nion_to_ceos(ab):
284
+ aberrations = {'C1': 0, 'A1-a': 0, 'A1-b': 0, 'B2-a': 0, 'B2-p': 0, 'A2-a': 0, 'A2-p': 0, 'C3': 0.,
285
+ 'S3-a': 0., 'S3-p': -0., 'A3-a': 0., 'A3-p': 0., 'B4-a': 0., 'B4-p': -0., 'D4-a': 0.,
286
+ 'D4-p': -0., 'A4-s': -0., 'A4-p': -0., 'C5': 0., 'A5-a': -0., 'A5-p': 0.}
287
+ aberrations['acceleration_voltage'] = 200000
288
+ for key in ab.keys():
289
+ if key == 'C10':
290
+ aberrations['C1'] = ab['C10']
291
+ elif key == 'C12a':
292
+ r, p = cart2pol(ab['C12a'], ab['C12b'])
293
+ aberrations['A1-a'] = r
294
+ aberrations['A1-p'] = p
295
+ elif key == 'C21a':
296
+ r, p = cart2pol(ab['C21a'], ab['C21b'])
297
+ aberrations['B2-a'] = r/3
298
+ aberrations['B2-p'] = p
299
+ elif key == 'C23a':
300
+ r, p = cart2pol(ab['C23a'], ab['C23b'])
301
+ aberrations['A2-a'] = r
302
+ aberrations['A2-p'] = p
303
+ elif key == 'C30':
304
+ aberrations['C3'] = ab['C30']
305
+ elif key == 'C32a':
306
+ r, p = cart2pol(ab['C32a'], ab['C32b'])
307
+ aberrations['S3-a'] = r/4
308
+ aberrations['S3-p'] = p
309
+ elif key == 'C34a':
310
+ r, p = cart2pol(ab['C34a'], ab['C34b'])
311
+ aberrations['A3-a'] = r
312
+ aberrations['A3-p'] = p
313
+ elif key == 'C41a':
314
+ r, p = cart2pol(ab['C41a'], ab['C41b'])
315
+ aberrations['B4-a'] = r/4
316
+ aberrations['B4-p'] = p
317
+ elif key == 'C43a':
318
+ r, p = cart2pol(ab['C43a'], ab['C43b'])
319
+ aberrations['D4-a'] = r/4
320
+ aberrations['D4-p'] = p
321
+ elif key == 'C31a':
322
+ r, p = cart2pol(ab['C41a'], ab['C41b'])
323
+ aberrations['A4-a'] = r
324
+ aberrations['A4-p'] = p
325
+ elif key == 'C50':
326
+ aberrations['C5'] = ab['C50']
327
+ elif key == 'C56a':
328
+ r, p = cart2pol(ab['C56a'], ab['C56b'])
329
+ aberrations['A5-a'] = r
330
+ aberrations['A5-p'] = p
331
+
332
+ return aberrations
333
+
136
334
  def get_ronchigram(size, ab, scale='mrad'):
137
335
  """ Get Ronchigram
138
336
 
@@ -447,15 +645,13 @@ def get_target_aberrations(TEM_name, acceleration_voltage):
447
645
  'C30': 123,
448
646
  'C32a': 95.3047364258614, 'C32b': -189.72105710231244, 'C34a': -47.45099594807912, 'C34b': -94.67424667529909,
449
647
  'C41a': -905.31842572806, 'C41b': 981.316128853203, 'C43a': 4021.8433526960034, 'C43b': 131.72716642732158,
450
- 'C45a': -4702.390968272048, 'C45b': -208.25028574642903, 'C50': -663.1,
451
- 'C50': 552000., 'C52a': -0., 'C52b': 0.,
648
+ 'C45a': -4702.390968272048, 'C45b': -208.25028574642903, 'C50': 552000., 'C52a': -0., 'C52b': 0.,
452
649
  'C54a': -0., 'C54b': -0., 'C56a': -36663.643489934424, 'C56b': 21356.079837905396,
453
650
  'acceleration_voltage': 200000,
454
651
  'FOV': 34.241659495148205,
455
652
  'Cc': 1* 1e6,
456
653
  'convergence_angle': 30,
457
654
  'wavelength': 0.0025079340450548005}
458
-
459
655
  return ab
460
656
 
461
657
 
@@ -488,7 +684,7 @@ def get_ronchigram_2(size, ab, scale='mrad', threshold=3):
488
684
  aperture[mask] = 0.
489
685
 
490
686
  v_noise = np.random.rand(size_x, size_y)
491
- smoothing = 5
687
+ smoothing = 10
492
688
  phi_r = ndimage.gaussian_filter(v_noise, sigma=(smoothing, smoothing), order=0)
493
689
 
494
690
  sigma = 6 # 6 for carbon and thin
pyTEMlib/version.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """
2
2
  version
3
3
  """
4
- _version = '0.2025.02.2'
4
+ _version = '0.2025.04.0'
5
5
  __version__ = _version
6
- _time = '2025-02-25 19:58:26'
6
+ _time = '2025-04-25 19:58:26'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pyTEMlib
3
- Version: 0.2025.2.2
3
+ Version: 0.2025.4.0
4
4
  Summary: pyTEM: TEM Data Quantification library through a model-based approach
5
5
  Home-page: https://pycroscopy.github.io/pyTEMlib/about.html
6
6
  Author: Gerd Duscher
@@ -39,7 +39,7 @@ Requires-Dist: spglib
39
39
  Requires-Dist: scikit-image
40
40
  Requires-Dist: scikit-learn
41
41
  Requires-Dist: pyNSID>=0.0.7
42
- Requires-Dist: sidpy>=0.12.1
42
+ Requires-Dist: sidpy>=0.12.7
43
43
  Requires-Dist: SciFiReaders>=0.0.8
44
44
  Dynamic: author
45
45
  Dynamic: author-email
@@ -48,6 +48,7 @@ Dynamic: description
48
48
  Dynamic: home-page
49
49
  Dynamic: keywords
50
50
  Dynamic: license
51
+ Dynamic: license-file
51
52
  Dynamic: platform
52
53
  Dynamic: requires-dist
53
54
  Dynamic: summary
@@ -1,22 +1,22 @@
1
1
  pyTEMlib/__init__.py,sha256=nEN93amIEoZxO7rJgN71ABeCoXrnmaywBbE97l5lPio,178
2
2
  pyTEMlib/animation.py,sha256=G9ykYo6yB5jexjKienShO9C2b8xEXKbf7t47apwcwTw,25188
3
- pyTEMlib/atom_tools.py,sha256=uoyZOs0lZKyBwGH6onKSAKzA6IxseqHpgw7ZTos9Nd0,7335
3
+ pyTEMlib/atom_tools.py,sha256=iXJbK66k3HbTNOGSWRLeUV01JlggUo1Qq89HJqbSv54,7341
4
4
  pyTEMlib/config_dir.py,sha256=4evlo9P2Yht-AnqaLI-WweLjDQcislbAP3I7P7EZsPU,2085
5
- pyTEMlib/core_loss_widget.py,sha256=EbCn2imCjzeLAL2h9n87APHPou1G4vvN-kA4_RQs_OE,30991
5
+ pyTEMlib/core_loss_widget.py,sha256=XQk071ZHMGYOM6RTSIaXQlZtnFQjUyxTWbQ2ZXOFMfU,30901
6
6
  pyTEMlib/crystal_tools.py,sha256=g4OXyvd5NLw7vaXhjDP3P6VZpVV6eiyuPn8MdgR2amI,61652
7
7
  pyTEMlib/diffraction_plot.py,sha256=pM5d3bdBGa8LlPZ5lw8sLT94mlYTXELxPLv-jUP2FWY,27959
8
8
  pyTEMlib/dynamic_scattering.py,sha256=O9MxnxfndWJ2VhQRjksKNQ4yY7y-gN_hitRQ4Qox4ns,9472
9
9
  pyTEMlib/eds_tools.py,sha256=Ilof2Cars-1ILXx5g2RsU2G4BgrPwjOHgQ7-OabmbrU,28000
10
- pyTEMlib/eels_dialog.py,sha256=NmPjO1SVjxzah2cCEK0bR1AqwX7Dl6xwejTZkruqIUA,32619
10
+ pyTEMlib/eels_dialog.py,sha256=QG_PU3uuzus_3I3zjfaxb2a9iYq8B053zYw-B52JklM,32595
11
11
  pyTEMlib/eels_dialog_utilities.py,sha256=73W9jFbPx-eeLEiSaBptTgGLr40bIYYfSyzLnZbhfvo,51761
12
- pyTEMlib/eels_tools.py,sha256=7gfDmu-CqfatVmtCPYwObJZsP5RMDx3bmFXclZzJINE,88739
13
- pyTEMlib/file_tools.py,sha256=gdP42-99Pjnl0tri50x8QXc33ye-1bs6Yiof3JKVn7I,59969
12
+ pyTEMlib/eels_tools.py,sha256=pl75ZDQHz1JngxD84T_595Kfrl5TEJSETPGgkXfBtrA,88688
13
+ pyTEMlib/file_tools.py,sha256=AIeWD3JaB1OI32_pj_RSqQuRGjPM-wiD0zTbL5W17Tc,64559
14
14
  pyTEMlib/file_tools_qt.py,sha256=tLZACS4JyGH_AOzNR_SGAhjA01y4VJB261opPhGMlm8,7223
15
- pyTEMlib/graph_tools.py,sha256=iu0Y2hIPU6CkQHQEh-dI1vKnUHnSNXx4-CXs2M-1Sr8,44097
15
+ pyTEMlib/graph_tools.py,sha256=VWuTgFGeu4gn4cfRgf-76kO6u2B1ZV_dz6gLfx2k4NY,46570
16
16
  pyTEMlib/graph_viz.py,sha256=m5PwSn6l2r0bsaLWBDSHc9IGR3_PneG2BrZgnEdi07I,13644
17
17
  pyTEMlib/image_dialog.py,sha256=F-ZgKq7UnMtPPd1b9eqb7t8MXDfWN-8hVKwB2Il0x28,6235
18
18
  pyTEMlib/image_dlg.py,sha256=n5gradDiYOFGEQ3k_Wlk9RUYYzl4bl_hKLzNVcYteNE,5694
19
- pyTEMlib/image_tools.py,sha256=guHd29VLo2z0KXz1-XxcHYWqOiHGoKZ0A9SE_FdSO2g,52655
19
+ pyTEMlib/image_tools.py,sha256=iBVsAr6-oQY6y2LTSbUIz6nMeAIOsSX5zPBezekJsFg,52334
20
20
  pyTEMlib/info_widget.py,sha256=lkzQOuNVlkaasiZDtc5UtYk541-plYNfnW4DQwQB_iA,53467
21
21
  pyTEMlib/info_widget3.py,sha256=QSbdSj6m57KQTir2fNhulVgjOu9EQL31c-9SzTlghnE,55495
22
22
  pyTEMlib/interactive_image.py,sha256=5PwypcA1OjLAD-fi8bmWWFHuOjdIPVY9Dh59V24WuDA,34
@@ -25,14 +25,14 @@ pyTEMlib/low_loss_widget.py,sha256=0SxHOAuUnuNjDrJSNS2PeWD6hjyuB5FCYxmVfsDTq5c,2
25
25
  pyTEMlib/microscope.py,sha256=iigUF1UImHEfmL2wqEBBj3aNRgEYouDbIln8VCo4_KM,1545
26
26
  pyTEMlib/peak_dialog.py,sha256=r3mhYvECC9niFFItS1oGa96F2nFthsE5kfpA3yVXQaE,51872
27
27
  pyTEMlib/peak_dlg.py,sha256=qcjcnhwpGa4jBCeXzwQz9sCyX-tHsLLQ67ToqfKOiQY,11550
28
- pyTEMlib/probe_tools.py,sha256=xVwoThZSSOY6hjUMO53va9R_qI7SL5dfwIigz6sd3DI,29242
28
+ pyTEMlib/probe_tools.py,sha256=sDW9CW3SMwjvSHYcEufceismHv_LVkqxcS-gCtEklCg,37926
29
29
  pyTEMlib/sidpy_tools.py,sha256=0oIx-qMtEmcZmLazQKW19dd-KoxyY3B15aIeMcyHA8E,4878
30
30
  pyTEMlib/simulation_tools.py,sha256=RmegD5TpQMU68uASvzZWVplAqs7bM5KkF6bWDWLjyc0,2799
31
- pyTEMlib/version.py,sha256=6GC1KyGj1D_9Hs8_3OJrq7lHdhziwk9qllcxLoWcEns,94
31
+ pyTEMlib/version.py,sha256=znL_4Pt7sBR4l1I39FM04SGxnneUfbZrv9R_6tFaynA,94
32
32
  pyTEMlib/xrpa_x_sections.py,sha256=m4gaH7gaJiNi-CsIT9aKoH4fB6MQIAe876kxEmzSebI,1825392
33
- pytemlib-0.2025.2.2.dist-info/LICENSE,sha256=7HdBF6SXIBd38bHOKkQd4DYR1KV-OYm9mwB16fM-984,1062
34
- pytemlib-0.2025.2.2.dist-info/METADATA,sha256=tOVUZqAPzyfc6ZXDHRyz5jp6OGzNJvv9CcyE-1bxtxI,3493
35
- pytemlib-0.2025.2.2.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
36
- pytemlib-0.2025.2.2.dist-info/entry_points.txt,sha256=zn2yO1IWTutI3c7C9e3GdARCvm43JURoOhqQ8YylV4Y,43
37
- pytemlib-0.2025.2.2.dist-info/top_level.txt,sha256=rPLVH0UJxrPSPgSoKScTjL1K_X69JFzsYYnDnYTYIlU,9
38
- pytemlib-0.2025.2.2.dist-info/RECORD,,
33
+ pytemlib-0.2025.4.0.dist-info/licenses/LICENSE,sha256=7HdBF6SXIBd38bHOKkQd4DYR1KV-OYm9mwB16fM-984,1062
34
+ pytemlib-0.2025.4.0.dist-info/METADATA,sha256=mV1ni5hE1q19Z8qOzNfWGb9YAOy0rv69JZd3J1e14MM,3515
35
+ pytemlib-0.2025.4.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
36
+ pytemlib-0.2025.4.0.dist-info/entry_points.txt,sha256=zn2yO1IWTutI3c7C9e3GdARCvm43JURoOhqQ8YylV4Y,43
37
+ pytemlib-0.2025.4.0.dist-info/top_level.txt,sha256=rPLVH0UJxrPSPgSoKScTjL1K_X69JFzsYYnDnYTYIlU,9
38
+ pytemlib-0.2025.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.1)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5