pyTEMlib 0.2025.12.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.
Files changed (92) hide show
  1. build/lib/pyTEMlib/__init__.py +36 -0
  2. build/lib/pyTEMlib/animation.py +667 -0
  3. build/lib/pyTEMlib/atom_tools.py +229 -0
  4. build/lib/pyTEMlib/config_dir.py +43 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +757 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +298 -0
  8. build/lib/pyTEMlib/eds_tools.py +901 -0
  9. build/lib/pyTEMlib/eds_xsections.py +268 -0
  10. build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
  11. build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  12. build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
  13. build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  14. build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  15. build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  16. build/lib/pyTEMlib/file_reader.py +275 -0
  17. build/lib/pyTEMlib/file_tools.py +816 -0
  18. build/lib/pyTEMlib/get_bote_salvat.py +69 -0
  19. build/lib/pyTEMlib/graph_tools.py +1153 -0
  20. build/lib/pyTEMlib/graph_viz.py +599 -0
  21. build/lib/pyTEMlib/image/__init__.py +37 -0
  22. build/lib/pyTEMlib/image/image_atoms.py +270 -0
  23. build/lib/pyTEMlib/image/image_clean.py +198 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +278 -0
  26. build/lib/pyTEMlib/image/image_graph.py +926 -0
  27. build/lib/pyTEMlib/image/image_registration.py +316 -0
  28. build/lib/pyTEMlib/image/image_utilities.py +308 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +701 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1145 -0
  33. build/lib/pyTEMlib/microscope.py +62 -0
  34. build/lib/pyTEMlib/probe_tools.py +928 -0
  35. build/lib/pyTEMlib/sidpy_tools.py +153 -0
  36. build/lib/pyTEMlib/simulation_tools.py +104 -0
  37. build/lib/pyTEMlib/test.py +437 -0
  38. build/lib/pyTEMlib/utilities.py +431 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +36 -0
  42. pyTEMlib/animation.py +667 -0
  43. pyTEMlib/atom_tools.py +229 -0
  44. pyTEMlib/config_dir.py +43 -0
  45. pyTEMlib/crystal_tools.py +1219 -0
  46. pyTEMlib/data/k_factors_Bruker_15keV.json +293 -0
  47. pyTEMlib/data/k_factors_Thermo_200keV.json +494 -0
  48. pyTEMlib/data/xrays_X_section_100kV.json +19334 -0
  49. pyTEMlib/data/xrays_X_section_200kV.json +18711 -0
  50. pyTEMlib/data/xrays_X_section_300kV.json +18347 -0
  51. pyTEMlib/data/xrays_X_section_60kV.json +20202 -0
  52. pyTEMlib/diffraction_plot.py +757 -0
  53. pyTEMlib/dynamic_scattering.py +298 -0
  54. pyTEMlib/eds_tools.py +901 -0
  55. pyTEMlib/eds_xsections.py +268 -0
  56. pyTEMlib/eels_tools/__init__.py +44 -0
  57. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  58. pyTEMlib/eels_tools/eels_database.py +134 -0
  59. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  60. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  61. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  62. pyTEMlib/file_reader.py +275 -0
  63. pyTEMlib/file_tools.py +816 -0
  64. pyTEMlib/get_bote_salvat.py +69 -0
  65. pyTEMlib/graph_tools.py +1153 -0
  66. pyTEMlib/graph_viz.py +599 -0
  67. pyTEMlib/image/__init__.py +37 -0
  68. pyTEMlib/image/image_atoms.py +270 -0
  69. pyTEMlib/image/image_clean.py +198 -0
  70. pyTEMlib/image/image_distortion.py +299 -0
  71. pyTEMlib/image/image_fft.py +278 -0
  72. pyTEMlib/image/image_graph.py +926 -0
  73. pyTEMlib/image/image_registration.py +316 -0
  74. pyTEMlib/image/image_utilities.py +308 -0
  75. pyTEMlib/image/image_window.py +421 -0
  76. pyTEMlib/image_tools.py +701 -0
  77. pyTEMlib/interactive_image.py +1 -0
  78. pyTEMlib/kinematic_scattering.py +1145 -0
  79. pyTEMlib/microscope.py +62 -0
  80. pyTEMlib/probe_tools.py +928 -0
  81. pyTEMlib/sidpy_tools.py +153 -0
  82. pyTEMlib/simulation_tools.py +104 -0
  83. pyTEMlib/test.py +437 -0
  84. pyTEMlib/utilities.py +431 -0
  85. pyTEMlib/version.py +5 -0
  86. pyTEMlib/xrpa_x_sections.py +20976 -0
  87. pytemlib-0.2025.12.0.dist-info/METADATA +100 -0
  88. pytemlib-0.2025.12.0.dist-info/RECORD +92 -0
  89. pytemlib-0.2025.12.0.dist-info/WHEEL +5 -0
  90. pytemlib-0.2025.12.0.dist-info/entry_points.txt +2 -0
  91. pytemlib-0.2025.12.0.dist-info/licenses/LICENSE +21 -0
  92. pytemlib-0.2025.12.0.dist-info/top_level.txt +5 -0
