pymodaq_data 5.0.4__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.
Files changed (29) hide show
  1. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/PKG-INFO +1 -1
  2. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/_version.py +2 -2
  3. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/data.py +225 -39
  4. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/data_saving.py +2 -2
  5. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/numpy_func.py +14 -0
  6. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/slicing.py +8 -5
  7. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/.gitignore +0 -0
  8. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/LICENSE +0 -0
  9. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/README.rst +0 -0
  10. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/pyproject.toml +0 -0
  11. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/__init__.py +0 -0
  12. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/__init__.py +0 -0
  13. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/backends.py +0 -0
  14. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/browsing.py +0 -0
  15. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporter.py +0 -0
  16. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/__init__.py +0 -0
  17. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/base.py +0 -0
  18. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/flimj.py +0 -0
  19. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/exporters/hyperspy.py +0 -0
  20. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/saving.py +0 -0
  21. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/h5modules/utils.py +0 -0
  22. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/icon.ico +0 -0
  23. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/__init__.py +0 -0
  24. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotter.py +0 -0
  25. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
  26. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +0 -0
  27. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/post_treatment/__init__.py +0 -0
  28. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/src/pymodaq_data/post_treatment/process_to_scalar.py +0 -0
  29. {pymodaq_data-5.0.4 → pymodaq_data-5.0.5}/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.5
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.5'
16
+ __version_tuple__ = version_tuple = (5, 0, 5)
@@ -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(
@@ -788,6 +884,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
788
884
  units = dwa.units
789
885
  ufunc_results = [ufunc(*zipped, **kwargs) for zipped in list(zip(*elts))]
790
886
  if isinstance(ufunc_results[0], Q_):
887
+ ufunc_results = [ufunc_result.to_reduced_units() for ufunc_result in ufunc_results]
791
888
  units = str(ufunc_results[0].units)
792
889
  ufunc_results = [ufunc_result.magnitude for ufunc_result in ufunc_results]
793
890
  dwa.data = ufunc_results
@@ -881,7 +978,13 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
881
978
 
882
979
  def angle(self):
883
980
  """ Take the phase value of itself"""
884
- return np.angle(self)
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)
885
988
 
886
989
  def real(self):
887
990
  """ Take the real part of itself"""
@@ -1002,15 +1105,17 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1002
1105
  """Get the data by its index in the list, same as self[index]"""
1003
1106
  return self.data[index]
1004
1107
 
1005
- @staticmethod
1006
- 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]:
1007
1109
  """make sure data is a list of nd-arrays"""
1008
1110
  is_valid = True
1009
1111
  if data is None:
1010
1112
  is_valid = False
1011
1113
  if not isinstance(data, list):
1012
1114
  # try to transform the data to regular type
1013
- if isinstance(data, np.ndarray):
1115
+ if isinstance(data, Q_):
1116
+ self.force_units(str(data.units))
1117
+ data = [data.magnitude]
1118
+ elif isinstance(data, np.ndarray):
1014
1119
  warnings.warn(DataTypeWarning(f'Your data should be a list of numpy arrays not just a single numpy'
1015
1120
  f' array, wrapping them with a list'))
1016
1121
  data = [data]
@@ -1023,12 +1128,16 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1023
1128
  if isinstance(data, list):
1024
1129
  if len(data) == 0:
1025
1130
  is_valid = False
1026
- elif not isinstance(data[0], np.ndarray):
1131
+ elif not (isinstance(data[0], np.ndarray) or
1132
+ isinstance(data[0], Q_)):
1027
1133
  is_valid = False
1028
1134
  elif len(data[0].shape) == 0:
1029
1135
  is_valid = False
1030
1136
  if not is_valid:
1031
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]
1032
1141
  return data
1033
1142
 
1034
1143
  def check_shape_from_data(self, data: List[np.ndarray]):
@@ -1093,7 +1202,7 @@ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
1093
1202
  return self._data
1094
1203
 
1095
1204
  @data.setter
1096
- def data(self, data: List[np.ndarray]):
1205
+ def data(self, data: List[Union[np.ndarray, Q_]]):
1097
1206
  data = self._check_data_type(data)
1098
1207
  self._check_shape_dim_consistency(data)
1099
1208
  self._check_same_shape(data)
@@ -1704,8 +1813,11 @@ class DataWithAxes(DataBase):
1704
1813
 
1705
1814
  self.set_axes_manager(self.shape, axes=axes, nav_indexes=nav_indexes, **other_kwargs)
1706
1815
 
1707
- self.inav: Iterable[DataWithAxes] = SpecialSlicersData(self, True)
1708
- self.isig: Iterable[DataWithAxes] = SpecialSlicersData(self, False)
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)
1709
1821
 
1710
1822
  self.get_dim_from_data_axes() # in DataBase, dim is processed from the shape of data, but if axes are provided
1711
1823
  #then use get_dim_from axes
@@ -1891,7 +2003,32 @@ class DataWithAxes(DataBase):
1891
2003
  mean = np.array([mean])
1892
2004
  dat_mean.append(mean)
1893
2005
  return self.deepcopy_with_new_data(dat_mean, remove_axes_index=axis)
1894
-
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
+
1895
2032
  def sum(self, axis: int = 0) -> DataWithAxes:
1896
2033
  """Process the sum of the data on the specified axis and returns the new data
1897
2034
 
@@ -1905,7 +2042,7 @@ class DataWithAxes(DataBase):
1905
2042
  """
1906
2043
  dat_sum = []
1907
2044
  for dat in self.data:
