pymodaq_data 5.0.4__tar.gz → 5.0.6__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.
Files changed (29) hide show
  1. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/PKG-INFO +1 -1
  2. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/_version.py +2 -2
  3. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/data.py +232 -39
  4. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/data_saving.py +2 -2
  5. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/numpy_func.py +14 -0
  6. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/slicing.py +8 -5
  7. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/.gitignore +0 -0
  8. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/LICENSE +0 -0
  9. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/README.rst +0 -0
  10. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/pyproject.toml +0 -0
  11. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/__init__.py +0 -0
  12. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/__init__.py +0 -0
  13. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/backends.py +0 -0
  14. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/browsing.py +0 -0
  15. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/exporter.py +0 -0
  16. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/exporters/__init__.py +0 -0
  17. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/exporters/base.py +0 -0
  18. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/exporters/flimj.py +0 -0
  19. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/exporters/hyperspy.py +0 -0
  20. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/saving.py +0 -0
  21. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/h5modules/utils.py +0 -0
  22. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/icon.ico +0 -0
  23. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/plotting/__init__.py +0 -0
  24. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/plotting/plotter/plotter.py +0 -0
  25. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
  26. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +0 -0
  27. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/post_treatment/__init__.py +0 -0
  28. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/post_treatment/process_to_scalar.py +0 -0
  29. {pymodaq_data-5.0.4 → pymodaq_data-5.0.6}/src/pymodaq_data/splash.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pymodaq_data
3
- Version: 5.0.4
3
+ Version: 5.0.6
4
4
  Summary: Modular Data Acquisition with Python
5
5
  Project-URL: Homepage, http://pymodaq.cnrs.fr
6
6
  Project-URL: Source, https://github.com/PyMoDAQ/PyMoDAQ
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '5.0.4'
16
- __version_tuple__ = version_tuple = (5, 0, 4)
15
+ __version__ = version = '5.0.6'
16
+ __version_tuple__ = version_tuple = (5, 0, 6)
@@ -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, data=[self.get_data()],
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
- dwa.create_missing_axes()
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
- if not isinstance(units, str):
243
- raise TypeError('units for the Axis class should be a string')
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
- @staticmethod
350
- def _check_data_valid(data):
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, slices, *ignored, **ignored_also):
372
- return slices
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
- if isinstance(_slice, int):
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) -> 'DataBase':
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
- arrays.append(self.quantities[ind_array].m_as(units))
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,13 @@ 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 to_base_units(self):
778
+ dwa = self.deepcopy()
779
+ data_quantities = [quantity.to_base_units() for quantity in self.quantities]
780
+ dwa.data = [quantity.magnitude for quantity in data_quantities]
781
+ dwa.force_units(str(data_quantities[0].units))
782
+ return dwa
783
+
681
784
  def value(self, units: str = None) -> float:
