pyTEMlib 0.2025.4.1__py3-none-any.whl → 0.2025.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyTEMlib might be problematic. Click here for more details.

Files changed (94) hide show
  1. build/lib/pyTEMlib/__init__.py +33 -0
  2. build/lib/pyTEMlib/animation.py +640 -0
  3. build/lib/pyTEMlib/atom_tools.py +238 -0
  4. build/lib/pyTEMlib/config_dir.py +31 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +756 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +293 -0
  8. build/lib/pyTEMlib/eds_tools.py +826 -0
  9. build/lib/pyTEMlib/eds_xsections.py +432 -0
  10. build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
  11. build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  12. build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
  13. build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  14. build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  15. build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  16. build/lib/pyTEMlib/file_reader.py +274 -0
  17. build/lib/pyTEMlib/file_tools.py +811 -0
  18. build/lib/pyTEMlib/get_bote_salvat.py +69 -0
  19. build/lib/pyTEMlib/graph_tools.py +1153 -0
  20. build/lib/pyTEMlib/graph_viz.py +599 -0
  21. build/lib/pyTEMlib/image/__init__.py +37 -0
  22. build/lib/pyTEMlib/image/image_atoms.py +270 -0
  23. build/lib/pyTEMlib/image/image_clean.py +197 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +277 -0
  26. build/lib/pyTEMlib/image/image_graph.py +926 -0
  27. build/lib/pyTEMlib/image/image_registration.py +316 -0
  28. build/lib/pyTEMlib/image/image_utilities.py +309 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +699 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
  33. build/lib/pyTEMlib/microscope.py +61 -0
  34. build/lib/pyTEMlib/probe_tools.py +906 -0
  35. build/lib/pyTEMlib/sidpy_tools.py +153 -0
  36. build/lib/pyTEMlib/simulation_tools.py +104 -0
  37. build/lib/pyTEMlib/test.py +437 -0
  38. build/lib/pyTEMlib/utilities.py +314 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +25 -3
  42. pyTEMlib/animation.py +31 -22
  43. pyTEMlib/atom_tools.py +29 -34
  44. pyTEMlib/config_dir.py +2 -28
  45. pyTEMlib/crystal_tools.py +129 -165
  46. pyTEMlib/eds_tools.py +559 -342
  47. pyTEMlib/eds_xsections.py +432 -0
  48. pyTEMlib/eels_tools/__init__.py +44 -0
  49. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  50. pyTEMlib/eels_tools/eels_database.py +134 -0
  51. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  52. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  53. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  54. pyTEMlib/file_reader.py +274 -0
  55. pyTEMlib/file_tools.py +260 -1130
  56. pyTEMlib/get_bote_salvat.py +69 -0
  57. pyTEMlib/graph_tools.py +101 -174
  58. pyTEMlib/graph_viz.py +150 -0
  59. pyTEMlib/image/__init__.py +37 -0
  60. pyTEMlib/image/image_atoms.py +270 -0
  61. pyTEMlib/image/image_clean.py +197 -0
  62. pyTEMlib/image/image_distortion.py +299 -0
  63. pyTEMlib/image/image_fft.py +277 -0
  64. pyTEMlib/image/image_graph.py +926 -0
  65. pyTEMlib/image/image_registration.py +316 -0
  66. pyTEMlib/image/image_utilities.py +309 -0
  67. pyTEMlib/image/image_window.py +421 -0
  68. pyTEMlib/image_tools.py +154 -915
  69. pyTEMlib/kinematic_scattering.py +1 -1
  70. pyTEMlib/probe_tools.py +1 -1
  71. pyTEMlib/test.py +437 -0
  72. pyTEMlib/utilities.py +314 -0
  73. pyTEMlib/version.py +2 -3
  74. pyTEMlib/xrpa_x_sections.py +14 -10
  75. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
  76. pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
  77. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
  78. pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
  79. pyTEMlib/core_loss_widget.py +0 -721
  80. pyTEMlib/eels_dialog.py +0 -754
  81. pyTEMlib/eels_dialog_utilities.py +0 -1199
  82. pyTEMlib/eels_tools.py +0 -2359
  83. pyTEMlib/file_tools_qt.py +0 -193
  84. pyTEMlib/image_dialog.py +0 -158
  85. pyTEMlib/image_dlg.py +0 -146
  86. pyTEMlib/info_widget.py +0 -1086
  87. pyTEMlib/info_widget3.py +0 -1120
  88. pyTEMlib/low_loss_widget.py +0 -479
  89. pyTEMlib/peak_dialog.py +0 -1129
  90. pyTEMlib/peak_dlg.py +0 -286
  91. pytemlib-0.2025.4.1.dist-info/RECORD +0 -38
  92. pytemlib-0.2025.4.1.dist-info/top_level.txt +0 -1
  93. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
  94. {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,316 @@
1
+ """
2
+ image_registration.py
3
+ by Gerd Duscher, UTK
4
+ part of pycroscopy.image
5
+ MIT license except where stated differently
6
+ """
7
+
8
+ import typing
9
+
10
+ import numpy as np
11
+ import skimage
12
+ import scipy
13
+
14
+ from tqdm.auto import trange
15
+
16
+ import sidpy
17
+ _SIMPLEITK_PRESENT = True
18
+ try:
19
+ import SimpleITK
20
+ except ModuleNotFoundError:
21
+ _SIMPLEITK_PRESENT = False
22
+ if not _SIMPLEITK_PRESENT:
23
+ print('SimpleITK not installed; Registration Functions for Image Stacks not available')
24
+
25
+ #####################################################
26
+ # Registration Functions
27
+ #####################################################
28
+
29
+ def complete_registration(main_dataset: sidpy.Dataset) -> typing.Tuple[sidpy.Dataset,
30
+ sidpy.Dataset]:
31
+ """Rigid and then non-rigid (demon) registration
32
+
33
+ Performs rigid and then non-rigid registration, please see individual functions:
34
+ - rigid_registration
35
+ - demon_registration
36
+
37
+ Parameters
38
+ ----------
39
+ main_dataset: sidpy.Dataset
40
+ dataset of data_type 'IMAGE_STACK' to be registered
41
+
42
+ Returns
43
+ -------
44
+ non_rigid_registered: sidpy.Dataset
45
+ rigid_registered_dataset: sidpy.Dataset
46
+
47
+ """
48
+
49
+ if main_dataset.data_type.name != 'IMAGE_STACK':
50
+ raise TypeError('Registration makes only sense for an image stack')
51
+
52
+ rigid_registered_dataset = rigid_registration(main_dataset)
53
+
54
+ rigid_registered_dataset.data_type = 'IMAGE_STACK'
55
+
56
+ non_rigid_registered = demon_registration(rigid_registered_dataset)
57
+ return non_rigid_registered, rigid_registered_dataset
58
+
59
+
60
+ def demon_registration(dataset: sidpy.Dataset, verbose: bool=False) -> sidpy.Dataset:
61
+ """
62
+ Diffeomorphic Demon Non-Rigid Registration
63
+
64
+ Depends on:
65
+ simpleITK and numpy
66
+ Please Cite: http://www.simpleitk.org/SimpleITK/project/parti.html
67
+ and T. Vercauteren, X. Pennec, A. Perchant and N. Ayache
68
+ Diffeomorphic Demons Using ITK\'s Finite Difference Solver Hierarchy
69
+ The Insight Journal, http://hdl.handle.net/1926/510 2007
70
+
71
+ Parameters
72
+ ----------
73
+ dataset: sidpy.Dataset
74
+ stack of image after rigid registration and cropping
75
+ verbose: boolean
76
+ optional for increased output
77
+ Returns
78
+ -------
79
+ dem_reg: stack of images with non-rigid registration
80
+
81
+ Example
82
+ -------
83
+ dem_reg = demon_reg(stack_dataset, verbose=False)
84
+ """
85
+
86
+ if dataset.data_type.name != 'IMAGE_STACK':
87
+ raise TypeError('Registration makes only sense for an image stack')
88
+
89
+ dem_reg = np.zeros(dataset.shape)
90
+ nimages = dataset.shape[0]
91
+ if verbose:
92
+ print(nimages)
93
+ # create fixed image by summing over rigid registration
94
+
95
+ fixed_np = np.average(np.array(dataset), axis=0)
96
+
97
+ if not _SIMPLEITK_PRESENT:
98
+ print('This feature is not available:')
99
+ print('Please install simpleITK with: conda install simpleitk -c simpleitk')
100
+
101
+ fixed = SimpleITK.GetImageFromArray(fixed_np)
102
+ fixed = SimpleITK.DiscreteGaussian(fixed, 2.0)
103
+
104
+ demons = SimpleITK.DiffeomorphicDemonsRegistrationFilter()
105
+
106
+ demons.SetNumberOfIterations(200)
107
+ demons.SetStandardDeviations(1.0)
108
+
109
+ resampler = SimpleITK.ResampleImageFilter()
110
+ resampler.SetReferenceImage(fixed)
111
+ resampler.SetInterpolator(SimpleITK.sitkBSpline)
112
+ resampler.SetDefaultPixelValue(0)
113
+
114
+ for i in trange(nimages):
115
+ moving = SimpleITK.GetImageFromArray(dataset[i])
116
+ moving_f = SimpleITK.DiscreteGaussian(moving, 2.0)
117
+ displacement_field = demons.Execute(fixed, moving_f)
118
+ out_tx = SimpleITK.DisplacementFieldTransform(displacement_field)
119
+ resampler.SetTransform(out_tx)
120
+ out = resampler.Execute(moving)
121
+ dem_reg[i, :, :] = SimpleITK.GetArrayFromImage(out)
122
+
123
+ print(':-)')
124
+ print('You have successfully completed Diffeomorphic Demons Registration')
125
+
126
+ demon_registered = dataset.like_data(dem_reg)
127
+ demon_registered.title = 'Non-Rigid Registration'
128
+ demon_registered.source = dataset.title
129
+
130
+ demon_registered.metadata =dataset.metadata.copy()
131
+ if 'analysis' not in demon_registered.metadata:
132
+ demon_registered.metadata['analysis'] = {}
133
+ demon_registered.metadata['analysis']['non_rigid_demon_registration'] = {'package': 'simpleITK',
134
+ 'method': 'DiscreteGaussian',
135
+ 'variance': 2,
136
+ 'input_dataset': dataset.source}
137
+ demon_registered.data_type = 'IMAGE_STACK'
138
+ return demon_registered
139
+
140
+
141
+ # ##############################
142
+ # Rigid Registration New 05/09/2024
143
+ # ##############################
144
+ def rigid_registration(dataset: sidpy.Dataset, normalization: typing.Optional[str] = None) -> sidpy.Dataset:
145
+ """
146
+ Rigid registration of image stack with pixel accuracy
147
+
148
+ Uses simple cross_correlation
149
+ (we determine drift from one image to next)
150
+
151
+ Parameters
152
+ ----------
153
+ dataset: sidpy.Dataset
154
+ sidpy dataset with image_stack dataset
155
+ normalization: str or None
156
+ if 'phase' then phase cross correlation is used, otherwise
157
+ normalized cross correlation is used
158
+
159
+ Returns
160
+ -------
161
+ rigid_registered: sidpy.Dataset
162
+ Registered Stack and drift (with respect to center image)
163
+ """
164
+
165
+ if dataset.data_type.name != 'IMAGE_STACK':
166
+ raise TypeError('Registration makes only sense for an image stack')
167
+
168
+ if isinstance (normalization, str):
169
+ if normalization.lower() != 'phase':
170
+ normalization = None
171
+ else:
172
+ normalization = None
173
+ image_dimensions = dataset.get_image_dims(return_axis=True)
174
+ if dataset.get_dimensions_by_type('TEMPORAL')[0] != 0:
175
+ x = image_dimensions[0]
176
+ y = image_dimensions[1]
177
+ z = dataset.get_dimensions_by_type('TEMPORAL', return_axis=True)[0]
178
+ metadata = dataset.metadata.copy()
179
+ original_metadata = dataset.original_metadata.copy()
180
+ arr = np.rollaxis(np.array(dataset), 2, 0)
181
+ dataset = sidpy.Dataset.from_array(arr, title=dataset.title, data_type='IMAGE_STACK',
182
+ quantity=dataset.quantity, units=dataset.units)
183
+ dataset.set_dimension(0, sidpy.Dimension(z.values, name='frame', units='frame', quantity='time',
184
+ dimension_type='temporal'))
185
+ dataset.set_dimension(1, x)
186
+ dataset.set_dimension(2, y)
187
+ dataset.metadata = metadata
188
+ dataset.original_metadata = original_metadata
189
+
190
+ stack_dim = dataset.get_dimensions_by_type('TEMPORAL', return_axis=True)[0]
191
+ image_dim = dataset.get_image_dims(return_axis=True)
192
+ if len(image_dim) != 2:
193
+ raise ValueError('need at least two SPATIAL dimension for an image stack')
194
+
195
+ relative_drift = [[0., 0.]]
196
+ im1 = np.fft.fft2(np.array(dataset[0]))
197
+ for i in range(1, len(stack_dim)):
198
+ im2 = np.fft.fft2(np.array(dataset[i]))
199
+ shift, error, _ = skimage.registration.phase_cross_correlation(im1, im2,
200
+ normalization=normalization,
201
+ space='fourier')
202
+ im1 = im2.copy()
203
+ relative_drift.append(shift)
204
+
205
+ rig_reg, drift = rig_reg_drift(dataset, relative_drift)
206
+ crop_reg, input_crop = crop_image_stack(rig_reg, drift)
207
+
208
+ rigid_registered = sidpy.Dataset.from_array(crop_reg,
209
+ title='Rigid Registration',
210
+ data_type='IMAGE_STACK',
211
+ quantity=dataset.quantity,
212
+ units=dataset.units)
213
+ rigid_registered.title = 'Rigid_Registration'
214
+ rigid_registered.source = dataset.title
215
+ rigid_registered.metadata['analysis'] = {'rigid_registration': {'drift': drift,
216
+ 'input_crop': input_crop, 'input_shape': dataset.shape[1:]}}
217
+
218
+ if 'experiment' in dataset.metadata:
219
+ rigid_registered.metadata['experiment'] = dataset.metadata['experiment'].copy()
220
+ rigid_registered.set_dimension(0, sidpy.Dimension(np.arange(rigid_registered.shape[0]),
221
+ name='frame', units='frame', quantity='time',
222
+ dimension_type='temporal'))
223
+
224
+ array_x = image_dim[0].values[input_crop[0]:input_crop[1]]
225
+ rigid_registered.set_dimension(1, sidpy.Dimension(array_x, name='x',
226
+ units='nm', quantity='Length',
227
+ dimension_type='spatial'))
228
+ array_y =image_dim[1].values[input_crop[2]:input_crop[3]]
229
+ rigid_registered.set_dimension(2, sidpy.Dimension(array_y, name='y',
230
+ units='nm', quantity='Length',
231
+ dimension_type='spatial'))
232
+ rigid_registered.data_type = 'IMAGE_STACK'
233
+ return rigid_registered.rechunk({0: 'auto', 1: -1, 2: -1})
234
+
235
+
236
+ def rig_reg_drift(dset: sidpy.Dataset,
237
+ rel_drift: typing.Union[typing.List[typing.List[float]], np.ndarray]
238
+ ) -> typing.Tuple[np.ndarray, np.ndarray]:
239
+ """ Shifting images on top of each other
240
+
241
+ Uses relative drift to shift images on top of each other,
242
+ with center image as reference.
243
+ Shifting is done with shift routine of ndimage from scipy.
244
+ This function is used by rigid_registration routine
245
+
246
+ Parameters
247
+ ----------
248
+ dset: sidpy.Dataset
249
+ dataset with image_stack
250
+ rel_drift:
251
+ relative_drift from image to image as list of [shiftx, shifty]
252
+
253
+ Returns
254
+ -------
255
+ stack: numpy array
256
+ drift: list of drift in pixel
257
+ """
258
+
259
+ frame_dim = []
260
+ spatial_dim = []
261
+ selection = []
262
+
263
+ for i, axis in dset._axes.items():
264
+ if axis.dimension_type.name == 'SPATIAL':
265
+ spatial_dim.append(i)
266
+ selection.append(slice(None))
267
+ else:
268
+ frame_dim.append(i)
269
+ selection.append(slice(0, 1))
270
+
271
+ if len(spatial_dim) != 2:
272
+ print('need two spatial dimensions')
273
+ if len(frame_dim) != 1:
274
+ print('need one frame dimensions')
275
+
276
+ rig_reg = np.zeros([dset.shape[frame_dim[0]], dset.shape[spatial_dim[0]], dset.shape[spatial_dim[1]]])
277
+
278
+ # absolute drift
279
+ drift = np.array(rel_drift).copy()
280
+
281
+ drift[0] = [0, 0]
282
+ for i in range(1, drift.shape[0]):
283
+ drift[i] = drift[i - 1] + rel_drift[i]
284
+ center_drift = drift[int(drift.shape[0] / 2)]
285
+ drift = drift - center_drift
286
+ # Shift images
287
+ for i in range(rig_reg.shape[0]):
288
+ selection[frame_dim[0]] = slice(i, i+1)
289
+ # Now we shift
290
+ rig_reg[i, :, :] = scipy.ndimage.shift(dset[tuple(selection)].squeeze().compute(),
291
+ [drift[i, 0], drift[i, 1]], order=3)
292
+ return rig_reg, drift
293
+
294
+
295
+
296
+ def crop_image_stack(rig_reg: np.ndarray, drift: typing.Union[np.ndarray, list]
297
+ ) -> typing.Tuple[np.ndarray, list[int]]:
298
+ """Crop images in stack according to drift
299
+
300
+ This function is used by rigid_registration routine
301
+
302
+ Parameters
303
+ ----------
304
+ rig_reg: numpy array (N,x,y)
305
+ drift: list (2,B)
306
+
307
+ Returns
308
+ -------
309
+ numpy array
310
+ """
311
+ xpmax = int(rig_reg.shape[1] - -np.floor(np.min(np.array(drift)[:, 0])))
312
+ xpmin = int(np.ceil(np.max(np.array(drift)[:, 0])))
313
+ ypmax = int(rig_reg.shape[1] - -np.floor(np.min(np.array(drift)[:, 1])))
314
+ ypmin = int(np.ceil(np.max(np.array(drift)[:, 1])))
315
+
316
+ return rig_reg[:, xpmin:xpmax, ypmin:ypmax:], [xpmin, xpmax, ypmin, ypmax]
@@ -0,0 +1,309 @@
1
+ """
2
+ image_utilities part of image package of pycroscopy
3
+
4
+ """
5
+ import numpy as np
6
+ import scipy
7
+ from skimage.restoration import inpaint
8
+
9
+ import sidpy
10
+
11
+ def crop_image(dataset: sidpy.Dataset, corners: np.ndarray) -> sidpy.Dataset:
12
+ """
13
+ Crops an image according to the corners given in the format of
14
+ matplotlib.widget.RectangleSelector.
15
+
16
+ Parameters
17
+ ----------
18
+ dataset: sidpy.Dataset
19
+ An instance of sidpy.Dataset representing the image to be cropped.
20
+ corners: np.ndarray
21
+ A 1D array of length 4 containing the corners of the rectangular region
22
+ to be cropped. The order of the corners should be (x1, y1, x2, y2).
23
+
24
+ Returns
25
+ -------
26
+ sidpy.Dataset
27
+ A new instance of sidpy.Dataset representing the cropped image.
28
+
29
+ Raises
30
+ ------
31
+ ValueError
32
+ If dataset is not an instance of sidpy.Dataset or if dataset is not an image
33
+ dataset. If corners parameter is not of correct shape or size.
34
+
35
+ """
36
+ if not isinstance(dataset, sidpy.Dataset):
37
+ raise ValueError('Input dataset is not an instance of sidpy.Dataset')
38
+ if not dataset.data_type.name == 'IMAGE':
39
+ raise ValueError('Only image datasets are supported at this point')
40
+
41
+ if corners.shape != (4,):
42
+ raise ValueError(f'Input corners parameter should have shape (4,) but got shape {corners.shape}')
43
+ if corners[2]-corners[0] <= 0 or corners[3]-corners[1] <= 0:
44
+ raise ValueError('Invalid input corners parameter')
45
+
46
+ pixel_size = np.array([dataset.x[1]-dataset.x[0], dataset.y[1]-dataset.y[0]])
47
+ corners /= pixel_size
48
+
49
+ selection = np.stack([np.min(corners[:2])+0.5, np.max(corners[2:])+0.5]).astype(int)
50
+
51
+ cropped_dset = dataset.like_data(dataset[selection[0, 0]:selection[1, 0],
52
+ selection[0, 1]:selection[1, 1]])
53
+ cropped_dset.title = 'cropped_' + dataset.title
54
+ cropped_dset.source = dataset.title
55
+ cropped_dset.metadata = {'crop_dimension': selection, 'original_dimensions': dataset.shape}
56
+
57
+ return cropped_dset
58
+
59
+
60
+ def flatten_image(sid_dset, order=1, flatten_axis = 'row', method = 'line_fit'):
61
+ """
62
+ Flattens an image according to the method chosen. Used heavily for AFM/STM images
63
+
64
+ Parameters
65
+ ----------
66
+ dataset: sidpy.Dataset
67
+ An instance of sidpy.Dataset representing the image to be flattened.
68
+ order: integer,
69
+ Optional, default = 1. Ordfor the polynomial fit.
70
+ flatten_axis: string,
71
+ Optional, default = 'row'. Axis along which to flatten the image.
72
+ method: string,
73
+ Optional, default = 'line_fit'. Method to use for flattening the image.
74
+
75
+ Returns
76
+ -------
77
+ sidpy.Dataset
78
+ A new instance of sidpy.Dataset representing the flattened image.
79
+ """
80
+ #TODO: lots of cleanup in this function required...
81
+ new_sid_dset = sid_dset.copy()
82
+ assert len(new_sid_dset._axes) == 2, "Dataset must be 2-D for this function"
83
+ assert new_sid_dset.data_type == sidpy.DataType.IMAGE, "Dataset must IMAGE for this function"
84
+ #check the spatial dimensions, flatten along each row
85
+ if flatten_axis == 'row':
86
+ num_pts = sid_dset.shape[0] # this is hard coded, it shouldn't be
87
+ elif flatten_axis == 'col':
88
+ num_pts = sid_dset.shape[1] # this is hard coded, but it shouldn't be
89
+ else:
90
+ raise ValueError(f"Gave flatten axis of {flatten_axis} but only 'row', 'col' are allowed")
91
+
92
+ data_flat = np.zeros(sid_dset.shape) #again this should be the spatial (2 dimensional) part only
93
+ print(sid_dset.shape, num_pts)
94
+ if method == 'line_fit':
95
+ for line in range(num_pts):
96
+ if flatten_axis=='row':
97
+ line_data = np.array(sid_dset[:])[line,:]
98
+ elif flatten_axis=='col':
99
+ line_data = np.array(sid_dset[:])[:,line]
100
+ p = np.polyfit(np.arange(len(line_data)), line_data,order)
101
+ lin_est = np.polyval(p,np.arange(len(line_data)))
102
+ new_line = line_data - lin_est
103
+ data_flat[line] = new_line
104
+ elif method == 'plane_fit':
105
+ #TODO: implement plane fit
106
+ pass
107
+ else:
108
+ raise ValueError("Gave method of {method} but only 'line_fit', 'plane_fit' are allowed")
109
+
110
+ new_sid_dset[:] = data_flat
111
+
112
+
113
+ def rebin(im, binning=2):
114
+ """
115
+ rebin an image by the number of pixels in x and y direction given by binning
116
+
117
+ Parameter
118
+ ---------
119
+ image: numpy array in 2 dimensions or sidpy.Dataset of data_type 'Image'
120
+
121
+ Returns
122
+ -------
123
+ binned image as numpy array or sidpy.Dataset
124
+ """
125
+ if len(im.shape) == 2:
126
+ rebinned_image = np.array(im).reshape((im.shape[0]//binning,
127
+ binning, im.shape[1]//binning,
128
+ binning)).mean(axis=3).mean(1)
129
+ if isinstance(im, sidpy.Dataset):
130
+ rebinned_image = im.like_data(rebinned_image)
131
+ rebinned_image.title = 'rebinned_' + im.title
132
+ rebinned_image.data_type = 'image'
133
+ im_dims = im.get_image_dims(return_axis=True)
134
+
135
+ rebinned_image.set_dimension(0, sidpy.Dimension(np.arange(rebinned_image.shape[0])/im_dims[0].slope,
136
+ name='x', units=im_dims[0].units,
137
+ dimension_type=im_dims[0].dimension_type,
138
+ quantity=im_dims[0].quantity))
139
+ rebinned_image.set_dimension(1, sidpy.Dimension(np.arange(rebinned_image.shape[1])/im_dims[1].slope,
140
+ name='y', units=im_dims[1].units,
141
+ dimension_type=im_dims[1].dimension_type,
142
+ quantity=im_dims[1].quantity))
143
+ return rebinned_image
144
+ else:
145
+ raise TypeError('not a 2D image')
146
+
147
+
148
+ def cart2pol(points):
149
+ """Cartesian to polar coordinate conversion
150
+
151
+ Parameters
152
+ ---------
153
+ points: float or numpy array
154
+ points to be converted (Nx2)
155
+
156
+ Returns
157
+ -------
158
+ rho: float or numpy array
159
+ distance
160
+ phi: float or numpy array
161
+ angle
162
+ """
163
+
164
+ rho = np.linalg.norm(points[:, 0:2], axis=1)
165
+ phi = np.arctan2(points[:, 1], points[:, 0])
166
+
167
+ return rho, phi
168
+
169
+
170
+ def pol2cart(rho, phi):
171
+ """Polar to Cartesian coordinate conversion
172
+
173
+ Parameters
174
+ ----------
175
+ rho: float or numpy array
176
+ distance
177
+ phi: float or numpy array
178
+ angle
179
+
180
+ Returns
181
+ -------
182
+ x: float or numpy array
183
+ x coordinates of converted points(Nx2)
184
+ """
185
+
186
+ x = rho * np.cos(phi)
187
+ y = rho * np.sin(phi)
188
+ return x, y
189
+
190
+
191
+ def xy2polar(points, rounding=1e-3):
192
+ """ Conversion from carthesian to polar coordinates
193
+
194
+ the angles and distances are sorted by r and then phi
195
+ The indices of this sort is also returned
196
+
197
+ Parameters
198
+ ----------
199
+ points: numpy array
200
+ number of points in axis 0 first two elements in axis 1 are x and y
201
+ rounding: int
202
+ optional rounding in significant digits
203
+
204
+ Returns
205
+ -------
206
+ r, phi, sorted_indices
207
+ """
208
+
209
+ r, phi = cart2pol(points)
210
+
211
+ r = (np.floor(r/rounding))*rounding # Remove rounding error differences
212
+
213
+ sorted_indices = np.lexsort((phi, r)) # sort first by r and then by phi
214
+ r = r[sorted_indices]
215
+ phi = phi[sorted_indices]
216
+
217
+ return r, phi, sorted_indices
218
+
219
+
220
+ def cartesian2polar(x, y, grid, r, t, order=3):
221
+ """Transform cartesian grid to polar grid
222
+
223
+ Used by warp
224
+ """
225
+
226
+ rr, tt = np.meshgrid(r, t)
227
+
228
+ new_x = rr*np.cos(tt)
229
+ new_y = rr*np.sin(tt)
230
+
231
+ ix = scipy.interpolate.interp1d(x, np.arange(len(x)))
232
+ iy = scipy.interpolate.interp1d(y, np.arange(len(y)))
233
+
234
+ new_ix = ix(new_x.ravel())
235
+ new_iy = iy(new_y.ravel())
236
+
237
+ return scipy.ndimage.map_coordinates(grid, np.array([new_ix, new_iy]),
238
+ order=order).reshape(new_x.shape)
239
+
240
+
241
+ def warp(diff, center):
242
+ """Takes a diffraction pattern (as a sidpy dataset)and warps it to a polar grid"""
243
+
244
+ # Define original polar grid
245
+ nx = np.shape(diff)[0]
246
+ ny = np.shape(diff)[1]
247
+
248
+ x = np.linspace(1, nx, nx, endpoint=True)-center[0]
249
+ y = np.linspace(1, ny, ny, endpoint=True)-center[1]
250
+ z = diff
251
+
252
+ # Define new polar grid
253
+ nr = int(min([center[0], center[1], diff.shape[0]-center[0], diff.shape[1]-center[1]])-1)
254
+ nt = 360 * 3
255
+
256
+ r = np.linspace(1, nr, nr)
257
+ t = np.linspace(0., np.pi, nt, endpoint=False)
258
+
259
+ return cartesian2polar(x, y, z, r, t, order=3).T
260
+
261
+
262
+ def inpaint_image(sid_dset, mask = None, channel = None):
263
+ """Inpaints a sparse image, given a mask.
264
+
265
+ Args:
266
+ sid_dset (_type_): sidpy Dataset
267
+ with two dimensions being of spatial or reciprocal type
268
+ mask (np.ndarry) : mask [0,1] same shape as sid_dset.
269
+ If providing a sidpy dataset and mask is in the metadata dict,
270
+ then this entry is optional
271
+ channel (int): (optional) for multi-channel datasets,
272
+ provide the channel to in-paint
273
+ """
274
+ if len(sid_dset.shape)==2:
275
+ image_data = np.array(sid_dset).squeeze()
276
+ elif len(sid_dset.shape)==3:
277
+ image_dims = []
278
+ selection = []
279
+ for dim, axis in sid_dset._axes.items():
280
+ if axis.dimension_type in [sidpy.DimensionType.SPATIAL, sidpy.DimensionType.RECIPROCAL]:
281
+ selection.append(slice(None))
282
+ image_dims.append(dim)
283
+ else:
284
+ if channel is None:
285
+ channel=0
286
+ selection.append(slice(channel, channel+1))
287
+
288
+ image_data = np.array(sid_dset[tuple(selection)]).squeeze()
289
+ if mask is None:
290
+ mask_data = sid_dset.metadata["mask"]
291
+ mask = np.copy(mask_data)
292
+ mask[mask==1] = -1
293
+ mask[mask==0] = 1
294
+ mask[mask==-1] = 0
295
+
296
+ inpainted_data = inpaint.inpaint_biharmonic(image_data, mask)
297
+
298
+ #convert this into a sidpy dataset
299
+ data_set = sidpy.Dataset.from_array(inpainted_data, name='inpainted_image')
300
+ data_set.data_type = 'image' # supported
301
+
302
+ data_set.units = sid_dset.units
303
+ data_set.quantity = sid_dset.quantity
304
+
305
+ data_set.set_dimension(0, sid_dset.get_dimension_by_number(image_dims[0])[0])
306
+ data_set.set_dimension(1, sid_dset.get_dimension_by_number(image_dims[1])[0])
307
+
308
+ data_set.metadata["mask"] = mask
309
+ return data_set