1908
- dat_sum.append(np.sum(dat, axis=axis))
2045
+ dat_sum.append(np.atleast_1d(np.sum(dat, axis=axis)))
1909
2046
  return self.deepcopy_with_new_data(dat_sum, remove_axes_index=axis)
1910
2047
 
1911
2048
  def interp(self, new_axis_data: Union[Axis, np.ndarray], **kwargs) -> DataWithAxes:
@@ -1945,12 +2082,20 @@ class DataWithAxes(DataBase):
1945
2082
  labels=self.labels)
1946
2083
  return new_data
1947
2084
 
1948
- def ft(self, axis: int = 0) -> DataWithAxes:
2085
+ def ft(self, axis: int = 0, axis_label: str = None,
2086
+ axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
1949
2087
  """Process the Fourier Transform of the data on the specified axis and returns the new data
1950
2088
 
1951
2089
  Parameters
1952
2090
  ----------
1953
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
1954
2099
 
1955
2100
  Returns
1956
2101
  -------
@@ -1967,19 +2112,34 @@ class DataWithAxes(DataBase):
1967
2112
  for dat in self.data:
1968
2113
  dat_ft.append(mutils.ft(dat, dim=axis))
1969
2114
  new_data = self.deepcopy_with_new_data(dat_ft)
2115
+ if labels is not None:
2116
+ new_data.labels = labels
1970
2117
  axis_obj = new_data.get_axis_from_index(axis)[0]
1971
2118
  axis_obj.data = omega_grid
1972
- axis_obj.label = f'ft({axis_obj.label})'
1973
- axis_obj.units = f'rad/{axis_obj.units}'
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}'
1974
2127
  return new_data
1975
2128
 
1976
- def ift(self, axis: int = 0) -> DataWithAxes:
2129
+ def ift(self, axis: int = 0, axis_label: str = None,
2130
+ axis_units: str = None, labels: List[str] = None) -> DataWithAxes:
1977
2131
  """Process the inverse Fourier Transform of the data on the specified axis and returns the
1978
2132
  new data
1979
2133
 
1980
2134
  Parameters
1981
2135
  ----------
1982
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
1983
2143
 
1984
2144
  Returns
1985
2145
  -------
@@ -1996,10 +2156,18 @@ class DataWithAxes(DataBase):
1996
2156
  for dat in self.data:
1997
2157
  dat_ift.append(mutils.ift(dat, dim=axis))
1998
2158
  new_data = self.deepcopy_with_new_data(dat_ift)
2159
+ if labels is not None:
2160
+ new_data.labels = labels
1999
2161
  axis_obj = new_data.get_axis_from_index(axis)[0]
2000
2162
  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})'))
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})'))
2003
2171
  return new_data
2004
2172
 
2005
2173
  def fit(self, function: Callable, initial_guess: IterableType, data_index: int = None,
@@ -2199,11 +2367,22 @@ class DataWithAxes(DataBase):
2199
2367
  axes.append(ax)
2200
2368
  self.axes = axes
2201
2369
 
2202
- def _compute_slices(self, slices, is_navigation=True):
2370
+ def _compute_slices(self, slices, is_navigation=True, is_index=True):
2203
2371
  """Compute the total slice to apply to the data
2204
2372
 
2205
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
2206
2384
  """
2385
+ _slices_as_index = []
2207
2386
  if isinstance(slices, numbers.Number) or isinstance(slices, slice):
2208
2387
  slices = [slices]
2209
2388
  if is_navigation:
@@ -2214,13 +2393,18 @@ class DataWithAxes(DataBase):
2214
2393
  slices = list(slices)
2215
2394
  for ind in range(len(self.shape)):
2216
2395
  if ind in indexes:
2217
- total_slices.append(slices.pop(0))
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)
2218
2402
  elif len(total_slices) == 0:
2219
2403
  total_slices.append(Ellipsis)
2220
2404
  elif not (Ellipsis in total_slices and total_slices[-1] is Ellipsis):
2221
2405
  total_slices.append(slice(None))
2222
2406
  total_slices = tuple(total_slices)
2223
- return total_slices
2407
+ return total_slices, _slices_as_index
2224
2408
 
2225
2409
  def check_squeeze(self, total_slices: List[slice], is_navigation: bool):
2226
2410
 
@@ -2232,7 +2416,7 @@ class DataWithAxes(DataBase):
2232
2416
  do_squeeze = False
2233
2417
  return do_squeeze
2234
2418
 
2235
- def _slicer(self, slices, is_navigation=True):
2419
+ def _slicer(self, slices, is_navigation=True, is_index=True):
2236
2420
  """Apply a given slice to the data either navigation or signal dimension
2237
2421
 
2238
2422
  Parameters
@@ -2241,6 +2425,8 @@ class DataWithAxes(DataBase):
2241
2425
  the slices to apply to the data
2242
2426
  is_navigation: bool
2243
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
2244
2430
 
2245
2431
  Returns
2246
2432
  -------
@@ -2248,10 +2434,10 @@ class DataWithAxes(DataBase):
2248
2434
  Object of the same type as the initial data, derived from DataWithAxes. But with lower
2249
2435
  data size due to the slicing and with eventually less axes.
2250
2436
  """
2251
-
2252
2437
  if isinstance(slices, numbers.Number) or isinstance(slices, slice):
2253
2438
  slices = [slices]
2254
- total_slices = self._compute_slices(slices, is_navigation)
2439
+
2440
+ total_slices, slices = self._compute_slices(slices, is_navigation, is_index=is_index)
2255
2441
 
2256
2442
  do_squeeze = self.check_squeeze(total_slices, is_navigation)
2257
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(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