pymodaq_data 5.0.3__tar.gz → 5.0.5__tar.gz
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_data-5.0.3 → pymodaq_data-5.0.5}/.gitignore +2 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/PKG-INFO +1 -1
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/_version.py +2 -2
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/data.py +268 -39
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/data_saving.py +2 -2
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/numpy_func.py +14 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/slicing.py +8 -5
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/LICENSE +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/README.rst +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/pyproject.toml +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/backends.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/browsing.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporter.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/base.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/flimj.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/hyperspy.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/saving.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/utils.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/icon.ico +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotter.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/post_treatment/__init__.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/post_treatment/process_to_scalar.py +0 -0
- {pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/splash.png +0 -0
|
@@ -20,6 +20,7 @@ import warnings
|
|
|
20
20
|
from time import time
|
|
21
21
|
import copy
|
|
22
22
|
import pint
|
|
23
|
+
from pint.compat import upcast_type_map
|
|
23
24
|
|
|
24
25
|
from multipledispatch import dispatch
|
|
25
26
|
|
|
@@ -158,6 +159,24 @@ class DataDistribution(BaseEnum):
|
|
|
158
159
|
spread = 1
|
|
159
160
|
|
|
160
161
|
|
|
162
|
+
def _compute_slices_from_axis(axis: Axis, _slice, *ignored, is_index=True, **ignored_also):
|
|
163
|
+
if not is_index:
|
|
164
|
+
if isinstance(_slice, numbers.Number):
|
|
165
|
+
if not is_index:
|
|
166
|
+
_slice = axis.find_index(_slice)
|
|
167
|
+
elif _slice is Ellipsis:
|
|
168
|
+
return _slice
|
|
169
|
+
elif isinstance(_slice, slice):
|
|
170
|
+
if not (_slice.start is None and
|
|
171
|
+
_slice.stop is None and _slice.step is None):
|
|
172
|
+
start = axis.find_index(
|
|
173
|
+
_slice.start if _slice.start is not None else axis.get_data()[0])
|
|
174
|
+
stop = axis.find_index(
|
|
175
|
+
_slice.stop if _slice.stop is not None else axis.get_data()[-1])
|
|
176
|
+
_slice = slice(start, stop)
|
|
177
|
+
return _slice
|
|
178
|
+
|
|
179
|
+
|
|
161
180
|
class Axis:
|
|
162
181
|
"""Object holding info and data about physical axis of some data
|
|
163
182
|
|
|
@@ -195,6 +214,7 @@ class Axis:
|
|
|
195
214
|
super().__init__()
|
|
196
215
|
|
|
197
216
|
self.iaxis: Axis = SpecialSlicersData(self, False)
|
|
217
|
+
self.vaxis: Axis = SpecialSlicersData(self, False, False)
|
|
198
218
|
|
|
199
219
|
self._size = size
|
|
200
220
|
self._data = None
|
|
@@ -212,13 +232,22 @@ class Axis:
|
|
|
212
232
|
if (scaling is None or offset is None or size is None) and data is not None:
|
|
213
233
|
self.get_scale_offset_from_data(data)
|
|
214
234
|
|
|
235
|
+
@staticmethod
|
|
236
|
+
def from_quantity(quantity: Q_[np.ndarray], label='axis', index=0) -> Axis:
|
|
237
|
+
return Axis(label, str(quantity.units), data=quantity.magnitude,
|
|
238
|
+
index=index)
|
|
239
|
+
|
|
215
240
|
def copy(self):
|
|
216
241
|
return copy.copy(self)
|
|
217
242
|
|
|
218
|
-
def as_dwa(self) -> DataWithAxes:
|
|
219
|
-
dwa = DataRaw(self.label,
|
|
243
|
+
def as_dwa(self, set_itself_as_axis=False) -> DataWithAxes:
|
|
244
|
+
dwa = DataRaw(self.label, units=self.units,
|
|
245
|
+
data=[self.get_data()],
|
|
220
246
|
labels=[f'{self.label}_{self.units}'])
|
|
221
|
-
|
|
247
|
+
if not set_itself_as_axis:
|
|
248
|
+
dwa.create_missing_axes()
|
|
249
|
+
else:
|
|
250
|
+
dwa.axes = [self.copy()]
|
|
222
251
|
return dwa
|
|
223
252
|
|
|
224
253
|
@property
|
|
@@ -234,16 +263,54 @@ class Axis:
|
|
|
234
263
|
|
|
235
264
|
@property
|
|
236
265
|
def units(self) -> str:
|
|
237
|
-
"""str: get/set the units for this axis
|
|
266
|
+
"""str: get/set the units for this axis without conversion (equivalent to
|
|
267
|
+
force_units)"""
|
|
238
268
|
return self._units
|
|
239
269
|
|
|
240
270
|
@units.setter
|
|
241
271
|
def units(self, units: str):
|
|
242
|
-
|
|
243
|
-
|
|
272
|
+
self.force_units(units)
|
|
273
|
+
|
|
274
|
+
def force_units(self, units: str):
|
|
275
|
+
""" Change immediately the units to whatever else. Use this with care!"""
|
|
244
276
|
units = check_units(units)
|
|
245
277
|
self._units = units
|
|
246
278
|
|
|
279
|
+
def units_as(self, units: str, inplace=True, context: str = None, **context_kwargs):
|
|
280
|
+
if inplace:
|
|
281
|
+
#self._units = units
|
|
282
|
+
if context is None:
|
|
283
|
+
self.data = self.get_quantity().to(units).magnitude
|
|
284
|
+
else:
|
|
285
|
+
self.data = self.get_quantity().to(units, context, **context_kwargs).magnitude
|
|
286
|
+
self.force_units(units)
|
|
287
|
+
return
|
|
288
|
+
else:
|
|
289
|
+
if context is not None:
|
|
290
|
+
return Axis(self.label, units,
|
|
291
|
+
data=self.get_quantity().to(units, context, **context_kwargs).magnitude)
|
|
292
|
+
else:
|
|
293
|
+
return Axis(self.label, units,
|
|
294
|
+
data=self.get_quantity().to(units))
|
|
295
|
+
|
|
296
|
+
def to_reduced_units(self, inplace=False):
|
|
297
|
+
quantity = self.get_quantity().to_reduced_units()
|
|
298
|
+
if inplace:
|
|
299
|
+
self.data = quantity.magnitude
|
|
300
|
+
self.force_units(str(quantity.units))
|
|
301
|
+
else:
|
|
302
|
+
return Axis(self.label, units=str(quantity.units),
|
|
303
|
+
data=quantity.magnitude)
|
|
304
|
+
|
|
305
|
+
def to_base_units(self, inplace=False):
|
|
306
|
+
quantity = self.get_quantity().to_base_units()
|
|
307
|
+
if inplace:
|
|
308
|
+
self.data = quantity.magnitude
|
|
309
|
+
self.force_units(str(quantity.units))
|
|
310
|
+
else:
|
|
311
|
+
return Axis(self.label, units=str(quantity.units),
|
|
312
|
+
data=quantity.magnitude)
|
|
313
|
+
|
|
247
314
|
@property
|
|
248
315
|
def index(self) -> int:
|
|
249
316
|
"""int: get/set the index this axis corresponds to in a DataWithAxis object"""
|
|
@@ -260,7 +327,7 @@ class Axis:
|
|
|
260
327
|
return self._data
|
|
261
328
|
|
|
262
329
|
@data.setter
|
|
263
|
-
def data(self, data: np.ndarray):
|
|
330
|
+
def data(self, data: Union[np.ndarray, Q_]):
|
|
264
331
|
if data is not None:
|
|
265
332
|
self._check_data_valid(data)
|
|
266
333
|
self.get_scale_offset_from_data(data)
|
|
@@ -273,6 +340,10 @@ class Axis:
|
|
|
273
340
|
"""Convenience method to obtain the axis data (usually None because scaling and offset are used)"""
|
|
274
341
|
return self._data if self._data is not None else self._linear_data(self.size)
|
|
275
342
|
|
|
343
|
+
def get_quantity(self) -> Q_:
|
|
344
|
+
""" Convenience method to obtain the numerical data as a quantity array"""
|
|
345
|
+
return Q_(self.get_data(), self.units)
|
|
346
|
+
|
|
276
347
|
def get_data_at(self, indexes: Union[int, IterableType, slice]) -> np.ndarray:
|
|
277
348
|
""" Get data at specified indexes
|
|
278
349
|
|
|
@@ -346,8 +417,10 @@ class Axis:
|
|
|
346
417
|
elif index < 0:
|
|
347
418
|
raise ValueError('index for the Axis class should be a positive integer')
|
|
348
419
|
|
|
349
|
-
|
|
350
|
-
|
|
420
|
+
def _check_data_valid(self, data: Union[np.ndarray, Q_]):
|
|
421
|
+
if isinstance(data, Q_):
|
|
422
|
+
self.units = str(data.units)
|
|
423
|
+
data = data.magnitude
|
|
351
424
|
if not isinstance(data, np.ndarray):
|
|
352
425
|
raise TypeError(f'data for the Axis class should be a 1D numpy array')
|
|
353
426
|
elif len(data.shape) != 1:
|
|
@@ -368,12 +441,14 @@ class Axis:
|
|
|
368
441
|
def __len__(self):
|
|
369
442
|
return self.size
|
|
370
443
|
|
|
371
|
-
def _compute_slices(self,
|
|
372
|
-
|
|
444
|
+
def _compute_slices(self, _slice, *ignored, is_index=True, **ignored_also):
|
|
445
|
+
_slice = _compute_slices_from_axis(self, _slice, is_index=is_index)
|
|
446
|
+
return _slice, _slice
|
|
373
447
|
|
|
374
|
-
def _slicer(self, _slice, *ignored, **ignored_also):
|
|
448
|
+
def _slicer(self, _slice, *ignored, is_index=True, **ignored_also):
|
|
375
449
|
ax: Axis = copy.deepcopy(self)
|
|
376
|
-
|
|
450
|
+
_slice, _slice = self._compute_slices(_slice, is_index=is_index)
|
|
451
|
+
if isinstance(_slice, numbers.Number):
|
|
377
452
|
ax.data = np.array([ax.get_data()[_slice]])
|
|
378
453
|
return ax
|
|
379
454
|
elif _slice is Ellipsis:
|
|
@@ -442,6 +517,11 @@ class Axis:
|
|
|
442
517
|
else:
|
|
443
518
|
return self.offset + self.size / 2 * self.scaling
|
|
444
519
|
|
|
520
|
+
def flip(self):
|
|
521
|
+
""" flip the direction of the axis"""
|
|
522
|
+
self.data = self.get_data()[::-1]
|
|
523
|
+
|
|
524
|
+
|
|
445
525
|
def min(self):
|
|
446
526
|
if self._data is not None:
|
|
447
527
|
return np.min(self._data)
|
|
@@ -533,8 +613,9 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
533
613
|
distribution: DataDistribution or str
|
|
534
614
|
The distribution type of the data: uniform if distributed on a regular grid or spread if on
|
|
535
615
|
specific unordered points
|
|
536
|
-
data: list of ndarray
|
|
537
|
-
The data the object is storing
|
|
616
|
+
data: list of ndarray or Quantities
|
|
617
|
+
The data the object is storing. In case of Quantities, the object units attribute will
|
|
618
|
+
be forced to the unit of this quantity, ignoring the units argument.
|
|
538
619
|
labels: list of str
|
|
539
620
|
The labels of the data nd-arrays
|
|
540
621
|
origin: str
|
|
@@ -601,6 +682,13 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
601
682
|
|
|
602
683
|
base_type = 'Data'
|
|
603
684
|
|
|
685
|
+
def __new__(cls, *args, **kwargs):
|
|
686
|
+
if not pint.compat.is_upcast_type(cls):
|
|
687
|
+
upcast_type_map.update({f'{cls.__module__}.{cls.__name__}': None})
|
|
688
|
+
# this will make sure, these
|
|
689
|
+
# objects are not wrapped by pint
|
|
690
|
+
return super().__new__(cls)
|
|
691
|
+
|
|
604
692
|
def __init__(self, name: str,
|
|
605
693
|
source: DataSource = None, dim: DataDim = None,
|
|
606
694
|
distribution: DataDistribution = DataDistribution.uniform,
|
|
@@ -642,7 +730,8 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
642
730
|
units = check_units(units)
|
|
643
731
|
self.units_as(units, inplace=True)
|
|
644
732
|
|
|
645
|
-
def units_as(self, units: str, inplace=True
|
|
733
|
+
def units_as(self, units: str, inplace=True, context: str = None,
|
|
734
|
+
**context_kwargs) -> 'DataBase':
|
|
646
735
|
""" Set the object units to the new one (if possible)
|
|
647
736
|
|
|
648
737
|
Parameters
|
|
@@ -653,11 +742,18 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
653
742
|
default True.
|
|
654
743
|
If True replace the data's arrays by array in the new units
|
|
655
744
|
If False, return a new data object
|
|
745
|
+
context: str
|
|
746
|
+
See pint documentation
|
|
656
747
|
"""
|
|
657
748
|
arrays = []
|
|
658
749
|
try:
|
|
659
750
|
for ind_array in range(len(self)):
|
|
660
|
-
|
|
751
|
+
if context is None:
|
|
752
|
+
arrays.append(
|
|
753
|
+
self.quantities[ind_array].to(units).magnitude)
|
|
754
|
+
else:
|
|
755
|
+
arrays.append(
|
|
756
|
+
self.quantities[ind_array].to(units, context, **context_kwargs).magnitude)
|
|
661
757
|
|
|
662
758
|
except pint.errors.DimensionalityError as e:
|
|
663
759
|
raise DataUnitError(
|
|
@@ -678,6 +774,49 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
678
774
|
""" Change immediately the units to whatever else. Use this with care!"""
|
|
679
775
|
self._units = units
|
|
680
776
|
|
|
777
|
+
def value(self, units: str = None) -> float:
|
|
778
|
+
"""Returns the underlying float value (of the first elt in the data list) if this data
|
|
779
|
+
holds only a float otherwise returns a mean of the underlying data
|
|
780
|
+
|
|
781
|
+
Parameters
|
|
782
|
+
----------
|
|
783
|
+
|
|
784
|
+
units: str
|
|
785
|
+
if unit is compatible with self.units, convert the data to these new units before
|
|
786
|
+
getting the value
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
"""
|
|
790
|
+
if self.length == 1 and self.size == 1:
|
|
791
|
+
if units is not None:
|
|
792
|
+
data = Q_(float(self.data[0][0]), self.units)
|
|
793
|
+
return data.m_as(units)
|
|
794
|
+
else:
|
|
795
|
+
return float(self.data[0][0])
|
|
796
|
+
else:
|
|
797
|
+
if units is not None:
|
|
798
|
+
data = Q_(float(np.mean(self.data[0])), self.units)
|
|
799
|
+
return data.m_as(units)
|
|
800
|
+
else:
|
|
801
|
+
return float(np.mean(self.data[0]))
|
|
802
|
+
|
|
803
|
+
def values(self, units: str = None) -> List[float]:
|
|
804
|
+
"""Returns the underlying float value (for each data array in the data list) if this data
|
|
805
|
+
holds only a float otherwise returns a mean of the underlying data"""
|
|
806
|
+
if self.length == 1 and self.size == 1:
|
|
807
|
+
if units is not None:
|
|
808
|
+
return [float(Q_(data_array[0], self.units).m_as(units))
|
|
809
|
+
for data_array in self.data]
|
|
810
|
+
else:
|
|
811
|
+
return [float(data_array[0])
|
|
812
|
+
for data_array in self.data]
|
|
813
|
+
else:
|
|
814
|
+
if units is not None:
|
|
815
|
+
return [float(Q_(np.mean(data_array), self.units).m_as(units))
|
|
816
|
+
for data_array in self.data]
|
|
817
|
+
else:
|
|
818
|
+
return [float(np.mean(data_array)) for data_array in self.data]
|
|
819
|
+
|
|
681
820
|
def as_dte(self, name: str = 'mydte') -> DataToExport:
|
|
682
821
|
"""Convenience method to wrap the DataWithAxes object into a DataToExport"""
|
|
683
822
|
return DataToExport(name, data=[self])
|
|
@@ -745,6 +884,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
745
884
|
units = dwa.units
|
|
746
885
|
ufunc_results = [ufunc(*zipped, **kwargs) for zipped in list(zip(*elts))]
|
|
747
886
|
if isinstance(ufunc_results[0], Q_):
|
|
887
|
+
ufunc_results = [ufunc_result.to_reduced_units() for ufunc_result in ufunc_results]
|
|
748
888
|
units = str(ufunc_results[0].units)
|
|
749
889
|
ufunc_results = [ufunc_result.magnitude for ufunc_result in ufunc_results]
|
|
750
890
|
dwa.data = ufunc_results
|
|
@@ -838,7 +978,13 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
838
978
|
|
|
839
979
|
def angle(self):
|
|
840
980
|
""" Take the phase value of itself"""
|
|
841
|
-
|
|
981
|
+
dwa_angle = np.angle(self)
|
|
982
|
+
dwa_angle.force_units('rad')
|
|
983
|
+
return dwa_angle
|
|
984
|
+
|
|
985
|
+
def unwrap(self):
|
|
986
|
+
""" unwrap the underlying array (should be angles otherwise meaningless)"""
|
|
987
|
+
return np.unwrap(self)
|
|
842
988
|
|
|
843
989
|
def real(self):
|
|
844
990
|
""" Take the real part of itself"""
|
|
@@ -959,15 +1105,17 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
959
1105
|
"""Get the data by its index in the list, same as self[index]"""
|
|
960
1106
|
return self.data[index]
|
|
961
1107
|
|
|
962
|
-
|
|
963
|
-
def _check_data_type(data: List[np.ndarray]) -> List[np.ndarray]:
|
|
1108
|
+
def _check_data_type(self, data: List[Union[np.ndarray, Q_]]) -> List[np.ndarray]:
|
|
964
1109
|
"""make sure data is a list of nd-arrays"""
|
|
965
1110
|
is_valid = True
|
|
966
1111
|
if data is None:
|
|
967
1112
|
is_valid = False
|
|
968
1113
|
if not isinstance(data, list):
|
|
969
1114
|
# try to transform the data to regular type
|
|
970
|
-
if isinstance(data,
|
|
1115
|
+
if isinstance(data, Q_):
|
|
1116
|
+
self.force_units(str(data.units))
|
|
1117
|
+
data = [data.magnitude]
|
|
1118
|
+
elif isinstance(data, np.ndarray):
|
|
971
1119
|
warnings.warn(DataTypeWarning(f'Your data should be a list of numpy arrays not just a single numpy'
|
|
972
1120
|
f' array, wrapping them with a list'))
|
|
973
1121
|
data = [data]
|
|
@@ -980,12 +1128,16 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
980
1128
|
if isinstance(data, list):
|
|
981
1129
|
if len(data) == 0:
|
|
982
1130
|
is_valid = False
|
|
983
|
-
elif not isinstance(data[0], np.ndarray)
|
|
1131
|
+
elif not (isinstance(data[0], np.ndarray) or
|
|
1132
|
+
isinstance(data[0], Q_)):
|
|
984
1133
|
is_valid = False
|
|
985
1134
|
elif len(data[0].shape) == 0:
|
|
986
1135
|
is_valid = False
|
|
987
1136
|
if not is_valid:
|
|
988
1137
|
raise TypeError(f'Data should be an non-empty list of non-empty numpy arrays')
|
|
1138
|
+
if isinstance(data[0], Q_):
|
|
1139
|
+
self.force_units(str(data[0].units))
|
|
1140
|
+
data = [array.magnitude for array in data]
|
|
989
1141
|
return data
|
|
990
1142
|
|
|
991
1143
|
def check_shape_from_data(self, data: List[np.ndarray]):
|
|
@@ -1050,7 +1202,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
|
|
|
1050
1202
|
return self._data
|
|
1051
1203
|
|
|
1052
1204
|
@data.setter
|
|
1053
|
-
def data(self, data: List[np.ndarray]):
|
|
1205
|
+
def data(self, data: List[Union[np.ndarray, Q_]]):
|
|
1054
1206
|
data = self._check_data_type(data)
|
|
1055
1207
|
self._check_shape_dim_consistency(data)
|
|
1056
1208
|
self._check_same_shape(data)
|
|
@@ -1661,8 +1813,11 @@ class DataWithAxes(DataBase):
|
|
|
1661
1813
|
|
|
1662
1814
|
self.set_axes_manager(self.shape, axes=axes, nav_indexes=nav_indexes, **other_kwargs)
|
|
1663
1815
|
|
|
1664
|
-
self.inav
|
|
1665
|
-
self.isig
|
|
1816
|
+
self.inav = SpecialSlicersData(self, True)
|
|
1817
|
+
self.isig = SpecialSlicersData(self, False)
|
|
1818
|
+
|
|
1819
|
+
self.vnav = SpecialSlicersData(self, True, is_index=False)
|
|
1820
|
+
self.vsig = SpecialSlicersData(self, False, is_index=False)
|
|
1666
1821
|
|
|
1667
1822
|
self.get_dim_from_data_axes() # in DataBase, dim is processed from the shape of data, but if axes are provided
|
|
1668
1823
|
#then use get_dim_from axes
|
|
@@ -1848,7 +2003,32 @@ class DataWithAxes(DataBase):
|
|
|
1848
2003
|
mean = np.array([mean])
|
|
1849
2004
|
dat_mean.append(mean)
|
|
1850
2005
|
return self.deepcopy_with_new_data(dat_mean, remove_axes_index=axis)
|
|
1851
|
-
|
|
2006
|
+
|
|
2007
|
+
def moment(self) -> Tuple[DataWithAxes, DataWithAxes]:
|
|
2008
|
+
""" returns the two first moments of the data over the axis
|
|
2009
|
+
|
|
2010
|
+
only valid for Data1D data
|
|
2011
|
+
|
|
2012
|
+
Returns
|
|
2013
|
+
-------
|
|
2014
|
+
DataCalculated: containing the moment of order 0 (mean)
|
|
2015
|
+
DataCalculated: containing the moment of order 1 (std)
|
|
2016
|
+
"""
|
|
2017
|
+
if self.dim != DataDim.Data1D:
|
|
2018
|
+
raise DataDimError('the moment method is only valid for 1D data')
|
|
2019
|
+
arrays_mean = []
|
|
2020
|
+
arrays_std = []
|
|
2021
|
+
for data in self:
|
|
2022
|
+
mean, std = mutils.my_moment(self.axes[0].get_data(), data)
|
|
2023
|
+
arrays_mean.append(np.array([mean]))
|
|
2024
|
+
arrays_std.append(np.array([std]))
|
|
2025
|
+
|
|
2026
|
+
return (DataCalculated('mean', data=arrays_mean),
|
|
2027
|
+
DataCalculated('std', data=arrays_std))
|
|
2028
|
+
|
|
2029
|
+
|
|
2030
|
+
|
|
2031
|
+
|
|
1852
2032
|
def sum(self, axis: int = 0) -> DataWithAxes:
|
|
1853
2033
|
"""Process the sum of the data on the specified axis and returns the new data
|
|
1854
2034
|
|
|
@@ -1862,7 +2042,7 @@ class DataWithAxes(DataBase):
|
|
|
1862
2042
|
"""
|
|
1863
2043
|
dat_sum = []
|
|
1864
2044
|
for dat in self.data:
|
|
1865
|
-
dat_sum.append(np.sum(dat, axis=axis))
|
|
2045
|
+
dat_sum.append(np.atleast_1d(np.sum(dat, axis=axis)))
|
|
1866
2046
|
return self.deepcopy_with_new_data(dat_sum, remove_axes_index=axis)
|
|
1867
2047
|
|
|
1868
2048
|
def interp(self, new_axis_data: Union[Axis, np.ndarray], **kwargs) -> DataWithAxes:
|
|
@@ -1902,12 +2082,20 @@ class DataWithAxes(DataBase):
|
|
|
1902
2082
|
labels=self.labels)
|
|
1903
2083
|
return new_data
|
|
1904
2084
|
|
|
1905
|
-
def ft(self, axis: int = 0
|
|
2085
|
+
def ft(self, axis: int = 0, axis_label: str = None,
|
|
2086
|
+
axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
|
|
1906
2087
|
"""Process the Fourier Transform of the data on the specified axis and returns the new data
|
|
1907
2088
|
|
|
1908
2089
|
Parameters
|
|
1909
2090
|
----------
|
|
1910
2091
|
axis: int
|
|
2092
|
+
Apply the FT on this axis index
|
|
2093
|
+
axis_label: str
|
|
2094
|
+
A new label for the FT computed axis
|
|
2095
|
+
axis_units: str
|
|
2096
|
+
New units (without conversion on top of the one from the FT) for the computed axis
|
|
2097
|
+
labels: List[str]
|
|
2098
|
+
list of string for new labels
|
|
1911
2099
|
|
|
1912
2100
|
Returns
|
|
1913
2101
|
-------
|
|
@@ -1924,19 +2112,34 @@ class DataWithAxes(DataBase):
|
|
|
1924
2112
|
for dat in self.data:
|
|
1925
2113
|
dat_ft.append(mutils.ft(dat, dim=axis))
|
|
1926
2114
|
new_data = self.deepcopy_with_new_data(dat_ft)
|
|
2115
|
+
if labels is not None:
|
|
2116
|
+
new_data.labels = labels
|
|
1927
2117
|
axis_obj = new_data.get_axis_from_index(axis)[0]
|
|
1928
2118
|
axis_obj.data = omega_grid
|
|
1929
|
-
|
|
1930
|
-
|
|
2119
|
+
if axis_label is not None:
|
|
2120
|
+
axis_obj.label = axis_label
|
|
2121
|
+
else:
|
|
2122
|
+
axis_obj.label = f'ft({axis_obj.label})'
|
|
2123
|
+
if axis_units is not None:
|
|
2124
|
+
axis_obj.force_units(axis_units)
|
|
2125
|
+
else:
|
|
2126
|
+
axis_obj.units = f'rad/{axis_obj.units}'
|
|
1931
2127
|
return new_data
|
|
1932
2128
|
|
|
1933
|
-
def ift(self, axis: int = 0
|
|
2129
|
+
def ift(self, axis: int = 0, axis_label: str = None,
|
|
2130
|
+
axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
|
|
1934
2131
|
"""Process the inverse Fourier Transform of the data on the specified axis and returns the
|
|
1935
2132
|
new data
|
|
1936
2133
|
|
|
1937
2134
|
Parameters
|
|
1938
2135
|
----------
|
|
1939
2136
|
axis: int
|
|
2137
|
+
axis_label: str
|
|
2138
|
+
A new label for the FT computed axis
|
|
2139
|
+
axis_units: str
|
|
2140
|
+
New units (without conversion on top of the one from the FT) for the computed axis
|
|
2141
|
+
labels: List[str]
|
|
2142
|
+
list of string for new labels
|
|
1940
2143
|
|
|
1941
2144
|
Returns
|
|
1942
2145
|
-------
|
|
@@ -1953,10 +2156,18 @@ class DataWithAxes(DataBase):
|
|
|
1953
2156
|
for dat in self.data:
|
|
1954
2157
|
dat_ift.append(mutils.ift(dat, dim=axis))
|
|
1955
2158
|
new_data = self.deepcopy_with_new_data(dat_ift)
|
|
2159
|
+
if labels is not None:
|
|
2160
|
+
new_data.labels = labels
|
|
1956
2161
|
axis_obj = new_data.get_axis_from_index(axis)[0]
|
|
1957
2162
|
axis_obj.data = omega_grid
|
|
1958
|
-
|
|
1959
|
-
|
|
2163
|
+
if axis_label is not None:
|
|
2164
|
+
axis_obj.label = axis_label
|
|
2165
|
+
else:
|
|
2166
|
+
axis_obj.label = f'ift({axis_obj.label})'
|
|
2167
|
+
if axis_units is not None:
|
|
2168
|
+
axis_obj.force_units(axis_units)
|
|
2169
|
+
else:
|
|
2170
|
+
axis_obj.units = str(Unit(f'rad/({axis_obj.units})'))
|
|
1960
2171
|
return new_data
|
|
1961
2172
|
|
|
1962
2173
|
def fit(self, function: Callable, initial_guess: IterableType, data_index: int = None,
|
|
@@ -2156,11 +2367,22 @@ class DataWithAxes(DataBase):
|
|
|
2156
2367
|
axes.append(ax)
|
|
2157
2368
|
self.axes = axes
|
|
2158
2369
|
|
|
2159
|
-
def _compute_slices(self, slices, is_navigation=True):
|
|
2370
|
+
def _compute_slices(self, slices, is_navigation=True, is_index=True):
|
|
2160
2371
|
"""Compute the total slice to apply to the data
|
|
2161
2372
|
|
|
2162
2373
|
Filling in Ellipsis when no slicing should be done
|
|
2374
|
+
Parameters
|
|
2375
|
+
----------
|
|
2376
|
+
slices: List of slice
|
|
2377
|
+
is_navigation: bool
|
|
2378
|
+
is_index: bool
|
|
2379
|
+
if False, the slice are on the values of the underlying axes
|
|
2380
|
+
Returns
|
|
2381
|
+
-------
|
|
2382
|
+
list(slice): the computed slices as index (eventually for all axes)
|
|
2383
|
+
list(slice): a version as index of the input argument
|
|
2163
2384
|
"""
|
|
2385
|
+
_slices_as_index = []
|
|
2164
2386
|
if isinstance(slices, numbers.Number) or isinstance(slices, slice):
|
|
2165
2387
|
slices = [slices]
|
|
2166
2388
|
if is_navigation:
|
|
@@ -2171,13 +2393,18 @@ class DataWithAxes(DataBase):
|
|
|
2171
2393
|
slices = list(slices)
|
|
2172
2394
|
for ind in range(len(self.shape)):
|
|
2173
2395
|
if ind in indexes:
|
|
2174
|
-
|
|
2396
|
+
_slice = slices.pop(0)
|
|
2397
|
+
if not is_index:
|
|
2398
|
+
axis = self.get_axis_from_index(ind)[0]
|
|
2399
|
+
_slice = _compute_slices_from_axis(axis, _slice, is_index=is_index)
|
|
2400
|
+
_slices_as_index.append(_slice)
|
|
2401
|
+
total_slices.append(_slice)
|
|
2175
2402
|
elif len(total_slices) == 0:
|
|
2176
2403
|
total_slices.append(Ellipsis)
|
|
2177
2404
|
elif not (Ellipsis in total_slices and total_slices[-1] is Ellipsis):
|
|
2178
2405
|
total_slices.append(slice(None))
|
|
2179
2406
|
total_slices = tuple(total_slices)
|
|
2180
|
-
return total_slices
|
|
2407
|
+
return total_slices, _slices_as_index
|
|
2181
2408
|
|
|
2182
2409
|
def check_squeeze(self, total_slices: List[slice], is_navigation: bool):
|
|
2183
2410
|
|
|
@@ -2189,7 +2416,7 @@ class DataWithAxes(DataBase):
|
|
|
2189
2416
|
do_squeeze = False
|
|
2190
2417
|
return do_squeeze
|
|
2191
2418
|
|
|
2192
|
-
def _slicer(self, slices, is_navigation=True):
|
|
2419
|
+
def _slicer(self, slices, is_navigation=True, is_index=True):
|
|
2193
2420
|
"""Apply a given slice to the data either navigation or signal dimension
|
|
2194
2421
|
|
|
2195
2422
|
Parameters
|
|
@@ -2198,6 +2425,8 @@ class DataWithAxes(DataBase):
|
|
|
2198
2425
|
the slices to apply to the data
|
|
2199
2426
|
is_navigation: bool
|
|
2200
2427
|
if True apply the slices to the navigation dimension else to the signal ones
|
|
2428
|
+
is_index: bool
|
|
2429
|
+
if True the slices are indexes otherwise the slices are axes values to be indexed first
|
|
2201
2430
|
|
|
2202
2431
|
Returns
|
|
2203
2432
|
-------
|
|
@@ -2205,10 +2434,10 @@ class DataWithAxes(DataBase):
|
|
|
2205
2434
|
Object of the same type as the initial data, derived from DataWithAxes. But with lower
|
|
2206
2435
|
data size due to the slicing and with eventually less axes.
|
|
2207
2436
|
"""
|
|
2208
|
-
|
|
2209
2437
|
if isinstance(slices, numbers.Number) or isinstance(slices, slice):
|
|
2210
2438
|
slices = [slices]
|
|
2211
|
-
|
|
2439
|
+
|
|
2440
|
+
total_slices, slices = self._compute_slices(slices, is_navigation, is_index=is_index)
|
|
2212
2441
|
|
|
2213
2442
|
do_squeeze = self.check_squeeze(total_slices, is_navigation)
|
|
2214
2443
|
new_arrays_data = [squeeze(dat[total_slices], do_squeeze) for dat in self.data]
|
|
@@ -737,7 +737,7 @@ class DataToExportSaver:
|
|
|
737
737
|
def __init__(self, h5saver: Union[H5SaverLowLevel, Path, str]):
|
|
738
738
|
if isinstance(h5saver, Path) or isinstance(h5saver, str):
|
|
739
739
|
h5saver_tmp = H5SaverLowLevel()
|
|
740
|
-
h5saver_tmp.init_file(
|
|
740
|
+
h5saver_tmp.init_file(file_name=Path(h5saver))
|
|
741
741
|
h5saver = h5saver_tmp
|
|
742
742
|
|
|
743
743
|
self._h5saver = h5saver
|
|
@@ -748,7 +748,7 @@ class DataToExportSaver:
|
|
|
748
748
|
return self._h5saver.get_node(where)
|
|
749
749
|
|
|
750
750
|
def close(self):
|
|
751
|
-
self.
|
|
751
|
+
self.close_file()
|
|
752
752
|
|
|
753
753
|
def close_file(self):
|
|
754
754
|
self._h5saver.close_file()
|
|
@@ -84,11 +84,17 @@ def _min(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] =
|
|
|
84
84
|
def _std(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
|
|
85
85
|
return process_with_reduced_dimensions(np.std, dwa, *args, axis=axis, **kwargs)
|
|
86
86
|
|
|
87
|
+
|
|
87
88
|
@implements("mean")
|
|
88
89
|
def _mean(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
|
|
89
90
|
return process_with_reduced_dimensions(np.mean, dwa, *args, axis=axis, **kwargs)
|
|
90
91
|
|
|
91
92
|
|
|
93
|
+
@implements("sum")
|
|
94
|
+
def _sum(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
|
|
95
|
+
return process_with_reduced_dimensions(np.sum, dwa, *args, axis=axis, **kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
92
98
|
# ************* FUNCTIONS that apply with units ********
|
|
93
99
|
|
|
94
100
|
@implements('angle')
|
|
@@ -100,6 +106,14 @@ def _angle(dwa: 'DataWithAxes', *args, **kwargs):
|
|
|
100
106
|
return dwa_func
|
|
101
107
|
|
|
102
108
|
|
|
109
|
+
@implements('unwrap')
|
|
110
|
+
def _unwrap(dwa: 'DataWithAxes', *args, **kwargs):
|
|
111
|
+
dwa_func = dwa.deepcopy_with_new_data(
|
|
112
|
+
data=[np.unwrap(array, *args, **kwargs) for array in dwa.data])
|
|
113
|
+
dwa_func.name += f"_{'unwrap'}"
|
|
114
|
+
return dwa_func
|
|
115
|
+
|
|
116
|
+
|
|
103
117
|
@implements('real')
|
|
104
118
|
def _real(dwa: 'DataWithAxes', *args, **kwargs):
|
|
105
119
|
|
|
@@ -13,12 +13,15 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
class SpecialSlicers(object):
|
|
15
15
|
"""make it elegant to apply a slice to navigation or signal dimensions"""
|
|
16
|
-
def __init__(self, obj: Union['DataWithAxes', 'Axis'], is_navigation):
|
|
16
|
+
def __init__(self, obj: Union['DataWithAxes', 'Axis'], is_navigation, is_index=True):
|
|
17
17
|
self.is_navigation = is_navigation
|
|
18
|
+
self.is_index = is_index
|
|
18
19
|
self.obj = obj
|
|
19
20
|
|
|
20
21
|
def __getitem__(self, slices) -> Union['DataWithAxes', 'Axis']:
|
|
21
|
-
|
|
22
|
+
if hasattr(self.obj, 'axes') and self.obj.distribution.name == 'spread' and not self.is_index:
|
|
23
|
+
raise NotImplementedError('Impossible to slice by value a spread data object')
|
|
24
|
+
return self.obj._slicer(slices, self.is_navigation, is_index=self.is_index)
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class SpecialSlicersData(SpecialSlicers):
|
|
@@ -26,7 +29,7 @@ class SpecialSlicersData(SpecialSlicers):
|
|
|
26
29
|
def __setitem__(self, slices, data: Union[np.ndarray, 'DataWithAxes', 'Axis']):
|
|
27
30
|
"""x.__setitem__(slices, data) <==> x[slices]=data
|
|
28
31
|
"""
|
|
29
|
-
|
|
32
|
+
total_slices, slices_index = self.obj._compute_slices(slices, self.is_navigation, is_index=self.is_index)
|
|
30
33
|
|
|
31
34
|
if hasattr(self.obj, 'base_type') and self.obj.base_type == 'Axis':
|
|
32
35
|
if isinstance(data, np.ndarray):
|
|
@@ -35,14 +38,14 @@ class SpecialSlicersData(SpecialSlicers):
|
|
|
35
38
|
data_to_replace = data.get_data()
|
|
36
39
|
if hasattr(self.obj, 'units') and self.obj.data is None:
|
|
37
40
|
self.obj.create_linear_data(len(self.obj))
|
|
38
|
-
self.obj.data[
|
|
41
|
+
self.obj.data[total_slices] = data_to_replace
|
|
39
42
|
else:
|
|
40
43
|
for ind in range(len(self.obj)):
|
|
41
44
|
if isinstance(data, np.ndarray):
|
|
42
45
|
data_to_replace = data
|
|
43
46
|
else: # means it's a DataWithAxes
|
|
44
47
|
data_to_replace = data[ind]
|
|
45
|
-
self.obj[ind][
|
|
48
|
+
self.obj[ind][total_slices] = data_to_replace
|
|
46
49
|
|
|
47
50
|
def __len__(self):
|
|
48
51
|
return self.obj.axes_manager.sig_shape[0]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymodaq_data-5.0.3 → pymodaq_data-5.0.5}/src/pymodaq_data/post_treatment/process_to_scalar.py
RENAMED
|
File without changes
|
|
File without changes
|