pymodaq_utils 0.0.2__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.
- pymodaq_utils/__init__.py +35 -0
- pymodaq_utils/abstract.py +48 -0
- pymodaq_utils/array_manipulation.py +386 -0
- pymodaq_utils/config.py +379 -0
- pymodaq_utils/enums.py +72 -0
- pymodaq_utils/factory.py +82 -0
- pymodaq_utils/icon.ico +0 -0
- pymodaq_utils/logger.py +78 -0
- pymodaq_utils/math_utils.py +584 -0
- pymodaq_utils/resources/__init__.py +0 -0
- pymodaq_utils/resources/config_template.toml +96 -0
- pymodaq_utils/resources/hatch_build_plugins.py +41 -0
- pymodaq_utils/splash.png +0 -0
- pymodaq_utils/units.py +216 -0
- pymodaq_utils/utils.py +649 -0
- pymodaq_utils/warnings.py +10 -0
- pymodaq_utils-0.0.2.dist-info/METADATA +152 -0
- pymodaq_utils-0.0.2.dist-info/RECORD +20 -0
- pymodaq_utils-0.0.2.dist-info/WHEEL +4 -0
- pymodaq_utils-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import numbers
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numbers import Number
|
|
7
|
+
from typing import List, Union, Tuple
|
|
8
|
+
from pymodaq_utils.logger import get_module_name, set_logger
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
|
|
11
|
+
logger = set_logger(get_module_name(__file__))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def my_moment(x, y):
|
|
15
|
+
"""Returns the moments of a distribution y over an axe x
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
x: list or ndarray
|
|
20
|
+
vector of floats
|
|
21
|
+
y: list or ndarray
|
|
22
|
+
vector of floats corresponding to the x axis
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
m: list
|
|
27
|
+
Contains moment of order 0 (mean) and of order 1 (std) of the distribution y
|
|
28
|
+
"""
|
|
29
|
+
dx = np.mean(np.diff(x))
|
|
30
|
+
norm = np.sum(y) * dx
|
|
31
|
+
m = [np.sum(x * y) * dx / norm]
|
|
32
|
+
m.extend([np.sqrt(np.sum((x - m[0]) ** 2 * y) * dx / norm)])
|
|
33
|
+
return m
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def normalize(x):
|
|
37
|
+
x = x - np.min(x)
|
|
38
|
+
x = x / np.max(x)
|
|
39
|
+
return x
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def normalize_to(array: np.ndarray, value: numbers.Number):
|
|
43
|
+
return normalize(array) * value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def odd_even(x: Union[int, np.ndarray]):
|
|
47
|
+
"""
|
|
48
|
+
odd_even tells if a number (or an array) is odd (return True) or even (return False)
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
x: the integer number (or array) to test
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
bool : boolean or array of boolean
|
|
57
|
+
"""
|
|
58
|
+
if not (isinstance(x, int) or (isinstance(x, np.ndarray) and x.dtype == int)):
|
|
59
|
+
raise TypeError(f'{x} should be an integer or an array of integers')
|
|
60
|
+
if isinstance(x, int):
|
|
61
|
+
if x % 2 == 0:
|
|
62
|
+
isodd = False
|
|
63
|
+
else:
|
|
64
|
+
isodd = True
|
|
65
|
+
else:
|
|
66
|
+
isodd = x % 2 != 0
|
|
67
|
+
return isodd
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def greater2n(x):
|
|
71
|
+
"""
|
|
72
|
+
return the first power of 2 greater than x
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
x: (int or float) a number
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
int: the power of 2 greater than x
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(x, bool):
|
|
82
|
+
raise TypeError(f'{x} should be an integer or a float')
|
|
83
|
+
if hasattr(x, '__iter__'):
|
|
84
|
+
res = []
|
|
85
|
+
for el in x:
|
|
86
|
+
if isinstance(el, bool):
|
|
87
|
+
raise TypeError(f'{el} should be an integer or a float')
|
|
88
|
+
if not (isinstance(el, int) or isinstance(el, float)):
|
|
89
|
+
raise TypeError(f'{x} elements should be integer or float')
|
|
90
|
+
res.append(1 << (int(el) - 1).bit_length())
|
|
91
|
+
if isinstance(x, np.ndarray):
|
|
92
|
+
return np.array(res)
|
|
93
|
+
else:
|
|
94
|
+
return res
|
|
95
|
+
else:
|
|
96
|
+
if not (isinstance(x, int) or isinstance(x, float)):
|
|
97
|
+
raise TypeError(f'{x} should be an integer or a float')
|
|
98
|
+
return 1 << (int(x) - 1).bit_length()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def linspace_step(start, stop, step):
|
|
102
|
+
"""
|
|
103
|
+
Compute a regular linspace_step distribution from start to stop values.
|
|
104
|
+
|
|
105
|
+
=============== =========== ======================================
|
|
106
|
+
**Parameters** **Type** **Description**
|
|
107
|
+
*start* scalar the starting value of distribution
|
|
108
|
+
*stop* scalar the stopping value of distribution
|
|
109
|
+
*step* scalar the length of a distribution step
|
|
110
|
+
=============== =========== ======================================
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
|
|
115
|
+
scalar array
|
|
116
|
+
The computed distribution axis as an array.
|
|
117
|
+
"""
|
|
118
|
+
if np.sign(stop - start) != np.sign(step) or start == stop:
|
|
119
|
+
raise ValueError('Invalid value for one parameter')
|
|
120
|
+
Nsteps = int(np.ceil((stop - start) / step))
|
|
121
|
+
new_stop = start + (Nsteps - 1) * step
|
|
122
|
+
if np.abs(new_stop + step - stop) < 1e-12:
|
|
123
|
+
Nsteps += 1
|
|
124
|
+
new_stop = start + (Nsteps - 1) * step
|
|
125
|
+
return np.linspace(start, new_stop, Nsteps)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def linspace_step_N(start, step, Npts):
|
|
129
|
+
stop = (Npts - 1) * step + start
|
|
130
|
+
return linspace_step(start, stop, step)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def find_index(x, threshold: Union[Number, List[Number]]) -> List[tuple]:
|
|
134
|
+
"""find_index finds the index ix such that x(ix) is the closest from threshold
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
x : vector
|
|
139
|
+
threshold : list of real numbers
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
out : list of 2-tuple containing ix,x[ix]
|
|
144
|
+
out=[(ix0,xval0),(ix1,xval1),...]
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
if not hasattr(threshold, '__iter__'):
|
|
148
|
+
threshold = [threshold]
|
|
149
|
+
out = []
|
|
150
|
+
for value in threshold:
|
|
151
|
+
ix = int(np.argmin(np.abs(x - value)))
|
|
152
|
+
out.append((ix, x[ix]))
|
|
153
|
+
return out
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def find_common_index(x: Iterable, y: Iterable, x0: Number, y0: Number) -> Tuple:
|
|
157
|
+
"""find the index in two vectors corresponding to x0 and y0"""
|
|
158
|
+
vals = x + 1j * y
|
|
159
|
+
val = x0 + 1j * y0
|
|
160
|
+
ind = int(np.argmin(np.abs(vals - val)))
|
|
161
|
+
return ind, x[ind], y[ind]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def gauss1D(x, x0, dx, n=1):
|
|
165
|
+
"""
|
|
166
|
+
compute the gaussian function along a vector x, centered in x0 and with a
|
|
167
|
+
FWHM i intensity of dx. n=1 is for the standart gaussian while n>1 defines
|
|
168
|
+
a hypergaussian
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
x: (ndarray) first axis of the 2D gaussian
|
|
173
|
+
x0: (float) the central position of the gaussian
|
|
174
|
+
dx: (float) :the FWHM of the gaussian
|
|
175
|
+
n=1 : an integer to define hypergaussian, n=1 by default for regular gaussian
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
out : vector
|
|
179
|
+
the value taken by the gaussian along x axis
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
if dx <= 0:
|
|
183
|
+
raise ValueError('dx should be strictly positive')
|
|
184
|
+
if not isinstance(n, int):
|
|
185
|
+
raise TypeError('n should be a positive integer')
|
|
186
|
+
elif n < 0:
|
|
187
|
+
raise ValueError('n should be a positive integer')
|
|
188
|
+
out = np.exp(-2 * np.log(2) ** (1 / n) * (((x - x0) / dx)) ** (2 * n))
|
|
189
|
+
return out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def gauss2D(x, x0, dx, y, y0, dy, n=1, angle=0):
|
|
193
|
+
"""
|
|
194
|
+
compute the 2D gaussian function along a vector x, centered in x0 and with a
|
|
195
|
+
FWHM in intensity of dx and smae along y axis. n=1 is for the standard gaussian while n>1 defines
|
|
196
|
+
a hypergaussian. optionally rotate it by an angle in degree
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
x: (ndarray) first axis of the 2D gaussian
|
|
201
|
+
x0: (float) the central position of the gaussian
|
|
202
|
+
dx: (float) :the FWHM of the gaussian
|
|
203
|
+
y: (ndarray) second axis of the 2D gaussian
|
|
204
|
+
y0: (float) the central position of the gaussian
|
|
205
|
+
dy: (float) :the FWHM of the gaussian
|
|
206
|
+
n=1 : an integer to define hypergaussian, n=1 by default for regular gaussian
|
|
207
|
+
angle: (float) a float to rotate main axes, in degree
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
out : ndarray 2 dimensions
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
if angle == 0:
|
|
215
|
+
data = np.transpose(np.outer(gauss1D(x, x0, dx, n), gauss1D(y, y0, dy, n)))
|
|
216
|
+
|
|
217
|
+
else:
|
|
218
|
+
|
|
219
|
+
theta = np.radians(angle)
|
|
220
|
+
c, s = np.cos(theta), np.sin(theta)
|
|
221
|
+
R = np.array(((c, -s), (s, c)))
|
|
222
|
+
(x0r, y0r) = tuple(R.dot(np.array([x0, y0])))
|
|
223
|
+
|
|
224
|
+
data = np.zeros((len(y), len(x)))
|
|
225
|
+
|
|
226
|
+
for indx, xtmp in enumerate(x):
|
|
227
|
+
for indy, ytmp in enumerate(y):
|
|
228
|
+
rotatedvect = R.dot(np.array([xtmp, ytmp]))
|
|
229
|
+
data[indy, indx] = gauss1D(rotatedvect[0], x0r, dx, n) * gauss1D(rotatedvect[1], y0r, dy, n)
|
|
230
|
+
|
|
231
|
+
return data
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def rotate2D(origin:tuple = (0,0), point:tuple = (0,0), angle:float = 0):
|
|
235
|
+
"""
|
|
236
|
+
Rotate a point counterclockwise by a given angle around a given origin.
|
|
237
|
+
|
|
238
|
+
The angle should be given in radians.
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
origin: (tuple) x,y coordinate from which to rotate
|
|
242
|
+
point: (tuple) x,y coordinate of point to rotate
|
|
243
|
+
angle: (float) a float to rotate main axes, in radian
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
out : (tuple) x,y coordinate of rotated point
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
ox, oy = origin
|
|
251
|
+
px, py = point
|
|
252
|
+
|
|
253
|
+
qx = ox + np.cos(angle) * (px - ox) - np.sin(angle) * (py - oy)
|
|
254
|
+
qy = oy + np.sin(angle) * (px - ox) + np.cos(angle) * (py - oy)
|
|
255
|
+
return qx, qy
|
|
256
|
+
|
|
257
|
+
def ftAxis(Npts, omega_max):
|
|
258
|
+
"""
|
|
259
|
+
Given two numbers Npts,omega_max, return two vectors spanning the temporal
|
|
260
|
+
and spectral range. They are related by Fourier Transform
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
Npts: (int)
|
|
265
|
+
A number of points defining the length of both grids
|
|
266
|
+
omega_max: (float)
|
|
267
|
+
The maximum circular frequency in the spectral domain. its unit defines
|
|
268
|
+
the temporal units. ex: omega_max in rad/fs implies time_grid in fs
|
|
269
|
+
|
|
270
|
+
Returns
|
|
271
|
+
-------
|
|
272
|
+
omega_grid: (ndarray)
|
|
273
|
+
The spectral axis of the FFT
|
|
274
|
+
time_grid: (ndarray))
|
|
275
|
+
The temporal axis of the FFT
|
|
276
|
+
See Also
|
|
277
|
+
--------
|
|
278
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
279
|
+
"""
|
|
280
|
+
if not isinstance(Npts, int):
|
|
281
|
+
raise TypeError('n should be a positive integer, if possible power of 2')
|
|
282
|
+
elif Npts < 1:
|
|
283
|
+
raise ValueError('n should be a strictly positive integer')
|
|
284
|
+
dT = 2 * np.pi / (2 * omega_max)
|
|
285
|
+
omega_grid = np.linspace(-omega_max, omega_max, Npts)
|
|
286
|
+
time_grid = dT * np.linspace(-(Npts - 1) / 2, (Npts - 1) / 2, Npts)
|
|
287
|
+
return omega_grid, time_grid
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def ftAxis_time(Npts, time_max):
|
|
291
|
+
"""
|
|
292
|
+
Given two numbers Npts,omega_max, return two vectors spanning the temporal
|
|
293
|
+
and spectral range. They are related by Fourier Transform
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
Npts : number
|
|
298
|
+
A number of points defining the length of both grids
|
|
299
|
+
time_max : number
|
|
300
|
+
The maximum tmporal window
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
omega_grid : vector
|
|
305
|
+
The spectral axis of the FFT
|
|
306
|
+
time_grid : vector
|
|
307
|
+
The temporal axis of the FFT
|
|
308
|
+
See Also
|
|
309
|
+
--------
|
|
310
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
311
|
+
"""
|
|
312
|
+
if not isinstance(Npts, int):
|
|
313
|
+
raise TypeError('n should be a positive integer, if possible power of 2')
|
|
314
|
+
elif Npts < 1:
|
|
315
|
+
raise ValueError('n should be a strictly positive integer')
|
|
316
|
+
dT = time_max / Npts
|
|
317
|
+
omega_max = (Npts - 1) / 2 * 2 * np.pi / time_max
|
|
318
|
+
omega_grid = np.linspace(-omega_max, omega_max, Npts)
|
|
319
|
+
time_grid = dT * np.linspace(-(Npts - 1) / 2, (Npts - 1) / 2, Npts)
|
|
320
|
+
return omega_grid, time_grid
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def ft(x, dim=-1):
|
|
324
|
+
"""
|
|
325
|
+
Process the 1D fast fourier transform and swaps the axis to get coorect results using ftAxis
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
x: (ndarray) the array on which the FFT should be done
|
|
329
|
+
dim: the axis over which is done the FFT (default is the last of the array)
|
|
330
|
+
|
|
331
|
+
Returns
|
|
332
|
+
-------
|
|
333
|
+
See Also
|
|
334
|
+
--------
|
|
335
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
336
|
+
"""
|
|
337
|
+
if not isinstance(dim, int):
|
|
338
|
+
raise TypeError('dim should be an integer specifying the array dimension over which to do the calculation')
|
|
339
|
+
assert isinstance(x, np.ndarray)
|
|
340
|
+
assert dim >= -1
|
|
341
|
+
assert dim <= len(x.shape) - 1
|
|
342
|
+
|
|
343
|
+
out = np.fft.fftshift(np.fft.fft(np.fft.fftshift(x, axes=dim), axis=dim), axes=dim)
|
|
344
|
+
return out
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def ift(x, dim=0):
|
|
348
|
+
"""
|
|
349
|
+
Process the inverse 1D fast fourier transform and swaps the axis to get correct results using ftAxis
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
x: (ndarray) the array on which the FFT should be done
|
|
353
|
+
dim: the axis over which is done the FFT (default is the last of the array)
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
See Also
|
|
358
|
+
--------
|
|
359
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
360
|
+
"""
|
|
361
|
+
if not isinstance(dim, int):
|
|
362
|
+
raise TypeError('dim should be an integer specifying the array dimension over which to do the calculation')
|
|
363
|
+
assert isinstance(x, np.ndarray)
|
|
364
|
+
assert dim >= -1
|
|
365
|
+
assert dim <= len(x.shape) - 1
|
|
366
|
+
out = np.fft.fftshift(np.fft.ifft(np.fft.fftshift(x, axes=dim), axis=dim), axes=dim)
|
|
367
|
+
return out
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def ft2(x, dim=(-2, -1)):
|
|
371
|
+
"""
|
|
372
|
+
Process the 2D fast fourier transform and swaps the axis to get correct results using ftAxis
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
x: (ndarray) the array on which the FFT should be done
|
|
376
|
+
dim: the axis over which is done the FFT (default is the last of the array)
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
See Also
|
|
381
|
+
--------
|
|
382
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
383
|
+
"""
|
|
384
|
+
assert isinstance(x, np.ndarray)
|
|
385
|
+
if hasattr(dim, '__iter__'):
|
|
386
|
+
for d in dim:
|
|
387
|
+
if not isinstance(d, int):
|
|
388
|
+
raise TypeError(
|
|
389
|
+
'elements in dim should be an integer specifying the array dimension over which to do the calculation')
|
|
390
|
+
assert d <= len(x.shape)
|
|
391
|
+
else:
|
|
392
|
+
if not isinstance(dim, int):
|
|
393
|
+
raise TypeError(
|
|
394
|
+
'elements in dim should be an integer specifying the array dimension over which to do the calculation')
|
|
395
|
+
assert dim <= len(x.shape)
|
|
396
|
+
out = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(x, axes=dim)), axes=dim)
|
|
397
|
+
return out
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def ift2(x, dim=(-2, -1)):
|
|
401
|
+
"""
|
|
402
|
+
Process the inverse 2D fast fourier transform and swaps the axis to get correct results using ftAxis
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
x: (ndarray) the array on which the FFT should be done
|
|
406
|
+
dim: the axis (or a tuple of axes) over which is done the FFT (default is the last of the array)
|
|
407
|
+
|
|
408
|
+
Returns
|
|
409
|
+
-------
|
|
410
|
+
See Also
|
|
411
|
+
--------
|
|
412
|
+
ftAxis, ftAxis_time, ift, ft2, ift2
|
|
413
|
+
"""
|
|
414
|
+
assert isinstance(x, np.ndarray)
|
|
415
|
+
if hasattr(dim, '__iter__'):
|
|
416
|
+
for d in dim:
|
|
417
|
+
if not isinstance(d, int):
|
|
418
|
+
raise TypeError(
|
|
419
|
+
'elements in dim should be an integer specifying the array dimension over which to do the calculation')
|
|
420
|
+
assert d <= len(x.shape)
|
|
421
|
+
else:
|
|
422
|
+
if not isinstance(dim, int):
|
|
423
|
+
raise TypeError(
|
|
424
|
+
'elements in dim should be an integer specifying the array dimension over which to do the calculation')
|
|
425
|
+
assert dim <= len(x.shape)
|
|
426
|
+
out = np.fft.fftshift(np.fft.ifft2(np.fft.fftshift(x, axes=dim)), axes=dim)
|
|
427
|
+
return out
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def flatten(xs):
|
|
431
|
+
"""Flatten nested list"""
|
|
432
|
+
for x in xs:
|
|
433
|
+
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
|
434
|
+
yield from flatten(x)
|
|
435
|
+
else:
|
|
436
|
+
yield x
|
|
437
|
+
|
|
438
|
+
class LSqEllipse:
|
|
439
|
+
|
|
440
|
+
def fit(self, data):
|
|
441
|
+
"""Lest Squares fitting algorithm
|
|
442
|
+
|
|
443
|
+
Theory taken from (*)
|
|
444
|
+
Solving equation Sa=lCa. with a = |a b c d f g> and a1 = |a b c>
|
|
445
|
+
a2 = |d f g>
|
|
446
|
+
|
|
447
|
+
Args
|
|
448
|
+
----
|
|
449
|
+
data (list:list:float): list of two lists containing the x and y data of the
|
|
450
|
+
ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
|
|
451
|
+
|
|
452
|
+
Returns
|
|
453
|
+
------
|
|
454
|
+
coef (list): list of the coefficients describing an ellipse
|
|
455
|
+
[a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
|
|
456
|
+
"""
|
|
457
|
+
x, y = numpy.asarray(data, dtype=float)
|
|
458
|
+
|
|
459
|
+
# Quadratic part of design matrix [eqn. 15] from (*)
|
|
460
|
+
D1 = numpy.mat(numpy.vstack([x ** 2, x * y, y ** 2])).T
|
|
461
|
+
# Linear part of design matrix [eqn. 16] from (*)
|
|
462
|
+
D2 = numpy.mat(numpy.vstack([x, y, numpy.ones(len(x))])).T
|
|
463
|
+
|
|
464
|
+
# forming scatter matrix [eqn. 17] from (*)
|
|
465
|
+
S1 = D1.T * D1
|
|
466
|
+
S2 = D1.T * D2
|
|
467
|
+
S3 = D2.T * D2
|
|
468
|
+
|
|
469
|
+
# Constraint matrix [eqn. 18]
|
|
470
|
+
C1 = numpy.mat('0. 0. 2.; 0. -1. 0.; 2. 0. 0.')
|
|
471
|
+
|
|
472
|
+
# Reduced scatter matrix [eqn. 29]
|
|
473
|
+
M = C1.I * (S1 - S2 * S3.I * S2.T)
|
|
474
|
+
|
|
475
|
+
# M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors from this equation [eqn. 28]
|
|
476
|
+
eval, evec = numpy.linalg.eig(M)
|
|
477
|
+
|
|
478
|
+
# eigenvector must meet constraint 4ac - b^2 to be valid.
|
|
479
|
+
cond = 4 * numpy.multiply(evec[0, :], evec[2, :]) - numpy.power(evec[1, :], 2)
|
|
480
|
+
a1 = evec[:, numpy.nonzero(cond.A > 0)[1]]
|
|
481
|
+
|
|
482
|
+
# |d f g> = -S3^(-1)*S2^(T)*|a b c> [eqn. 24]
|
|
483
|
+
a2 = -S3.I * S2.T * a1
|
|
484
|
+
|
|
485
|
+
# eigenvectors |a b c d f g>
|
|
486
|
+
self.coef = numpy.vstack([a1, a2])
|
|
487
|
+
self._save_parameters()
|
|
488
|
+
|
|
489
|
+
def _save_parameters(self):
|
|
490
|
+
"""finds the important parameters of the fitted ellipse
|
|
491
|
+
|
|
492
|
+
Theory taken form http://mathworld.wolfram
|
|
493
|
+
|
|
494
|
+
Args
|
|
495
|
+
-----
|
|
496
|
+
coef (list): list of the coefficients describing an ellipse
|
|
497
|
+
[a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
|
|
498
|
+
|
|
499
|
+
Returns
|
|
500
|
+
_______
|
|
501
|
+
center (List): of the form [x0, y0]
|
|
502
|
+
width (float): major axis
|
|
503
|
+
height (float): minor axis
|
|
504
|
+
phi (float): rotation of major axis form the x-axis in radians
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
# eigenvectors are the coefficients of an ellipse in general form
|
|
508
|
+
# a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0 [eqn. 15) from (**) or (***)
|
|
509
|
+
a = self.coef[0, 0]
|
|
510
|
+
b = self.coef[1, 0] / 2.
|
|
511
|
+
c = self.coef[2, 0]
|
|
512
|
+
d = self.coef[3, 0] / 2.
|
|
513
|
+
f = self.coef[4, 0] / 2.
|
|
514
|
+
g = self.coef[5, 0]
|
|
515
|
+
|
|
516
|
+
# finding center of ellipse [eqn.19 and 20] from (**)
|
|
517
|
+
x0 = (c * d - b * f) / (b ** 2. - a * c)
|
|
518
|
+
y0 = (a * f - b * d) / (b ** 2. - a * c)
|
|
519
|
+
|
|
520
|
+
# Find the semi-axes lengths [eqn. 21 and 22] from (**)
|
|
521
|
+
numerator = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g)
|
|
522
|
+
denominator1 = (b * b - a * c) * ((c - a) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a))
|
|
523
|
+
denominator2 = (b * b - a * c) * ((a - c) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a))
|
|
524
|
+
width = numpy.sqrt(numerator / denominator1)
|
|
525
|
+
height = numpy.sqrt(numerator / denominator2)
|
|
526
|
+
|
|
527
|
+
# angle of counterclockwise rotation of major-axis of ellipse to x-axis [eqn. 23] from (**)
|
|
528
|
+
# or [eqn. 26] from (***).
|
|
529
|
+
phi = .5 * numpy.arctan((2. * b) / (a - c))
|
|
530
|
+
|
|
531
|
+
self._center = [x0, y0]
|
|
532
|
+
self._width = width
|
|
533
|
+
self._height = height
|
|
534
|
+
self._phi = phi
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def center(self):
|
|
538
|
+
return self._center
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def width(self):
|
|
542
|
+
return self._width
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def height(self):
|
|
546
|
+
return self._height
|
|
547
|
+
|
|
548
|
+
@property
|
|
549
|
+
def phi(self):
|
|
550
|
+
"""angle of counterclockwise rotation of major-axis of ellipse to x-axis
|
|
551
|
+
[eqn. 23] from (**)
|
|
552
|
+
"""
|
|
553
|
+
return self._phi
|
|
554
|
+
|
|
555
|
+
def parameters(self):
|
|
556
|
+
return self.center, self.width, self.height, self.phi
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def make_test_ellipse(center=[1, 1], width=1, height=.6, phi=3.14 / 5):
|
|
560
|
+
"""Generate Elliptical data with noise
|
|
561
|
+
|
|
562
|
+
Args
|
|
563
|
+
----
|
|
564
|
+
center (list:float): (<x_location>, <y_location>)
|
|
565
|
+
width (float): semimajor axis. Horizontal dimension of the ellipse (**)
|
|
566
|
+
height (float): semiminor axis. Vertical dimension of the ellipse (**)
|
|
567
|
+
phi (float:radians): tilt of the ellipse, the angle the semimajor axis
|
|
568
|
+
makes with the x-axis
|
|
569
|
+
|
|
570
|
+
Returns
|
|
571
|
+
-------
|
|
572
|
+
data (list:list:float): list of two lists containing the x and y data of the
|
|
573
|
+
ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
|
|
574
|
+
"""
|
|
575
|
+
t = numpy.linspace(0, 2 * numpy.pi, 1000)
|
|
576
|
+
x_noise, y_noise = numpy.random.rand(2, len(t))
|
|
577
|
+
|
|
578
|
+
ellipse_x = center[0] + width * numpy.cos(t) * numpy.cos(phi) - height * numpy.sin(t) * numpy.sin(
|
|
579
|
+
phi) + x_noise / 2.
|
|
580
|
+
ellipse_y = center[1] + width * numpy.cos(t) * numpy.sin(phi) + height * numpy.sin(t) * numpy.cos(
|
|
581
|
+
phi) + y_noise / 2.
|
|
582
|
+
|
|
583
|
+
return [ellipse_x, ellipse_y]
|
|
584
|
+
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#this is the configuration file of PyMoDAQ
|
|
2
|
+
[style]
|
|
3
|
+
darkstyle = true
|
|
4
|
+
syntax_highlighting = 'github-dark'
|
|
5
|
+
language = "English"
|
|
6
|
+
country = "UnitedStates"
|
|
7
|
+
|
|
8
|
+
[qtbackend]
|
|
9
|
+
backends = [ "pyqt5", "pyqt6", "pyside2", "pyside6",]
|
|
10
|
+
backend = "pyqt5"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[data_saving]
|
|
14
|
+
[data_saving.h5file]
|
|
15
|
+
save_path = "C:\\Data" #base path where data are automatically saved
|
|
16
|
+
compression_level = 5 # for hdf5 files between 0(min) and 9 (max)
|
|
17
|
+
|
|
18
|
+
[data_saving.hsds] #hsds connection option (https://www.hdfgroup.org/solutions/highly-scalable-data-service-hsds/)
|
|
19
|
+
#to save data in pymodaq using hpyd backend towards distant server or cloud (mimicking hdf5 files)
|
|
20
|
+
root_url = "http://hsds.sebastienweber.fr"
|
|
21
|
+
username = "pymodaq_user"
|
|
22
|
+
pwd = "pymodaq"
|
|
23
|
+
|
|
24
|
+
[data_saving.data_type]
|
|
25
|
+
dynamic = 'float64' # choose from below. This will force the datatype to be saved to
|
|
26
|
+
dynamics = ['uint8', 'int8', 'uint16', 'int16', 'uint32', 'int32', 'uint64', 'int64', 'float64']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[general]
|
|
30
|
+
debug_level = "INFO" #either "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
|
|
31
|
+
debug_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
32
|
+
check_version = true # automatically check version at startup (or not if False)
|
|
33
|
+
message_status_persistence = 1000 # ms
|
|
34
|
+
|
|
35
|
+
hdf5_backend = 'tables' # could be among ['tables', 'h5py', 'h5pyd'], mostly tested with tables
|
|
36
|
+
|
|
37
|
+
[user]
|
|
38
|
+
name = "User name" # default name used as author in the hdf5 saving files
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
[plotting]
|
|
42
|
+
backend = 'matplotlib' # either 'matplotlib' or 'qt' or any other custom backend
|
|
43
|
+
plot_colors = [[255, 255, 255], [255, 0, 0], [0, 255, 0], [0, 0, 255], [14, 207, 189], [207, 14, 166], [207, 204, 14]]
|
|
44
|
+
|
|
45
|
+
[network]
|
|
46
|
+
[network.logging]
|
|
47
|
+
[network.logging.user]
|
|
48
|
+
username = "pymodaq_user"
|
|
49
|
+
pwd = "pymodaq"
|
|
50
|
+
|
|
51
|
+
[network.logging.sql] #location of the postgresql database server and options where the DAQ_Logger will log data
|
|
52
|
+
ip = "10.47.3.22"
|
|
53
|
+
port = 5432
|
|
54
|
+
|
|
55
|
+
[network.tcp-server]
|
|
56
|
+
ip = "10.47.0.39"
|
|
57
|
+
port = 6341
|
|
58
|
+
|
|
59
|
+
[network.leco-server]
|
|
60
|
+
run_coordinator_at_startup = false
|
|
61
|
+
host = "localhost"
|
|
62
|
+
port = 12300 # pyleco default Coordinator port
|
|
63
|
+
|
|
64
|
+
[presets]
|
|
65
|
+
default_preset_for_scan = "preset_default"
|
|
66
|
+
default_preset_for_logger = "preset_default"
|
|
67
|
+
default_preset_for_pid = "beam_steering_mock"
|
|
68
|
+
|
|
69
|
+
[actuator]
|
|
70
|
+
epsilon_default = 1
|
|
71
|
+
polling_interval_ms = 100 # ms Careful when using TCP/IP connection as you can saturate the connection with too much polling
|
|
72
|
+
polling_timeout_s = 20 # s
|
|
73
|
+
refresh_timeout_ms = 500 # ms
|
|
74
|
+
timeout = 10000 # default duration in ms to wait for data to be acquirred
|
|
75
|
+
siprefix = true # tell if printing of current value use a SI prefix or not (µ, m, k, M...)
|
|
76
|
+
display_units = true # display units in the SpinBoxes
|
|
77
|
+
|
|
78
|
+
[viewer]
|
|
79
|
+
daq_type = 'DAQ0D' #either "DAQ0D", "DAQ1D", "DAQ2D", "DAQND"
|
|
80
|
+
viewer_in_thread = true
|
|
81
|
+
timeout = 10000 # default duration in ms to wait for data to be acquirred
|
|
82
|
+
allow_settings_edition = false
|
|
83
|
+
|
|
84
|
+
[scan]
|
|
85
|
+
scan_in_thread = true
|
|
86
|
+
show_popups = true
|
|
87
|
+
default = "Scan2D"
|
|
88
|
+
Naverage = 1 # minimum is 1
|
|
89
|
+
steps_limit = 1000 # the limit of the number of steps you can set in a given scan
|
|
90
|
+
sort1D = true
|
|
91
|
+
|
|
92
|
+
[scan.timeflow]
|
|
93
|
+
wait_time = 0
|
|
94
|
+
wait_time_between = 0
|
|
95
|
+
timeout = 10000 # in millisecond
|
|
96
|
+
|