pyTEMlib 0.2025.3.0__py3-none-any.whl → 0.2025.4.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.

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
 
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
 
@@ -875,7 +878,6 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
875
878
  else:
876
879
  if isinstance(datasets, dict):
877
880
  dataset_dict = datasets
878
-
879
881
  else:
880
882
  dataset_dict = {}
881
883
  for index, dataset in enumerate(datasets):
@@ -917,23 +919,28 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
917
919
  dset.title = dset.title + '_SI'
918
920
  dset = dset.T
919
921
  dset.title = dset.title[11:]
922
+ dset.add_provenance('pyTEMlib', 'open_file', version=pyTEMlib.__version__, linked_data='emi_converted_by_hyperspy')
920
923
  dataset_dict[f'Channel_{index:03d}'] = dset
924
+
921
925
  return dataset_dict
922
926
  except ImportError:
923
927
  print('This file type needs hyperspy to be installed to be able to be read')
924
928
  return
925
929
  elif extension == '.emd':
926
930
  reader = SciFiReaders.EMDReader(filename, sum_frames=sum_frames)
927
-
931
+ provenance = 'SciFiReader.EMDReader'
928
932
  elif 'edax' in extension.lower():
929
933
  if 'h5' in extension:
930
934
  reader = SciFiReaders.EDAXReader(filename)
935
+ provenance = 'SciFiReader.EDAXReader'
931
936
 
932
937
  elif extension in ['.ndata', '.h5']:
933
938
  reader = SciFiReaders.NionReader(filename)
934
-
939
+ provenance = 'SciFiReader.NionReader'
940
+
935
941
  elif extension in ['.mrc']:
936
942
  reader = SciFiReaders.MRCReader(filename)
943
+ provenance = 'SciFiReader.MRCReader'
937
944
 
938
945
  else:
939
946
  raise NotImplementedError('extension not supported')