682
785
  """Returns the underlying float value (of the first elt in the data list) if this data
683
786
  holds only a float otherwise returns a mean of the underlying data
@@ -788,6 +891,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
788
891
  units = dwa.units
789
892
  ufunc_results = [ufunc(*zipped, **kwargs) for zipped in list(zip(*elts))]
790
893
  if isinstance(ufunc_results[0], Q_):
894
+ ufunc_results = [ufunc_result.to_reduced_units() for ufunc_result in ufunc_results]
791
895
  units = str(ufunc_results[0].units)
792
896
  ufunc_results = [ufunc_result.magnitude for ufunc_result in ufunc_results]
793
897
  dwa.data = ufunc_results
@@ -881,7 +985,13 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
881
985
 
882
986
  def angle(self):
883
987
  """ Take the phase value of itself"""
884
- return np.angle(self)
988
+ dwa_angle = np.angle(self)
989
+ dwa_angle.force_units('rad')
990
+ return dwa_angle
991
+
992
+ def unwrap(self):
993
+ """ unwrap the underlying array (should be angles otherwise meaningless)"""
994
+ return np.unwrap(self)
885
995
 
886
996
  def real(self):
887
997
  """ Take the real part of itself"""
@@ -1002,15 +1112,17 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1002
1112
  """Get the data by its index in the list, same as self[index]"""
1003
1113
  return self.data[index]
1004
1114
 
1005
- @staticmethod
1006
- def _check_data_type(data: List[np.ndarray]) -> List[np.ndarray]:
1115
+ def _check_data_type(self, data: List[Union[np.ndarray, Q_]]) -> List[np.ndarray]:
1007
1116
  """make sure data is a list of nd-arrays"""
1008
1117
  is_valid = True
1009
1118
  if data is None:
1010
1119
  is_valid = False
1011
1120
  if not isinstance(data, list):
1012
1121
  # try to transform the data to regular type
1013
- if isinstance(data, np.ndarray):
1122
+ if isinstance(data, Q_):
1123
+ self.force_units(str(data.units))
1124
+ data = [data.magnitude]
1125
+ elif isinstance(data, np.ndarray):
1014
1126
  warnings.warn(DataTypeWarning(f'Your data should be a list of numpy arrays not just a single numpy'
1015
1127
  f' array, wrapping them with a list'))
1016
1128
  data = [data]
@@ -1023,12 +1135,16 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1023
1135
  if isinstance(data, list):
1024
1136
  if len(data) == 0:
1025
1137
  is_valid = False
1026
- elif not isinstance(data[0], np.ndarray):
1138
+ elif not (isinstance(data[0], np.ndarray) or
1139
+ isinstance(data[0], Q_)):
1027
1140
  is_valid = False
1028
1141
  elif len(data[0].shape) == 0:
1029
1142
  is_valid = False
1030
1143
  if not is_valid:
1031
1144
  raise TypeError(f'Data should be an non-empty list of non-empty numpy arrays')
1145
+ if isinstance(data[0], Q_):
1146
+ self.force_units(str(data[0].units))
1147
+ data = [array.magnitude for array in data]
1032
1148
  return data
1033
1149
 
1034
1150
  def check_shape_from_data(self, data: List[np.ndarray]):
@@ -1093,7 +1209,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1093
1209
  return self._data
1094
1210
 
1095
1211
  @data.setter
1096
- def data(self, data: List[np.ndarray]):
1212
+ def data(self, data: List[Union[np.ndarray, Q_]]):
1097
1213
  data = self._check_data_type(data)
1098
1214
  self._check_shape_dim_consistency(data)
1099
1215
  self._check_same_shape(data)
@@ -1704,8 +1820,11 @@ class DataWithAxes(DataBase):
1704
1820
 
1705
1821
  self.set_axes_manager(self.shape, axes=axes, nav_indexes=nav_indexes, **other_kwargs)
1706
1822
 
1707
- self.inav: Iterable[DataWithAxes] = SpecialSlicersData(self, True)
1708
- self.isig: Iterable[DataWithAxes] = SpecialSlicersData(self, False)
1823
+ self.inav = SpecialSlicersData(self, True)
1824
+ self.isig = SpecialSlicersData(self, False)
1825
+
1826
+ self.vnav = SpecialSlicersData(self, True, is_index=False)
1827
+ self.vsig = SpecialSlicersData(self, False, is_index=False)
1709
1828
 
1710
1829
  self.get_dim_from_data_axes() # in DataBase, dim is processed from the shape of data, but if axes are provided
1711
1830
  #then use get_dim_from axes
@@ -1891,7 +2010,32 @@ class DataWithAxes(DataBase):
1891
2010
  mean = np.array([mean])
1892
2011
  dat_mean.append(mean)
1893
2012
  return self.deepcopy_with_new_data(dat_mean, remove_axes_index=axis)
1894
-
2013
+
2014
+ def moment(self) -> Tuple[DataWithAxes, DataWithAxes]:
2015
+ """ returns the two first moments of the data over the axis
2016
+
2017
+ only valid for Data1D data
2018
+
2019
+ Returns
2020
+ -------
2021
+ DataCalculated: containing the moment of order 0 (mean)
2022
+ DataCalculated: containing the moment of order 1 (std)
2023
+ """
2024
+ if self.dim != DataDim.Data1D:
2025
+ raise DataDimError('the moment method is only valid for 1D data')
2026
+ arrays_mean = []
2027
+ arrays_std = []
2028
+ for data in self:
2029
+ mean, std = mutils.my_moment(self.axes[0].get_data(), data)
2030
+ arrays_mean.append(np.array([mean]))
2031
+ arrays_std.append(np.array([std]))
2032
+
2033
+ return (DataCalculated('mean', data=arrays_mean),
2034
+ DataCalculated('std', data=arrays_std))
2035
+
2036
+
2037
+
2038
+
1895
2039
  def sum(self, axis: int = 0) -> DataWithAxes:
1896
2040
  """Process the sum of the data on the specified axis and returns the new data