pyTEMlib/file_tools.py ADDED
@@ -0,0 +1,816 @@
1
+ """file_tools: All tools to load and save data
2
+
3
+ ##################################
4
+
5
+ 2018 01 31 Included Nion Swift files to be opened
6
+ major revision 2020 09 to include sidpy and pyNSID data formats
7
+ 2022 change to ase format for structures: this changed the default unit of length to Angstrom!!!
8
+
9
+ ##################################
10
+ """
11
+ import typing
12
+
13
+ import os
14
+ import pickle
15
+ import numpy as np
16
+ import h5py
17
+
18
+ # For structure files of various flavor for instance POSCAR and other theory packages
19
+ import ase.io
20
+
21
+ # =============================================
22
+ # Include pycroscopy libraries #
23
+ # =============================================
24
+ import SciFiReaders
25
+ import pyNSID
26
+ import sidpy
27
+ import ipywidgets
28
+ import IPython
29
+
30
+ # =============================================
31
+ # Include pyTEMlib libraries #
32
+ # =============================================
33
+ from . import crystal_tools
34
+ from .config_dir import config_path
35
+ from .file_reader import adorned_to_sidpy, read_old_h5group
36
+ from .version import __version__
37
+ Dimension = sidpy.Dimension
38
+
39
+ __version__ = '2025.8.07'
40
+
41
+
42
+ ChooseDataset = sidpy.ChooseDataset
43
+
44
+ class FileWidget(sidpy.FileWidget):
45
+ """Widget to select directories or widgets from a list
46
+
47
+ Works in google colab.
48
+ The widget converts the name of the nion file to the one in Nion's swift software,
49
+ because it is otherwise incomprehensible
50
+
51
+ Attributes
52
+ ----------
53
+ dir_name: str
54
+ name of starting directory
55
+ extension: list of str
56
+ extensions of files to be listed in widget
57
+
58
+ Methods
59
+ -------
60
+ get_directory
61
+ set_options
62
+ get_file_name
63
+
64
+ Example
65
+ -------
66
+ >>from google.colab import drive
67
+ >>drive.mount("/content/drive")
68
+ >>file_list = pyTEMlib.file_tools.FileWidget()
69
+ next code cell:
70
+ >>datasets = file_list.datasets
71
+ >>dataset = file_list.selected_dataset
72
+
73
+ """
74
+ def __init__(self, dir_name=None, extension=['*'], sum_frames=False):
75
+ if dir_name is None:
76
+ dir_name = get_last_path()
77
+ self.save_path = True
78
+ super().__init__(dir_name=dir_name, extension=extension, sum_frames=sum_frames)
79
+ select_button = ipywidgets.Button(description='Select Main',
80
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
81
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
82
+
83
+ add_button = ipywidgets.Button(description='Add',
84
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
85
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
86
+ self.dataset_list = ['None']
87
+ self.selected_dataset = None
88
+ self.datasets = {}
89
+ self.selected_key = ''
90
+ self.loaded_datasets = ipywidgets.Dropdown(options=self.dataset_list,
91
+ value=self.dataset_list[0],
92
+ description='loaded datasets:',
93
+ disabled=False)
94
+
95
+ ui = ipywidgets.HBox([select_button, add_button, self.loaded_datasets])
96
+ IPython.display.display(ui)
97
+ select_button.on_click(self.select_main)
98
+ add_button.on_click(self.add_dataset)
99
+ self.loaded_datasets.observe(self.select_dataset)
100
+
101
+ def select_dataset(self, value: int = 0):
102
+ """Select a dataset from the dropdown."""
103
+ key = self.loaded_datasets.value.split(':')[0]
104
+ if key != 'None':
105
+ self.selected_dataset = self.datasets[key]
106
+ self.selected_key = key
107
+
108
+ def select_main(self, value: int = 0):
109
+ """Select the main dataset."""
110
+ self.datasets = {}
111
+ self.dataset_list = []
112
+ self.datasets = open_file(self.file_name, sum_frames=self.sum_frames)
113
+ self.dataset_list = []
114
+ for key in self.datasets.keys():
115
+ self.dataset_list.append(f'{key}: {self.datasets[key].title}')
116
+ self.loaded_datasets.options = self.dataset_list
117
+ self.loaded_datasets.value = self.dataset_list[0]
118
+ self.dataset = self.datasets[list(self.datasets.keys())[0]]
119
+ self.selected_dataset = self.dataset
120
+
121
+ def add_dataset(self, value: int = 0):
122
+ """Add another dataset to the list of loaded datasets."""
123
+ key = add_dataset_from_file(self.datasets, self.file_name, 'Channel')
124
+ self.dataset_list.append(f'{key}: {self.datasets[key].title}')
125
+ self.loaded_datasets.options = self.dataset_list
126
+ self.loaded_datasets.value = self.dataset_list[-1]
127
+
128
+
129
+ def add_to_dict(file_dict: dict, name: str):
130
+ """Add a file to the dictionary with its metadata."""
131
+ full_name = os.path.join(file_dict['directory'], name)
132
+ basename, extension = os.path.splitext(name)
133
+ size = os.path.getsize(full_name) * 2 ** -20
134
+ display_name = name
135
+ if len(extension) == 0:
136
+ display_file_list = f' {name} - {size:.1f} MB'
137
+ elif extension[0] == 'hf5':
138
+ if extension in ['.hf5']:
139
+ display_file_list = f" {name} - {size:.1f} MB"
140
+ elif extension in ['.h5', '.ndata']:
141
+ try:
142
+ reader = SciFiReaders.NionReader(full_name)
143
+ dataset_nion = reader.read()
144
+ key = list(dataset_nion.keys())[0]
145
+ display_name = dataset_nion[key].title
146
+ display_file_list = f" {display_name}{extension} - {size:.1f} MB"
147
+ except:
148
+ display_file_list = f" {name} - {size:.1f} MB"
149
+ else:
150
+ display_file_list = f' {name} - {size:.1f} MB'
151
+ file_dict[name] = {'display_string': display_file_list, 'basename': basename,
152
+ 'extension': extension, 'size': size, 'display_name': display_name}
153
+
154
+
155
+ def update_directory_list(directory_name: str) -> dict:
156
+ """Update the directory list and return the file dictionary."""
157
+ dir_list = os.listdir(directory_name)
158
+
159
+ if '.pyTEMlib.files.pkl' in dir_list:
160
+ with open(os.path.join(directory_name, '.pyTEMlib.files.pkl'), 'rb') as f:
161
+ file_dict = pickle.load(f)
162
+ if directory_name != file_dict['directory']:
163
+ print('directory moved since last time read')
164
+ file_dict['directory'] = directory_name
165
+ dir_list.remove('.pyTEMlib.files.pkl')
166
+ else:
167
+ file_dict = {'directory': directory_name}
168
+
169
+ # add new files
170
+ file_dict['file_list'] = []
171
+ file_dict['display_file_list'] = []
172
+ file_dict['directory_list'] = []
173
+
174
+ for name in dir_list:
175
+ if os.path.isfile(os.path.join(file_dict['directory'], name)):
176
+ if name not in file_dict:
177
+ add_to_dict(file_dict, name)
178
+ file_dict['file_list'].append(name)
179
+ file_dict['display_file_list'].append(file_dict[name]['display_string'])
180
+ else:
181
+ file_dict['directory_list'].append(name)
182
+ remove_item = []
183
+
184
+ # delete items of deleted files
185
+ save_pickle = False
186
+
187
+ for name in file_dict.keys():
188
+ if name not in dir_list and name not in ['directory', 'file_list',
189
+ 'directory_list', 'display_file_list']:
190
+ remove_item.append(name)
191
+ else:
192
+ if 'extension' in file_dict[name]:
193
+ save_pickle = True
194
+ for item in remove_item:
195
+ file_dict.pop(item)
196
+
197
+ if save_pickle:
198
+ with open(os.path.join(file_dict['directory'], '.pyTEMlib.files.pkl'), 'wb') as f:
199
+ pickle.dump(file_dict, f)
200
+ return file_dict
201
+
202
+
203
+ ####
204
+ # General Open and Save Methods
205
+ ####
206
+
207
+ def get_last_path() -> str:
208
+ """Returns the path of the file last opened"""
209
+ try:
210
+ with open(config_path + '\\path.txt', 'r', encoding='utf-8') as file:
211
+ path = file.read()
212
+ except IOError:
213
+ path = ''
214
+
215
+ if len(path) < 2:
216
+ path = '.'
217
+ else:
218
+ if not os.path.exists(path):
219
+ path = '.'
220
+ return path
221
+
222
+
223
+ def save_path(filename: str) -> str:
224
+ """Save path of last opened file"""
225
+
226
+ if len(filename) > 1:
227
+ with open(config_path + '\\path.txt', 'w', encoding='utf-8') as file:
228
+ path, _ = os.path.split(filename)
229
+ file.write(path)
230
+ else:
231
+ path = '.'
232
+ return path
233
+
234
+
235
+ def save_dataset(dataset, filename, h5_group=None):
236
+ """ Saves a dataset to a file in pyNSID format
237
+ Parameters
238
+ ----------
239
+ dataset: sidpy.Dataset
240
+ the data
241
+ filename: str
242
+ name of file to be opened
243
+ h5_group: hd5py.Group
244
+ not used yet
245
+ """
246
+ h5_filename = get_h5_filename(filename)
247
+ h5_file = h5py.File(h5_filename, mode='a')
248
+ if isinstance(dataset, dict):
249
+ h5_group = save_dataset_dictionary(h5_file, dataset)
250
+ return h5_group
251
+ if isinstance(dataset, sidpy.Dataset):
252
+ h5_dataset = save_single_dataset(h5_file, dataset, h5_group=h5_group)
253
+ return h5_dataset.parent
254
+
255
+ raise TypeError('Only sidpy.datasets or dictionaries can be saved with pyTEMlib')
256
+
257
+
258
+ def save_single_dataset(h5_file, dataset, h5_group=None):
259
+ """
260
+ Saves a single sidpy.Dataset to an HDF5 file.
261
+ """
262
+ if h5_group is None:
263
+ h5_measurement_group = sidpy.hdf.prov_utils.create_indexed_group(h5_file, 'Measurement_')
264
+ h5_group = sidpy.hdf.prov_utils.create_indexed_group(h5_measurement_group, 'Channel_')
265
+
266
+ elif isinstance(h5_group, str):
267
+ if h5_group not in h5_file:
268
+ h5_group = h5_file.create_group(h5_group)
269
+ else:
270
+ if h5_group[-1] == '/':
271
+ h5_group = h5_group[:-1]
272
+
273
+ channel = h5_group.split('/')[-1]
274
+ h5_measurement_group = h5_group[:-len(channel)]
275
+ h5_group = sidpy.hdf.prov_utils.create_indexed_group(h5_group, 'Channel_')
276
+ else:
277
+ raise ValueError('h5_group needs to be string or None')
278
+
279
+ h5_dataset = pyNSID.hdf_io.write_nsid_dataset(dataset, h5_group)
280
+ dataset.h5_dataset = h5_dataset
281
+ h5_dataset.file.flush()
282
+ return h5_dataset
283
+
284
+
285
+ def save_dataset_dictionary(h5_file: h5py.File, datasets: dict) -> h5py.Group:
286
+ """Saves a dictionary of datasets to an HDF5 file."""
287
+ h5_measurement_group = sidpy.hdf.prov_utils.create_indexed_group(h5_file, 'Measurement_')
288
+ for key, dataset in datasets.items():
289
+ if key[-1] == '/':
290
+ key = key[:-1]
291
+ if isinstance(dataset, sidpy.Dataset):
292
+ h5_group = h5_measurement_group.create_group(key)
293
+ h5_dataset = pyNSID.hdf_io.write_nsid_dataset(dataset, h5_group)
294
+ dataset.h5_dataset = h5_dataset
295
+ h5_dataset.file.flush()
296
+ elif isinstance(dataset, dict):
297
+ sidpy.hdf.hdf_utils.write_dict_to_h5_group(h5_measurement_group, dataset, key)
298
+ else:
299
+ print('could not save item ', key, 'of dataset dictionary')
300
+ return h5_measurement_group
301
+
302
+
303
+ def h5_group_to_dict(group, group_dict={}):
304
+ """
305
+ Converts an h5py group to a python dictionary.
306
+ """
307
+ if not isinstance(group, h5py.Group):
308
+ raise TypeError('we need a h5py group to read from')
309
+ if not isinstance(group_dict, dict):
310
+ raise TypeError('group_dict needs to be a python dictionary')
311
+
312
+ group_dict[group.name.split('/')[-1]] = dict(group.attrs)
313
+ for key in group.keys():
314
+ h5_group_to_dict(group[key], group_dict[group.name.split('/')[-1]])
315
+ return group_dict
316
+
317
+
318
+ def read_dm_annotation(image: sidpy.Dataset) -> typing.Dict[str, typing.Any]:
319
+ """
320
+ Reads annotations from a sidpy.Dataset that originated from a dm3 file.
321
+ """
322
+ if 'MAGE' not in image.data_type.name:
323
+ return {}
324
+ dimensions = image.get_image_dims(return_axis=True)
325
+ scale_x = np.abs(dimensions[0].slope)
326
+ scale_y = np.abs(dimensions[1].slope)
327
+ rec_scale = np.array([scale_x, scale_y, scale_x, scale_y])
328
+ annotations = {}
329
+ tags = image.original_metadata.get('DocumentObjectList', {}).get('0', {}).get('AnnotationGroupList', {})
330
+
331
+ if not tags:
332
+ return annotations
333
+
334
+ for key in tags:
335
+ if isinstance(tags[key], dict):
336
+ if tags[key]['AnnotationType'] == 13: #type 'text'
337
+ annotations[key] = {'type': 'text'}
338
+ annotations[key]['label'] = tags[key].get('Label', '')
339
+ rect = np.array(tags[key]['Rectangle']) * rec_scale
340
+ annotations[key]['position'] = [rect[1], rect[0]]
341
+ annotations[key]['text'] = tags[key].get('Text', key)
342
+ elif tags[key]['AnnotationType']==6:
343
+ annotations[key] = {'type': 'circle'}
344
+ annotations[key]['label'] = tags[key].get('Label', '')
345
+ rect = np.array(tags[key]['Rectangle']) * rec_scale
346
+ annotations[key]['radius'] = rect[3]-rect[1]
347
+ annotations[key]['position'] = [rect[1],rect[0]]
348
+ elif tags[key]['AnnotationType'] == 23:
349
+ annotations[key] = {'type': 'spectral_image'}
350
+ annotations[key]['label'] = tags[key].get('Label', '')
351
+ rect = np.array(tags[key].get('Rectangle', [0 ,0, 0, 0])) * rec_scale
352
+ annotations[key]['width'] = rect[3]-rect[1]
353
+ annotations[key]['height'] = rect[2]-rect[0]
354
+ annotations[key]['position'] = [rect[1],rect[0]]
355
+ annotations[key]['Rectangle'] = np.array(tags[key].get('Rectangle', [0 ,0, 0, 0]))
356
+ if annotations:
357
+ image.metadata['annotations'] = annotations
358
+ return annotations
359
+
360
+
361
+ def open_file(filename, write_hdf_file=False, sum_frames=False, sum_eds=True):
362
+ """Opens a file if the extension is .emd, .mrc, .hf5, .ndata, .dm3 or .dm4
363
+
364
+ Everything will be stored in a NSID style hf5 file.
365
+ Subroutines used:
366
+ - NSIDReader
367
+ - nsid.write_
368
+ - get_main_tags
369
+ - get_additional tags
370
+
371
+ Parameters
372
+ ----------
373
+ filename: str
374
+ name of file to be opened
375
+ h5_group: hd5py.Group
376
+ not used yet #TODO: provide hook for usage of external chosen group
377
+ write_hdf_file: bool
378
+ set to false so that sidpy dataset will not be written to hf5-file automatically
379
+
380
+ Returns
381
+ -------
382
+ sidpy.Dataset
383
+ sidpy dataset with location of hdf5 dataset as attribute
384
+
385
+ """
386
+ if not isinstance(filename, str):
387
+ raise TypeError('filename must be a non-empty string')
388
+ if filename == '':
389
+ raise TypeError('filename must be a non-empty string')
390
+
391
+ _, file_name = os.path.split(filename)
392
+ basename, extension = os.path.splitext(file_name)
393
+ provenance = ''
394
+ if extension == '.hf5':
395
+ reader = SciFiReaders.NSIDReader(filename)
396
+ datasets = reader.read()
397
+ if len(datasets) < 1:
398
+ print('no hdf5 dataset found in file')
399
+ return {}
400
+ if isinstance(datasets, dict):
401
+ dataset_dict = datasets
402
+ else:
403
+ dataset_dict = {}
404
+ for index, dataset in enumerate(datasets):
405
+ title = str(dataset.title).rsplit('/', maxsplit=1)[-1]
406
+ # dataset.title = str(dataset.title).split('/')[-1]
407
+ dataset_dict[title] = dataset
408
+ if index == 0:
409
+ file = datasets[0].h5_dataset.file
410
+ master_group = datasets[0].h5_dataset.parent.parent.parent
411
+ for key in master_group.keys():
412
+ if key not in dataset_dict:
413
+ dataset_dict[key] = h5_group_to_dict(master_group[key])
414
+ if not write_hdf_file:
415
+ file.close()
416
+ for dset in dataset_dict.values():
417
+ if isinstance(dset, sidpy.Dataset):
418
+ if 'Measurement' in dset.title:
419
+ dset.title = dset.title.split('/')[-1]
420
+ return dataset_dict
421
+
422
+ if extension in ['.dm3', '.dm4']:
423
+ reader = SciFiReaders.DMReader(filename)
424
+ elif extension in ['.emi']:
425
+ try:
426
+ import hyperspy.api as hs
427
+ s = hs.load(filename)
428
+ dataset_dict = {}
429
+ spectrum_number = 0
430
+ if not isinstance(s, list):
431
+ s = [s]
432
+ for index, datum in enumerate(s):
433
+ dset = SciFiReaders.convert_hyperspy(datum)
434
+ if datum.data.ndim == 1:
435
+ dset.title = dset.title + f'_{spectrum_number}_Spectrum'
436
+ spectrum_number += 1
437
+ elif datum.data.ndim == 3:
438
+ dset.title = dset.title + '_SI'
439
+ dset = dset.T
440
+ dset.title = dset.title[11:]
441
+ dset.add_provenance('pyTEMlib', 'open_file', version=__version__,
442
+ linked_data='emi_converted_by_hyperspy')
443
+ dataset_dict[f'Channel_{index:03d}'] = dset
444
+ return dataset_dict
445
+ except ImportError:
446
+ print('This file type needs hyperspy to be installed to be able to be read')
447
+ return
448
+ elif extension == '.emd':
449
+ reader = SciFiReaders.EMDReader(filename, sum_frames=sum_frames)
450
+ provenance = 'SciFiReader.EMDReader'
451
+ elif 'edax' in extension.lower():
452
+ if 'h5' in extension:
453
+ reader = SciFiReaders.EDAXReader(filename)
454
+ provenance = 'SciFiReader.EDAXReader'
455
+
456
+ elif extension in ['.ndata', '.h5']:
457
+ reader = SciFiReaders.NionReader(filename)
458
+ provenance = 'SciFiReader.NionReader'
459
+
460
+ elif extension in ['.rto']:
461
+ reader = SciFiReaders.BrukerReader(filename)
462
+ provenance = 'SciFiReader.BrukerReader'
463
+
464
+ elif extension in ['.mrc']:
465
+ reader = SciFiReaders.MRCReader(filename)
466
+ provenance = 'SciFiReader.MRCReader'
467
+
468
+ else:
469
+ raise NotImplementedError('extension not supported')
470
+
471
+ _, file_name = os.path.split(filename)
472
+ basename, _ = os.path.splitext(file_name)
473
+
474
+ # ### Here we read the data into sidpy datasets
475
+ if extension != '.emi':
476
+ dset = reader.read()
477
+
478
+ if extension in ['.dm3', '.dm4']:
479
+ title = (basename.strip().replace('-', '_')).split('/')[-1]
480
+ if not isinstance(dset, dict):
481
+ print('Please use new SciFiReaders Package for full functionality')
482
+ if isinstance(dset, sidpy.Dataset):
483
+ dset = {'Channel_000': dset}
484
+
485
+ for key in dset:
486
+ read_dm_annotation(dset[key])
487
+
488
+ elif extension == '.emd':
489
+ if not sum_eds:
490
+ return
491
+ eds_keys = []
492
+ for key, item in dset.items():
493
+ if item.data_type.name in ['SPECTRUM', 'SPECTRAL_IMAGE']:
494
+ if ('SuperX' in item.title or 'UltraX' in item.title) and item.data_type.name in ['SPECTRUM', 'SPECTRAL_IMAGE']:
495
+ if item.title[-2:].isnumeric():
496
+ if item.title[-2].isdigit():
497
+ if len(eds_keys) == 0:
498
+ spectrum = item.copy()
499
+ else:
500
+ spectrum += item
501
+ eds_keys.append(key)
502
+ if eds_keys:
503
+ spectrum.compute()
504
+ spectrum.data_type = dset[eds_keys[0]].data_type
505
+ if 'SuperX' in dset[eds_keys[0]].title:
506
+ spectrum.title = 'EDS_SuperX'
507
+ if 'UltraX' in dset[eds_keys[0]].title:
508
+ spectrum.title = 'EDS_UltraX'
509
+ spectrum.original_metadata = dset[eds_keys[0]].original_metadata.copy()
510
+ spectrum.metadata = dset[eds_keys[0]].metadata.copy()
511
+
512
+ for key in eds_keys:
513
+ del dset[key]
514
+ dset['SuperX'] = spectrum
515
+
516
+ if isinstance(dset, dict):
517
+ dataset_dict = dset
518
+ for dataset in dataset_dict.values():
519
+ dataset.add_provenance('pyTEMlib', 'open_file',
520
+ version=__version__,
521
+ linked_data=provenance)
522
+ dataset.metadata['filename'] = filename
523
+
524
+ elif isinstance(dset, list):
525
+ DeprecationWarning('Update SciFiReaders, we do not support list of datasets anymore')
526
+ else:
527
+ dset.filename = basename.strip().replace('-', '_')
528
+ read_essential_metadata(dset)
529
+ dset.metadata['filename'] = filename
530
+ dataset_dict = {'Channel_000': dset}
531
+
532
+ # Temporary Fix for dual eels spectra in dm files
533
+ # Todo: Fix in SciFiReaders
534
+ for dset in dataset_dict.values():
535
+ if 'experiment' in dset.metadata:
536
+ exp_meta = dset.metadata['experiment']
537
+ if 'single_exposure_time' in exp_meta:
538
+ exp_meta['exposure_time'] = exp_meta['number_of_frames'] * \
539
+ exp_meta['single_exposure_time']
540
+ if write_hdf_file:
541
+ save_dataset(dataset_dict, filename=filename)
542
+
543
+ save_path(filename)
544
+ return dataset_dict
545
+
546
+
547
+ ################################################################
548
+ # Read Functions
549
+ #################################################################
550
+
551
+ def read_essential_metadata(dataset):
552
+ """Updates dataset.metadata['experiment'] with essential information read from original metadata
553
+
554
+ This depends on whether it is originally a nion or a dm3 file
555
+ """
556
+ if not isinstance(dataset, sidpy.Dataset):
557
+ raise TypeError("we need a sidpy.Dataset")
558
+ experiment_dictionary = {}
559
+ if dataset.original_metadata.get('metadata', {}).get('hardware_source'):
560
+ experiment_dictionary = read_nion_image_info(dataset.original_metadata)
561
+ if 'experiment' not in dataset.metadata:
562
+ dataset.metadata['experiment'] = {}
563
+ dataset.metadata['experiment'].update(experiment_dictionary)
564
+
565
+
566
+
567
+ def read_nion_image_info(original_metadata):
568
+ """Read essential parameter from original_metadata originating from a dm3 file"""
569
+ if not isinstance(original_metadata, dict):
570
+ raise TypeError('We need a dictionary to read')
571
+ metadata = original_metadata.get('metadata', {}).get('hardware_source', {})
572
+
573
+ return metadata.get('ImageScanned', {})
574
+
575
+
576
+ def get_h5_filename(fname):
577
+ """Determines file name of hdf5 file for newly converted data file"""
578
+
579
+ path, filename = os.path.split(fname)
580
+ basename, _ = os.path.splitext(filename)
581
+ h5_file_name_original = os.path.join(path, basename + '.hf5')
582
+ h5_file_name = h5_file_name_original
583
+
584
+ if os.path.exists(os.path.abspath(h5_file_name_original)):
585
+ count = 1
586
+ h5_file_name = h5_file_name_original[:-4] + '-' + str(count) + '.hf5'
587
+ while os.path.exists(os.path.abspath(h5_file_name)):
588
+ count += 1
589
+ h5_file_name = h5_file_name_original[:-4] + '-' + str(count) + '.hf5'
590
+
591
+ if h5_file_name != h5_file_name_original:
592
+ path, filename = os.path.split(h5_file_name)
593
+ print('Cannot overwrite file. Using: ', filename)
594
+ return str(h5_file_name)
595
+
596
+
597
+ def get_start_channel(h5_file):
598
+ """ Legacy for get start channel"""
599
+
600
+ DeprecationWarning('Depreciated: use function get_main_channel instead')
601
+ return get_main_channel(h5_file)
602
+
603
+
604
+ def get_main_channel(h5_file):
605
+ """Returns name of first channel group in hdf5-file"""
606
+
607
+ current_channel = None
608
+ if 'Measurement_000' in h5_file:
609
+ if 'Measurement_000/Channel_000' in h5_file:
610
+ current_channel = h5_file['Measurement_000/Channel_000']
611
+ return current_channel
612
+
613
+
614
+ def h5_tree(input_object):
615
+ """Just a wrapper for the sidpy function print_tree,
616
+
617
+ so that sidpy does not have to be loaded in notebook
618
+
619
+ """
620
+
621
+ if isinstance(input_object, sidpy.Dataset):
622
+ if not isinstance(input_object.h5_dataset, h5py.Dataset):
623
+ raise ValueError('sidpy dataset does not have an associated h5py dataset')
624
+ h5_file = input_object.h5_dataset.file
625
+ elif isinstance(input_object, h5py.Dataset):
626
+ h5_file = input_object.file
627
+ elif isinstance(input_object, (h5py.Group, h5py.File)):
628
+ h5_file = input_object
629
+ else:
630
+ raise TypeError('should be a h5py.object or sidpy Dataset')
631
+ sidpy.hdf_utils.print_tree(h5_file)
632
+
633
+
634
+ def add_dataset_from_file(datasets, filename=None, key_name='Log', single_dataset=True):
635
+ """Add dataset to datasets dictionary
636
+
637
+ Parameters
638
+ ----------
639
+ dataset: dict
640
+ dictionary to write to file
641
+ filename: str, default: None,
642
+ name of file to open, if None, adialog will appear
643
+ key_name: str, default: 'Log'
644
+ name for key in dictionary with running number being added
645
+
646
+ Returns
647
+ -------
648
+ key_name: str
649
+ actual last used name of dictionary key
650
+ """
651
+ datasets2 = open_file(filename=filename)
652
+ first_dataset = datasets2[list(datasets2)[0]]
653
+ if isinstance(first_dataset, sidpy.Dataset):
654
+ index = 0
655
+ for key in datasets.keys():
656
+ if key_name in key:
657
+ if int(key[-3:]) >= index:
658
+ index = int(key[-3:])+1
659
+ if single_dataset:
660
+ datasets[key_name+f'_{index:03}'] = first_dataset
661
+ else:
662
+ for key, dataset in datasets2.items():
663
+ print(key)
664
+ if isinstance(dataset, sidpy.Dataset):
665
+ datasets[key_name+f'_{index:03}'] = dataset
666
+ index += 1
667
+ else:
668
+ print(key)
669
+ datasets[key] = dataset
670
+ index -= 1
671
+ else:
672
+ return None
673
+
674
+ return f'{key_name}_{index:03}'
675
+
676
+
677
+ # ##
678
+ # Crystal Structure Read and Write
679
+ # ##
680
+ def read_poscar(file_name):
681
+ """
682
+ Open a POSCAR file from Vasp
683
+ If no file name is provided an open file dialog to select a POSCAR file appears
684
+
685
+ Parameters
686
+ ----------
687
+ file_name: str
688
+ if None is provided an open file dialog will appear
689
+
690
+ Return
691
+ ------
692
+ crystal: ase.Atoms
693
+ crystal structure in ase format
694
+ """
695
+
696
+ # use ase package to read file
697
+ base = os.path.basename(file_name)
698
+ base_name = os.path.splitext(base)[0]
699
+ crystal = ase.io.read(file_name, format='vasp', parallel=False)
700
+
701
+ # make dictionary and plot structure (not essential for further notebook)
702
+ crystal.info = {'title': base_name}
703
+ return crystal
704
+
705
+
706
+ def read_cif(file_name, verbose=False): # open file dialog to select cif file
707
+ """
708
+ Open a cif file
709
+ If no file name is provided an open file dialog to select a cif file appears
710
+
711
+ Parameters
712
+ ----------
713
+ file_name: str
714
+ if None is provided an open file dialog will appear
715
+ verbose: bool
716
+
717
+ Return
718
+ ------
719
+ crystal: ase.Atoms
720
+ crystal structure in ase format
721
+ """
722
+
723
+ base = os.path.basename(file_name)
724
+ base_name = os.path.splitext(base)[0]
725
+ crystal = ase.io.read(file_name, format='cif', store_tags=True, parallel=False)
726
+
727
+ # make dictionary and plot structure (not essential for further notebook)
728
+ if crystal.info is None:
729
+ crystal.info = {'title': base_name}
730
+ crystal.info.update({'title': base_name})
731
+ if verbose:
732
+ print('Opened cif file for ', crystal.get_chemical_formula())
733
+
734
+ return crystal
735
+
736
+
737
+ def h5_add_crystal_structure(h5_file, input_structure):
738
+ """Write crystal structure to NSID file"""
739
+
740
+ if isinstance(input_structure, ase.Atoms):
741
+
742
+ crystal_tags = crystal_tools.get_dictionary(input_structure)
743
+ if crystal_tags['metadata'] == {}:
744
+ crystal_tags['metadata'] = {'title': input_structure.get_chemical_formula()}
745
+ elif isinstance(input_structure, dict):
746
+ crystal_tags = input_structure
747
+ else:
748
+ raise TypeError('Need a dictionary or an ase.Atoms object with ase installed')
749
+
750
+ structure_group = sidpy.hdf.prov_utils.create_indexed_group(h5_file, 'Structure_')
751
+
752
+ for key, item in crystal_tags.items():
753
+ if not isinstance(item, dict):
754
+ structure_group[key] = item
755
+
756
+ if 'base' in crystal_tags:
757
+ structure_group['relative_positions'] = crystal_tags['base']
758
+ if 'title' in crystal_tags:
759
+ structure_group['title'] = str(crystal_tags['title'])
760
+ structure_group['_' + crystal_tags['title']] = str(crystal_tags['title'])
761
+
762
+ # ToDo: Save all of info dictionary
763
+ if 'metadata' in input_structure:
764
+ structure_group.create_group('metadata')
765
+ sidpy.hdf.hdf_utils.write_simple_attrs(structure_group['metadata'],
766
+ input_structure['metadata'])
767
+
768
+ h5_file.file.flush()
769
+ return structure_group
770
+
771
+
772
+ def h5_add_to_structure(structure_group, crystal_tags):
773
+ """add dictionary as structure group"""
774
+
775
+ for key in crystal_tags:
776
+ if key in structure_group.keys():
777
+ print(key, ' not written; use new name')
778
+ else:
779
+ structure_group[key] = crystal_tags[key]
780
+
781
+
782
+ def h5_get_crystal_structure(structure_group):
783
+ """Read crystal structure from NSID file
784
+ Any additional information will be read as dictionary into the
785
+ info attribute of the ase.Atoms object
786
+
787
+ Parameters
788
+ ----------
789
+ structure_group: h5py.Group
790
+ location in hdf5 file to where the structure information is stored
791
+
792
+ Returns
793
+ -------
794
+ atoms: ase.Atoms object
795
+ crystal structure in ase format
796
+
797
+ """
798
+
799
+ crystal_tags = {'unit_cell': structure_group['unit_cell'][()],
800
+ 'base': structure_group['relative_positions'][()],
801
+ 'title': structure_group['title'][()]}
802
+ if '2D' in structure_group:
803
+ crystal_tags['2D'] = structure_group['2D'][()]
804
+ elements = structure_group['elements'][()]
805
+ crystal_tags['elements'] = []
806
+ for e in elements:
807
+ crystal_tags['elements'].append(e.astype(str, copy=False))
808
+
809
+ atoms = crystal_tools.atoms_from_dictionary(crystal_tags)
810
+ if 'metadata' in structure_group:
811
+ atoms.info = sidpy.hdf.hdf_utils.h5_group_to_dict(structure_group)
812
+
813
+ if 'zone_axis' in structure_group:
814
+ atoms.info = {'experiment': {'zone_axis': structure_group['zone_axis'][()]}}
815
+ # ToDo: Read all of info dictionary
816
+ return atoms