@@ -985,6 +992,7 @@ def open_file(filename=None, h5_group=None, write_hdf_file=False, sum_frames=Fa
985
992
  if isinstance(dset, dict):
986
993
  dataset_dict = dset
987
994
  for dataset in dataset_dict.values():
995
+ dataset.add_provenance('pyTEMlib', 'open_file', version=pyTEMlib.__version__, linked_data = 'SciFiReader')
988
996
  dataset.metadata['filename'] = filename
989
997
 
990
998
  elif isinstance(dset, list):
@@ -1422,6 +1430,123 @@ def h5_get_crystal_structure(structure_group):
1422
1430
  # ToDo: Read all of info dictionary
1423
1431
  return atoms
1424
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
+
1461
+ if 'Detectors' in metadata_dict['Metadata']:
1462
+ if 'ScanningDetector' in metadata_dict['Metadata']['Detectors']:
1463
+ detector = metadata_dict['Metadata']['Detectors']['ScanningDetector']['DetectorName']
1464
+ elif 'ImagingDetector' in metadata_dict['Metadata']['Detectors']:
1465
+ detector = metadata_dict['Metadata']['Detectors']['ImagingDetector']['DetectorName']
1466
+ segment = ''
1467
+ if 'CustomPropertyGroup' in metadata_dict['Metadata']:
1468
+ if 'CustomProperties' in metadata_dict['Metadata']['CustomPropertyGroup']:
1469
+ for list_item in metadata_dict['Metadata']['CustomPropertyGroup']['CustomProperties']:
1470
+
1471
+ if isinstance(list_item, dict):
1472
+ for key in list_item:
1473
+ for item in list_item[key]:
1474
+ if '@name' in item:
1475
+ if item['@name']== 'DetectorCommercialName':
1476
+ detector = item['@value']
1477
+ if item['@name']== 'StemSegment':
1478
+ segment = '_'+item['@value']
1479
+ return detector+segment, metadata_dict['Metadata']
1480
+
1481
+
1482
+ def get_metadata_from_adorned(ds):
1483
+ ds.metadata['experiment']= {}
1484
+ if 'Optics' in ds.original_metadata:
1485
+ if 'LastMeasuredScreenCurrent' in ds.original_metadata['Optics']:
1486
+ ds.metadata['experiment']['current'] = float(ds.original_metadata['Optics']['LastMeasuredScreenCurrent'])
1487
+ if 'ConvergenceAngle' in ds.original_metadata['Optics']:
1488
+ ds.metadata['experiment']['convergence_angle'] = float(ds.original_metadata['Optics']['ConvergenceAngle'])
1489
+ if 'AccelerationVoltage' in ds.original_metadata['Optics']:
1490
+ ds.metadata['experiment']['acceleration_voltage'] = float(ds.original_metadata['Optics']['AccelerationVoltage'])
1491
+ if 'SpotIndex' in ds.original_metadata['Optics']:
1492
+ ds.metadata['experiment']['spot_size'] = ds.original_metadata['Optics']['SpotIndex']
1493
+ if' StagesSettings' in ds.original_metadata:
1494
+ if 'StagePosition' in ds.original_metadata['StagesSettings']:
1495
+ ds.metadata['experiment']['stage_position'] = ds.original_metadata['StagesSettings']['StagePosition']
1496
+ if 'Detectors' in ds.original_metadata:
1497
+ if 'ScanningDetector' in ds.original_metadata['Detectors']:
1498
+ ds.metadata['experiment']['detector'] = ds.original_metadata['Detectors']['ScanningDetector']['DetectorName']
1499
+ elif 'ImagingDetector' in ds.original_metadata['Detectors']:
1500
+ ds.metadata['experiment']['detector'] = ds.original_metadata['Detectors']['ImagingDetector']['DetectorName']
1501
+ ds.metadata['experiment']['exposure_time'] = ds.original_metadata['Detectors']['ImagingDetector']['ExposureTime']
1502
+
1503
+
1504
+ def adorned_to_sidpy(images):
1505
+ """
1506
+ Convert a list of adorned images to a dictionary of Sidpy datasets.
1507
+ Each dataset is created from the image data and adorned metadata.
1508
+ The datasets are stored in a dictionary with keys 'Channel_000', 'Channel_001', etc.
1509
+ The dimensions of the datasets are set based on the image data shape and pixel sizes.
1510
+ The original metadata is also stored in the dataset.
1511
+ Args:
1512
+ images (list or object): A list of adorned images or a single adorned image.
1513
+ Returns:
1514
+ dict: A dictionary of Sidpy datasets, where each dataset corresponds to an image.
1515
+ """
1516
+
1517
+ data_sets = {}
1518
+ if not isinstance(images, list):
1519
+ images = [images]
1520
+ for index, image in enumerate(images):
1521
+ name, original_metadata = read_adorned_metadata(image)
1522
+ data_sets[f'Channel_{index:03}'] = sidpy.Dataset.from_array(image.data.T, title=name)
1523
+ ds = data_sets[f'Channel_{index:03}']
1524
+
1525
+
1526
+ ds.original_metadata = original_metadata
1527
+
1528
+ pixel_size_x_m = float(ds.original_metadata['BinaryResult']['PixelSize']['X']['#text'])
1529
+ pixel_size_y_m = float(ds.original_metadata['BinaryResult']['PixelSize']['Y']['#text'])
1530
+ pixel_size_x_nm = pixel_size_x_m * 1e9
1531
+ pixel_size_y_nm = pixel_size_y_m * 1e9
1532
+ if image.data.ndim == 3:
1533
+ ds.data_type = 'image_stack'
1534
+ ds.set_dimension(0, sidpy.Dimension(np.arange(image.data.shape[0]),
1535
+ name='frame', units='frame', quantity='Length', dimension_type='temporal'))
1536
+ ds.set_dimension(1, sidpy.Dimension(np.arange(image.data.shape[1]) * pixel_size_y_nm,
1537
+ name='y', units='nm', quantity='Length', dimension_type='spatial'))
1538
+ ds.set_dimension(2, sidpy.Dimension(np.arange(image.data.shape[2]) * pixel_size_x_nm,
1539
+ name='x', units='nm', quantity='Length', dimension_type='spatial'))
1540
+ else:
1541
+ ds.data_type = 'image'
1542
+ ds.set_dimension(0, sidpy.Dimension(np.arange(image.data.shape[0]) * pixel_size_y_nm,
1543
+ name='y', units='nm', quantity='Length', dimension_type='spatial'))
1544
+ ds.set_dimension(1, sidpy.Dimension(np.arange(image.data.shape[1]) * pixel_size_x_nm,
1545
+ name='x', units='nm', quantity='Length', dimension_type='spatial'))
1546
+
1547
+ get_metadata_from_adorned(ds)
1548
+ return data_sets
1549
+
1425
1550
 
1426
1551
  ###############################################
1427
1552
  # Support old pyTEM file format
pyTEMlib/graph_tools.py CHANGED
@@ -808,15 +808,18 @@ def breadth_first_search2(graph, initial, projected_crystal):
808
808
  a_lattice_vector = projection_tags['lattice_vector']['a']
809
809
  b_lattice_vector = projection_tags['lattice_vector']['b']
810
810
  main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
811
- near = main
811
+ near = []
812
812
  else:
813
813
  # get lattice vectors to hopp along through graph
814
814
  projected_unit_cell = projected_crystal.cell[:2, :2]
815
815
  a_lattice_vector = projected_unit_cell[0]
816
816
  b_lattice_vector = projected_unit_cell[1]
817
817
  main = np.array([a_lattice_vector, -a_lattice_vector, b_lattice_vector, -b_lattice_vector]) # vectors of unit cell
818
- near = np.append(main, projection_tags['near_base'], axis=0) # all nearest atoms
818
+ near = projection_tags['near_base'] # all nearest atoms
819
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
+
820
823
  neighbour_tree = scipy.spatial.KDTree(graph)
821
824
  distances, indices = neighbour_tree.query(graph, # let's get all neighbours
822
825
  k=50) # projection_tags['number_of_nearest_neighbours']*2 + 1)
@@ -852,10 +855,17 @@ def breadth_first_search2(graph, initial, projected_crystal):
852
855
 
853
856
 
854
857
 
855
- def breath_first_search(graph, initial, lattice_parameter, tolerance=1):
858
+ def breadth_first_search_felxible(graph, initial, lattice_parameter, tolerance=1):
856
859
  """ breadth first search of atoms viewed as a graph
857
- we only
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.
858
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
+
859
869
  neighbour_tree = scipy.spatial.KDTree(graph)
860
870
  distances, indices = neighbour_tree.query(graph, # let's get all neighbours
861
871
  k=50) # projection_tags['number_of_nearest_neighbours']*2 + 1)
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))
@@ -229,7 +226,6 @@ def fourier_transform(dset: sidpy.Dataset) -> sidpy.Dataset:
229
226
 
230
227
  fft_dset.set_dimension(0, sidpy.Dimension(np.fft.fftshift(np.fft.fftfreq(new_image.shape[0],
231
228
  d=dset.x[1]-dset.x[0])),
232
-
233
229
  name='u', units=units_x, dimension_type='RECIPROCAL',
234
230
  quantity='reciprocal_length'))
235
231
  fft_dset.set_dimension(1, sidpy.Dimension(np.fft.fftshift(np.fft.fftfreq(new_image.shape[1],
@@ -599,13 +595,13 @@ def demon_registration(dataset, verbose=False):
599
595
  demon_registered.title = 'Non-Rigid Registration'
600
596
  demon_registered.source = dataset.title
601
597
 
602
- demon_registered.metadata = {'analysis': 'non-rigid demon registration'}
603
- demon_registered.metadata['experiment'] = dataset.metadata['experiment'].copy()
604
- if 'input_crop' in dataset.metadata:
605
- demon_registered.metadata['input_crop'] = dataset.metadata['input_crop']
606
- if 'input_shape' in dataset.metadata:
607
- demon_registered.metadata['input_shape'] = dataset.metadata['input_shape']
608
- 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}
609
605
  demon_registered.data_type = 'IMAGE_STACK'
610
606
  return demon_registered
611
607
 
@@ -613,7 +609,7 @@ def demon_registration(dataset, verbose=False):
613
609
  ###############################
614
610
  # Rigid Registration New 05/09/2020
615
611
 
616
- def rigid_registration(dataset, sub_pixel=True):
612
+ def rigid_registration(dataset, normalization=None):
617
613
  """
618
614
  Rigid registration of image stack with pixel accuracy
619
615
 
@@ -635,51 +631,32 @@ def rigid_registration(dataset, sub_pixel=True):
635
631
  raise TypeError('We need a sidpy.Dataset')
636
632
  if dataset.data_type.name != 'IMAGE_STACK':
637
633
  raise TypeError('Registration makes only sense for an image stack')
638
-
639
- frame_dim = []
640
- spatial_dim = []
641
- selection = []
642
-
643
- for i, axis in dataset._axes.items():
644
- if axis.dimension_type.name == 'SPATIAL':
645
- spatial_dim.append(i)
646
- selection.append(slice(None))
647
- else:
648
- frame_dim.append(i)
649
- selection.append(slice(0, 1))
650
-
651
- if len(spatial_dim) != 2:
652
- print('need two spatial dimensions')
653
- if len(frame_dim) != 1:
654
- print('need one frame dimensions')
655
-
656
- nopix = dataset.shape[spatial_dim[0]]
657
- nopiy = dataset.shape[spatial_dim[1]]
658
- nimages = dataset.shape[frame_dim[0]]
659
-
660
- print('Stack contains ', nimages, ' images, each with', nopix, ' pixels in x-direction and ', nopiy,
661
- ' pixels in y-direction')
662
-
663
- fixed = dataset[tuple(selection)].squeeze().compute()
664
- 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')
665
648
 
666
649
  relative_drift = [[0., 0.]]
667
-
668
- for i in trange(nimages):
669
- selection[frame_dim[0]] = slice(i, i+1)
670
- moving = dataset[tuple(selection)].squeeze().compute()
671
- fft_moving = np.fft.fft2(moving)
672
- if sub_pixel:
673
- shift = skimage.registration.phase_cross_correlation(fft_fixed, fft_moving, upsample_factor=1000,
674
- space='fourier')[0]
675
- else:
676
- image_product = fft_fixed * fft_moving.conj()
677
- cc_image = np.fft.fftshift(np.fft.ifft2(image_product))
678
- shift = np.array(ndimage.maximum_position(cc_image.real))-cc_image.shape[0]/2
679
- fft_fixed = fft_moving
680
- relative_drift.append(shift)
681
- rig_reg, drift = rig_reg_drift(dataset, relative_drift)
682
- 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)
683
660
 