1897
2041
 
@@ -1905,7 +2049,7 @@ class DataWithAxes(DataBase):
1905
2049
  """
1906
2050
  dat_sum = []
1907
2051
  for dat in self.data:
1908
- dat_sum.append(np.sum(dat, axis=axis))
2052
+ dat_sum.append(np.atleast_1d(np.sum(dat, axis=axis)))
1909
2053
  return self.deepcopy_with_new_data(dat_sum, remove_axes_index=axis)
1910
2054
 
1911
2055
  def interp(self, new_axis_data: Union[Axis, np.ndarray], **kwargs) -> DataWithAxes:
@@ -1945,12 +2089,20 @@ class DataWithAxes(DataBase):
1945
2089
  labels=self.labels)
1946
2090
  return new_data
1947
2091
 
1948
- def ft(self, axis: int = 0) -> DataWithAxes:
2092
+ def ft(self, axis: int = 0, axis_label: str = None,
2093
+ axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
1949
2094
  """Process the Fourier Transform of the data on the specified axis and returns the new data
1950
2095
 
1951
2096
  Parameters
1952
2097
  ----------
1953
2098
  axis: int
2099
+ Apply the FT on this axis index
2100
+ axis_label: str
2101
+ A new label for the FT computed axis
2102
+ axis_units: str
2103
+ New units (without conversion on top of the one from the FT) for the computed axis
2104
+ labels: List[str]
2105
+ list of string for new labels
1954
2106
 
1955
2107
  Returns
1956
2108
  -------
@@ -1967,19 +2119,34 @@ class DataWithAxes(DataBase):
1967
2119
  for dat in self.data:
1968
2120
  dat_ft.append(mutils.ft(dat, dim=axis))
1969
2121
  new_data = self.deepcopy_with_new_data(dat_ft)
2122
+ if labels is not None:
2123
+ new_data.labels = labels
1970
2124
  axis_obj = new_data.get_axis_from_index(axis)[0]
1971
2125
  axis_obj.data = omega_grid
1972
- axis_obj.label = f'ft({axis_obj.label})'
1973
- axis_obj.units = f'rad/{axis_obj.units}'
2126
+ if axis_label is not None:
2127
+ axis_obj.label = axis_label
2128
+ else:
2129
+ axis_obj.label = f'ft({axis_obj.label})'
2130
+ if axis_units is not None:
2131
+ axis_obj.force_units(axis_units)
2132
+ else:
2133
+ axis_obj.units = f'rad/{axis_obj.units}'
1974
2134
  return new_data
1975
2135
 