684
661
  rigid_registered = sidpy.Dataset.from_array(crop_reg,
685
662
  title='Rigid Registration',
@@ -688,21 +665,21 @@ def rigid_registration(dataset, sub_pixel=True):
688
665
  units=dataset.units)
689
666
  rigid_registered.title = 'Rigid_Registration'
690
667
  rigid_registered.source = dataset.title
691
- rigid_registered.metadata = {'analysis': 'rigid sub-pixel registration', 'drift': drift,
692
- '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:]}}
693
670
  rigid_registered.metadata['experiment'] = dataset.metadata['experiment'].copy()
694
671
  rigid_registered.set_dimension(0, sidpy.Dimension(np.arange(rigid_registered.shape[0]),
695
672
  name='frame', units='frame', quantity='time',
696
673
  dimension_type='temporal'))
697
674
 
698
- array_x = dataset._axes[spatial_dim[0]][input_crop[0]:input_crop[1]].values
699
- rigid_registered.set_dimension(1, sidpy.Dimension(array_x,
700
- 'x', units='nm', quantity='Length',
701
- dimension_type='spatial'))
702
- array_y = dataset._axes[spatial_dim[1]][input_crop[2]:input_crop[3]].values
703
- rigid_registered.set_dimension(2, sidpy.Dimension(array_y,
704
- 'y', units='nm', quantity='Length',
705
- 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'))
706
683
  rigid_registered.data_type = 'IMAGE_STACK'
707
684
  return rigid_registered.rechunk({0: 'auto', 1: -1, 2: -1})
708
685
 
@@ -778,13 +755,12 @@ def crop_image_stack(rig_reg, drift):
778
755
  -------
779
756
  numpy array
780
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])))
781
762
 