1976
- def ift(self, axis: int = 0) -> DataWithAxes:
2136
+ def ift(self, axis: int = 0, axis_label: str = None,
2137
+ axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
1977
2138
  """Process the inverse Fourier Transform of the data on the specified axis and returns the
1978
2139
  new data
1979
2140
 
1980
2141
  Parameters
1981
2142
  ----------
1982
2143
  axis: int
2144
+ axis_label: str
2145
+ A new label for the FT computed axis
2146
+ axis_units: str
2147
+ New units (without conversion on top of the one from the FT) for the computed axis
2148
+ labels: List[str]
2149
+ list of string for new labels
1983
2150
 
1984
2151
  Returns
1985
2152
  -------
@@ -1996,10 +2163,18 @@ class DataWithAxes(DataBase):
1996
2163
  for dat in self.data:
1997
2164
  dat_ift.append(mutils.ift(dat, dim=axis))
1998
2165
  new_data = self.deepcopy_with_new_data(dat_ift)
2166
+ if labels is not None:
2167
+ new_data.labels = labels
1999
2168
  axis_obj = new_data.get_axis_from_index(axis)[0]
2000
2169
  axis_obj.data = omega_grid
2001
- axis_obj.label = f'ift({axis_obj.label})'
2002
- axis_obj.units = str(Unit(f'rad/({axis_obj.units})'))
2170
+ if axis_label is not None:
2171
+ axis_obj.label = axis_label
2172
+ else:
2173
+ axis_obj.label = f'ift({axis_obj.label})'
2174
+ if axis_units is not None:
2175
+ axis_obj.force_units(axis_units)
2176
+ else:
2177
+ axis_obj.units = str(Unit(f'rad/({axis_obj.units})'))
2003
2178
  return new_data
2004
2179
 
2005
2180
  def fit(self, function: Callable, initial_guess: IterableType, data_index: int = None,
@@ -2199,11 +2374,22 @@ class DataWithAxes(DataBase):
2199
2374
  axes.append(ax)
2200
2375
  self.axes = axes
2201
2376
 
2202
- def _compute_slices(self, slices, is_navigation=True):
2377
+ def _compute_slices(self, slices, is_navigation=True, is_index=True):
2203
2378
  """Compute the total slice to apply to the data
2204
2379
 
2205
2380
  Filling in Ellipsis when no slicing should be done
2381
+ Parameters
2382
+ ----------
2383
+ slices: List of slice
2384
+ is_navigation: bool
2385
+ is_index: bool
2386
+ if False, the slice are on the values of the underlying axes
2387
+ Returns
2388
+ -------
2389
+ list(slice): the computed slices as index (eventually for all axes)
2390
+ list(slice): a version as index of the input argument
2206
2391
  """
2392
+ _slices_as_index = []
2207
2393
  if isinstance(slices, numbers.Number) or isinstance(slices, slice):
2208
2394
  slices = [slices]
2209
2395
  if is_navigation:
@@ -2214,13 +2400,18 @@ class DataWithAxes(DataBase):
2214
2400
  slices = list(slices)
2215
2401
  for ind in range(len(self.shape)):
2216
2402
  if ind in indexes:
2217
- total_slices.append(slices.pop(0))
2403
+ _slice = slices.pop(0)
2404
+ if not is_index:
2405
+ axis = self.get_axis_from_index(ind)[0]
2406
+ _slice = _compute_slices_from_axis(axis, _slice, is_index=is_index)
2407
+ _slices_as_index.append(_slice)
2408
+ total_slices.append(_slice)
2218
2409
  elif len(total_slices) == 0:
2219
2410
  total_slices.append(Ellipsis)
2220
2411
  elif not (Ellipsis in total_slices and total_slices[-1] is Ellipsis):
2221
2412
  total_slices.append(slice(None))
2222
2413
  total_slices = tuple(total_slices)
2223
- return total_slices
2414
+ return total_slices, _slices_as_index
2224
2415
 
2225
2416
  def check_squeeze(self, total_slices: List[slice], is_navigation: bool):
2226
2417
 
@@ -2232,7 +2423,7 @@ class DataWithAxes(DataBase):
2232
2423
  do_squeeze = False
2233
2424
  return do_squeeze
2234
2425
 
2235
- def _slicer(self, slices, is_navigation=True):
2426
+ def _slicer(self, slices, is_navigation=True, is_index=True):
2236
2427
  """Apply a given slice to the data either navigation or signal dimension
2237
2428
 
2238
2429
  Parameters
@@ -2241,6 +2432,8 @@ class DataWithAxes(DataBase):
2241
2432
  the slices to apply to the data
2242
2433
  is_navigation: bool
2243
2434
  if True apply the slices to the navigation dimension else to the signal ones
2435
+ is_index: bool
2436
+ if True the slices are indexes otherwise the slices are axes values to be indexed first
2244
2437
 
2245
2438
  Returns
2246
2439
  -------
@@ -2248,10 +2441,10 @@ class DataWithAxes(DataBase):
2248
2441
  Object of the same type as the initial data, derived from DataWithAxes. But with lower
2249
2442
  data size due to the slicing and with eventually less axes.
2250
2443
  """
2251
-
2252
2444
  if isinstance(slices, numbers.Number) or isinstance(slices, slice):
2253
2445
  slices = [slices]
2254
- total_slices = self._compute_slices(slices, is_navigation)
2446
+
2447
+ total_slices, slices = self._compute_slices(slices, is_navigation, is_index=is_index)
2255
2448
 
2256
2449
  do_squeeze = self.check_squeeze(total_slices, is_navigation)
2257
2450
  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(addhoc_file_path=Path(h5saver))
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._h5saver.close()
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
- return self.obj._slicer(slices, self.is_navigation)
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
- slices = self.obj._compute_slices(slices, self.is_navigation)
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[slices] = data_to_replace
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][slices] = data_to_replace
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