782
- xpmin = int(-np.floor(np.min(np.array(drift)[:, 0])))
783
- xpmax = int(rig_reg.shape[1] - np.ceil(np.max(np.array(drift)[:, 0])))
784
- ypmin = int(-np.floor(np.min(np.array(drift)[:, 1])))
785
- ypmax = int(rig_reg.shape[2] - np.ceil(np.max(np.array(drift)[:, 1])))
786
-
787
- return rig_reg[:, xpmin:xpmax, ypmin:ypmax], [xpmin, xpmax, ypmin, ypmax]
763
+ return rig_reg[:, xpmin:xpmax, ypmin:ypmax:], [xpmin, xpmax, ypmin, ypmax]
788
764
 
789
765
 
790
766
  class ImageWithLineProfile:
@@ -1416,8 +1392,6 @@ def decon_lr(o_image, probe, verbose=False):
1416
1392
 
1417
1393
  response_ft = fftpack.fft2(probe_c)
1418
1394
 
1419
-
1420
-
1421
1395
  ap_angle = o_image.metadata['experiment']['convergence_angle']
1422
1396
  if ap_angle > .1:
1423
1397
  ap_angle /= 1000 # now in rad
@@ -1452,7 +1426,7 @@ def decon_lr(o_image, probe, verbose=False):
1452
1426
  # de = 100
1453
1427
  dest = 100
1454
1428
  i = 0
1455
- while abs(dest) > 0.001: # or abs(de) > .025:
1429
+ while abs(dest) > 0.0001: # or abs(de) > .025:
1456
1430
  i += 1
1457
1431
  error_old = np.sum(error.real)
1458
1432
  est_old = est.copy()
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.03.0'
4
+ _version = '0.2025.04.1'
5
5
  __version__ = _version
6
- _time = '2025-02-25 19:58:26'
6
+ _time = '2025-05-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.3.0
3
+ Version: 0.2025.4.1
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
@@ -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,6 +1,6 @@
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
5
  pyTEMlib/core_loss_widget.py,sha256=XQk071ZHMGYOM6RTSIaXQlZtnFQjUyxTWbQ2ZXOFMfU,30901
6
6
  pyTEMlib/crystal_tools.py,sha256=g4OXyvd5NLw7vaXhjDP3P6VZpVV6eiyuPn8MdgR2amI,61652
@@ -10,13 +10,13 @@ pyTEMlib/eds_tools.py,sha256=Ilof2Cars-1ILXx5g2RsU2G4BgrPwjOHgQ7-OabmbrU,28000
10
10
  pyTEMlib/eels_dialog.py,sha256=QG_PU3uuzus_3I3zjfaxb2a9iYq8B053zYw-B52JklM,32595
11
11
  pyTEMlib/eels_dialog_utilities.py,sha256=73W9jFbPx-eeLEiSaBptTgGLr40bIYYfSyzLnZbhfvo,51761
12
12
  pyTEMlib/eels_tools.py,sha256=pl75ZDQHz1JngxD84T_595Kfrl5TEJSETPGgkXfBtrA,88688
13
- pyTEMlib/file_tools.py,sha256=liFMSpwHDXa0pbm0SAK5JQIhVLKkkGNPbrlC4UY5mQM,59467
13
+ pyTEMlib/file_tools.py,sha256=RniTJrcuiyt7PefE5SSY_z2pTZPTNhFRjb2oVXVa4nE,66373
14
14
  pyTEMlib/file_tools_qt.py,sha256=tLZACS4JyGH_AOzNR_SGAhjA01y4VJB261opPhGMlm8,7223
15
- pyTEMlib/graph_tools.py,sha256=orNt4p765oODyL3oYWaFXjqNkaiocmCsnemc4qaxTbU,45996
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=CyhcwaBbyi2j4DkUaG6dsKajaHl8BM-iPs6M9MmRhms,52896
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=dKkAks0i6sJYBlVk2cRu2SHpr9fJbMu8gav2PVaKOW4,94
31
+ pyTEMlib/version.py,sha256=1mG-HaMBzuYiDf1nig-t9gxjgThQprsDbDaDFGoWGBY,94
32
32
  pyTEMlib/xrpa_x_sections.py,sha256=m4gaH7gaJiNi-CsIT9aKoH4fB6MQIAe876kxEmzSebI,1825392
33
- pytemlib-0.2025.3.0.dist-info/LICENSE,sha256=7HdBF6SXIBd38bHOKkQd4DYR1KV-OYm9mwB16fM-984,1062
34
- pytemlib-0.2025.3.0.dist-info/METADATA,sha256=BqNTNOrFTLzDmoIE5uLAIUnvwDXmtkl2ZPRTK5DnwfE,3493
35
- pytemlib-0.2025.3.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
36
- pytemlib-0.2025.3.0.dist-info/entry_points.txt,sha256=zn2yO1IWTutI3c7C9e3GdARCvm43JURoOhqQ8YylV4Y,43
37
- pytemlib-0.2025.3.0.dist-info/top_level.txt,sha256=rPLVH0UJxrPSPgSoKScTjL1K_X69JFzsYYnDnYTYIlU,9
38
- pytemlib-0.2025.3.0.dist-info/RECORD,,
33
+ pytemlib-0.2025.4.1.dist-info/licenses/LICENSE,sha256=7HdBF6SXIBd38bHOKkQd4DYR1KV-OYm9mwB16fM-984,1062
34
+ pytemlib-0.2025.4.1.dist-info/METADATA,sha256=Cp3K3rOFMT-MZjC2EK0mKXBYU495_9UFUkZlg2yIH9Y,3515
35
+ pytemlib-0.2025.4.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
36
+ pytemlib-0.2025.4.1.dist-info/entry_points.txt,sha256=zn2yO1IWTutI3c7C9e3GdARCvm43JURoOhqQ8YylV4Y,43
37
+ pytemlib-0.2025.4.1.dist-info/top_level.txt,sha256=rPLVH0UJxrPSPgSoKScTjL1K_X69JFzsYYnDnYTYIlU,9
38
+ pytemlib-0.2025.4.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5