pymodaq_data 0.0.1__tar.gz → 0.0.3__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-0.0.1 → pymodaq_data-0.0.3}/PKG-INFO +1 -2
  2. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/README.rst +0 -1
  3. pymodaq_data-0.0.3/_version.py +16 -0
  4. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/pyproject.toml +3 -0
  5. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/__init__.py +1 -1
  6. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/data.py +52 -79
  7. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/data_saving.py +1 -1
  8. pymodaq_data-0.0.3/src/pymodaq_data/numpy_func.py +158 -0
  9. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/.gitignore +0 -0
  10. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/LICENSE +0 -0
  11. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/__init__.py +0 -0
  12. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/backends.py +0 -0
  13. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/browsing.py +0 -0
  14. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/exporter.py +0 -0
  15. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/exporters/__init__.py +0 -0
  16. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/exporters/base.py +0 -0
  17. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/exporters/flimj.py +0 -0
  18. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/exporters/hyperspy.py +0 -0
  19. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/saving.py +0 -0
  20. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/h5modules/utils.py +0 -0
  21. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/icon.ico +0 -0
  22. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/plotting/__init__.py +0 -0
  23. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/plotting/plotter/plotter.py +0 -0
  24. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
  25. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +0 -0
  26. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/post_treatment/__init__.py +0 -0
  27. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/post_treatment/process_to_scalar.py +0 -0
  28. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/slicing.py +0 -0
  29. {pymodaq_data-0.0.1 → pymodaq_data-0.0.3}/src/pymodaq_data/splash.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pymodaq_data
3
- Version: 0.0.1
3
+ Version: 0.0.3
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
@@ -135,7 +135,6 @@ or calculated from theory:
135
135
 
136
136
 
137
137
  .. figure:: https://pymodaq.cnrs.fr/en/latest/_images/data.png
138
- :alt: What is data?
139
138
 
140
139
  What is PyMoDAQ's data?.
141
140
 
@@ -78,7 +78,6 @@ or calculated from theory:
78
78
 
79
79
 
80
80
  .. figure:: https://pymodaq.cnrs.fr/en/latest/_images/data.png
81
- :alt: What is data?
82
81
 
83
82
  What is PyMoDAQ's data?.
84
83
 
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.3'
16
+ __version_tuple__ = version_tuple = (0, 0, 3)
@@ -56,3 +56,6 @@ source = "vcs"
56
56
  include = [
57
57
  "/src",
58
58
  ]
59
+
60
+ [tool.hatch.build.hooks.vcs]
61
+ version-file = "_version.py"
@@ -43,7 +43,7 @@ try:
43
43
  logger.info(f"Done")
44
44
  logger.info('************************')
45
45
 
46
- from pymodaq_data.data import (DataRaw, DataFromPlugins, DataWithAxes, DataToExport, Axis,
46
+ from pymodaq_data.data import (DataRaw, DataWithAxes, DataToExport, Axis,
47
47
  DataCalculated, DataDim, DataDistribution, DataSource, DataBase)
48
48
 
49
49
  except Exception as e:
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  from abc import ABCMeta, abstractmethod, abstractproperty
10
10
  import numbers
11
11
  import numpy as np
12
+ from numpy.lib.mixins import NDArrayOperatorsMixin
12
13
  from typing import List, Tuple, Union, Any, Callable
13
14
  from typing import Iterable as IterableType
14
15
  from collections.abc import Iterable
@@ -19,6 +20,7 @@ import warnings
19
20
  from time import time
20
21
  import copy
21
22
  import pint
23
+
22
24
  from multipledispatch import dispatch
23
25
 
24
26
  from pymodaq_utils.enums import BaseEnum, enum_checker
@@ -29,7 +31,7 @@ from pymodaq_data.slicing import SpecialSlicersData
29
31
  from pymodaq_utils import math_utils as mutils
30
32
  from pymodaq_utils.config import Config
31
33
  from pymodaq_data.plotting.plotter.plotter import PlotterFactory
32
-
34
+ from pymodaq_data.numpy_func import HANDLED_FUNCTIONS, HANDLED_UFUNCS, process_arguments_for_ufuncs
33
35
  from pymodaq_data import Q_, ureg, Unit
34
36
 
35
37
  config = Config()
@@ -515,7 +517,7 @@ class DataLowLevel:
515
517
  self._timestamp = timestamp
516
518
 
517
519
 
518
- class DataBase(DataLowLevel):
520
+ class DataBase(DataLowLevel, NDArrayOperatorsMixin):
519
521
  """Base object to store homogeneous data and metadata generated by pymodaq's objects.
520
522
 
521
523
  To be inherited for real data
@@ -672,6 +674,10 @@ class DataBase(DataLowLevel):
672
674
  new_data._units = units
673
675
  return new_data
674
676
 
677
+ def force_units(self, units: str):
678
+ """ Change immediately the units to whatever else. Use this with care!"""
679
+ self._units = units
680
+
675
681
  def as_dte(self, name: str = 'mydte') -> DataToExport:
676
682
  """Convenience method to wrap the DataWithAxes object into a DataToExport"""
677
683
  return DataToExport(name, data=[self])
@@ -726,70 +732,40 @@ class DataBase(DataLowLevel):
726
732
  else:
727
733
  raise IndexError(f'The index should be an positive integer lower than the data length')
728
734
 
729
- def __add__(self, other: object):
730
- if isinstance(other, DataBase) and len(other) == len(self):
731
- new_data = copy.deepcopy(self)
732
- for ind_array in range(len(new_data)):
733
- if self[ind_array].shape != other[ind_array].shape:
734
- raise ValueError('The shapes of arrays stored into the data are not consistent')
735
- try:
736
- new_data[ind_array] = (Q_(self[ind_array], self.units) +
737
- Q_(other[ind_array], other.units)).m_as(self.units)
738
- except pint.errors.DimensionalityError as e:
739
- raise DataUnitError(
740
- f'Cannot sum Data objects not having the same dimension: {e}')
741
- return new_data
735
+ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
736
+ ufunc_name = ufunc.__name__
737
+ if ufunc_name not in HANDLED_UFUNCS:
738
+ raise NotImplementedError
742
739
  else:
743
- raise TypeError(f'Could not add a {other.__class__.__name__} or a {self.__class__.__name__} '
744
- f'of a different length')
745
-
746
- def __sub__(self, other: object):
747
- return self.__add__(other * -1)
748
- #
749
- # if isinstance(other, DataBase) and len(other) == len(self):
750
- # new_data = copy.deepcopy(self)
751
- # for ind_array in range(len(new_data)):
752
- # new_data[ind_array] = self[ind_array] - other[ind_array]
753
- # return new_data
754
- # elif isinstance(other, numbers.Number) and self.length == 1 and self.size == 1:
755
- # new_data = copy.deepcopy(self)
756
- # new_data = new_data - DataActuator(data=other)
757
- # return new_data
758
- # else:
759
- # raise TypeError(f'Could not substract a {other.__class__.__name__} or a {self.__class__.__name__} '
760
- # f'of a different length')
761
-
762
- def __mul__(self, other):
763
- if (isinstance(other, numbers.Number) or
764
- (isinstance(other, np.ndarray) and other.shape == self._shape)):
765
- new_data = copy.deepcopy(self)
766
- for ind_array in range(len(new_data)):
767
- new_data[ind_array] = self[ind_array] * other
768
- return new_data
769
- elif isinstance(other, DataBase) and other.shape == self._shape:
770
- new_data = copy.deepcopy(self)
771
- new_unit = str((Q_(self[0], self.units) *
772
- Q_(other[0], other.units)).to_base_units().units)
773
- for ind_array in range(len(new_data)):
774
- new_data[ind_array] = \
775
- ((Q_(self[ind_array], self.units) * Q_(other[ind_array], other.units))
776
- .to_base_units()).magnitude
777
- new_data._units = new_unit
778
- return new_data
740
+ ufunc = HANDLED_UFUNCS[ufunc_name]
741
+ if method == '__call__':
742
+ elts = process_arguments_for_ufuncs(self, inputs)
743
+ dwa = self.deepcopy()
744
+ dwa.name = f'{self.name}_{ufunc_name}'
745
+ units = dwa.units
746
+ ufunc_results = [ufunc(*zipped, **kwargs) for zipped in list(zip(*elts))]
747
+ if isinstance(ufunc_results[0], Q_):
748
+ units = str(ufunc_results[0].units)
749
+ ufunc_results = [ufunc_result.magnitude for ufunc_result in ufunc_results]
750
+ dwa.data = ufunc_results
751
+ dwa.force_units(units)
752
+ return dwa
779
753
  else:
780
- raise TypeError(f'Could not multiply a {other.__class__.__name__} and a {self.__class__.__name__} '
781
- f'of a different length')
754
+ return NotImplemented
782
755
 
783
- def __truediv__(self, other):
784
- if isinstance(other, numbers.Number):
785
- return self * (1 / other)
756
+ def __array_function__(self, func, types, args, kwargs):
757
+ func_name = func.__name__
758
+ if func_name not in HANDLED_FUNCTIONS:
759
+ return NotImplemented
786
760
  else:
787
- raise TypeError(f'Could not divide a {other.__class__.__name__} and a {self.__class__.__name__} '
788
- f'of a different length')
761
+ func = HANDLED_FUNCTIONS[func_name]
762
+ if not all(issubclass(t, self.__class__) for t in types):
763
+ return NotImplemented
764
+ return func(*args, **kwargs)
789
765
 
790
766
  def _comparison_common(self, other, operator='__eq__'):
791
767
  if isinstance(other, DataBase):
792
- if not (self.name == other.name and
768
+ if not (#self.name == other.name and # who cares if the name is not the same?
793
769
  len(self) == len(other) and
794
770
  Unit(self.units).is_compatible_with(other.units)):
795
771
  return False
@@ -800,7 +776,10 @@ class DataBase(DataLowLevel):
800
776
  if self[ind].shape != other[ind].shape:
801
777
  eq = False
802
778
  break
803
- eq = eq and np.all(getattr(self.quantities[ind], operator)(other.quantities[ind]))
779
+ if operator == '__eq__':
780
+ eq = eq and np.allclose(self.quantities[ind], other.quantities[ind])
781
+ else:
782
+ eq = eq and np.all(getattr(self.quantities[ind], operator)(other.quantities[ind]))
804
783
  # extra attributes are not relevant as they may contain module specific data...
805
784
  # eq = eq and (self.extra_attributes == other.extra_attributes)
806
785
  # for attribute in self.extra_attributes:
@@ -811,9 +790,15 @@ class DataBase(DataLowLevel):
811
790
  else:
812
791
  raise TypeError()
813
792
 
793
+ def __bool__(self):
794
+ return all([np.all(array) for array in self])
795
+
814
796
  def __eq__(self, other):
815
797
  return self._comparison_common(other, '__eq__')
816
798
 
799
+ def __ne__(self, other):
800
+ return not self.__eq__(other)
801
+
817
802
  def __le__(self, other):
818
803
  return self._comparison_common(other, '__le__')
819
804
 
@@ -849,27 +834,19 @@ class DataBase(DataLowLevel):
849
834
 
850
835
  def abs(self):
851
836
  """ Take the absolute value of itself"""
852
- new_data = copy.copy(self)
853
- new_data.data = [np.abs(dat) for dat in new_data]
854
- return new_data
837
+ return np.abs(self)
855
838
 
856
839
  def angle(self):
857
840
  """ Take the phase value of itself"""
858
- new_data = copy.copy(self)
859
- new_data.data = [np.angle(dat) for dat in new_data]
860
- return new_data
841
+ return np.angle(self)
861
842
 
862
843
  def real(self):
863
844
  """ Take the real part of itself"""
864
- new_data = copy.copy(self)
865
- new_data.data = [np.real(dat) for dat in new_data]
866
- return new_data
845
+ return np.real(self)
867
846
 
868
847
  def imag(self):
869
848
  """ Take the imaginary part of itself"""
870
- new_data = copy.copy(self)
871
- new_data.data = [np.imag(dat) for dat in new_data]
872
- return new_data
849
+ return np.imag(self)
873
850
 
874
851
  def flipud(self):
875
852
  """Reverse the order of elements along axis 0 (up/down)"""
@@ -1091,11 +1068,7 @@ class DataBase(DataLowLevel):
1091
1068
 
1092
1069
  new in 4.3.0
1093
1070
  """
1094
- new_data = copy.deepcopy(self)
1095
- for ind_array in range(len(new_data)):
1096
- new_data[ind_array] = 10 * np.log10(self[ind_array] / self[ind_array].max())
1097
- new_data._units = 'dB'
1098
- return new_data
1071
+ return 10 * np.log10(self / np.max(self))
1099
1072
 
1100
1073
 
1101
1074
  class AxesManagerBase:
@@ -2274,14 +2247,14 @@ class DataWithAxes(DataBase):
2274
2247
 
2275
2248
  data = DataWithAxes(self.name, data=new_arrays_data, nav_indexes=tuple(nav_indexes),
2276
2249
  axes=axes,
2277
- source='calculated', origin=self.origin,
2250
+ source=DataSource.calculated, origin=self.origin,
2278
2251
  labels=self.labels[:],
2279
2252
  distribution=distribution)
2280
2253
  return data
2281
2254
 
2282
2255
  def deepcopy_with_new_data(self, data: List[np.ndarray] = None,
2283
2256
  remove_axes_index: Union[int, List[int]] = None,
2284
- source: DataSource = 'calculated',
2257
+ source: DataSource = DataSource.calculated,
2285
2258
  keep_dim=False) -> DataWithAxes:
2286
2259
  """deepcopy without copying the initial data (saving memory)
2287
2260
 
@@ -283,7 +283,7 @@ class DataSaverLoader(DataManagement):
283
283
 
284
284
  if isinstance(h5saver, Path) or isinstance(h5saver, str):
285
285
  h5saver_tmp = H5SaverLowLevel()
286
- h5saver_tmp.init_file(addhoc_file_path=Path(h5saver))
286
+ h5saver_tmp.init_file(file_name=Path(h5saver))
287
287
  h5saver = h5saver_tmp
288
288
 
289
289
  self._h5saver = h5saver
@@ -0,0 +1,158 @@
1
+ from typing import Union, List, TYPE_CHECKING, Iterable, Optional, Callable
2
+ import numbers
3
+
4
+ import numpy as np
5
+ from pint.facets.numpy.numpy_func import HANDLED_UFUNCS # imported by the data module
6
+ from pymodaq_data import Q_
7
+ from pymodaq_data import data as data_mod
8
+
9
+ if TYPE_CHECKING:
10
+ from pymodaq_data.data import DataBase, DataWithAxes
11
+
12
+ HANDLED_FUNCTIONS = {}
13
+
14
+
15
+ def process_arguments_for_ufuncs(input: 'DataBase',
16
+ inputs: List[Union[numbers.Number, Q_, np.ndarray, 'DataBase']]):
17
+ """
18
+
19
+ Parameters
20
+ ----------
21
+ input: 'DataBase'
22
+ inputs: list of elts in a numpy operation, could be numbers, quantities, ndarray, or 'DataBase'
23
+
24
+ Returns
25
+ -------
26
+ list of numbers, quantities or numpy arrays for applying to pint handled functions
27
+ """
28
+ elts = []
29
+ for elt in inputs:
30
+ if isinstance(elt, numbers.Number):
31
+ elts.append([elt for _ in range(input.length)])
32
+ elif isinstance(elt, Q_): # take its magnitude
33
+ elts.append([elt for _ in range(input.length)])
34
+ elif isinstance(elt, np.ndarray):
35
+ if elt.size != input.size:
36
+ raise TypeError("inconsistent sizes")
37
+ elts.append([elt for _ in range(input.length)])
38
+ else:
39
+ try:
40
+ elts.append([Q_(array, elt.units) for array in elt.data])
41
+ except:
42
+ return NotImplementedError
43
+ return elts
44
+
45
+
46
+ def implements(np_function):
47
+ """Register an __array_function__ implementation for DataWithAxes."""
48
+ def decorator(func):
49
+ HANDLED_FUNCTIONS[np_function] = func
50
+ return func
51
+ return decorator
52
+
53
+ # ********* FUNCTIONS that reduce dimensions *****************
54
+
55
+ def process_with_reduced_dimensions(func: Callable, dwa: 'DataWithAxes',
56
+ axis: Optional[Union[int, Iterable[int]]] = None,
57
+ *args, **kwargs):
58
+ all_axes = list(dwa.nav_indexes) + list(dwa.sig_indexes)
59
+ if axis is None:
60
+ remove_axis = all_axes
61
+ elif isinstance(axis, int):
62
+ remove_axis = all_axes[axis]
63
+ else:
64
+ remove_axis = [all_axes[axis_index] for axis_index in axis]
65
+ dwa_func = dwa.deepcopy_with_new_data(
66
+ data=[np.atleast_1d(func(dwa.data[ind], axis, *args, **kwargs)) for ind in range(len(dwa))],
67
+ remove_axes_index=remove_axis
68
+ )
69
+ dwa_func.name += f'_{func.__name__}'
70
+ return dwa_func
71
+
72
+
73
+ @implements('max')
74
+ def _max(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
75
+ return process_with_reduced_dimensions(np.max, dwa, *args, axis=axis, **kwargs)
76
+
77
+
78
+ @implements('min')
79
+ def _min(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
80
+ return process_with_reduced_dimensions(np.min, dwa, *args, axis=axis, **kwargs)
81
+
82
+
83
+ @implements("std")
84
+ def _std(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
85
+ return process_with_reduced_dimensions(np.std, dwa, *args, axis=axis, **kwargs)
86
+
87
+ @implements("mean")
88
+ def _mean(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
89
+ return process_with_reduced_dimensions(np.mean, dwa, *args, axis=axis, **kwargs)
90
+
91
+
92
+ # ************* FUNCTIONS that apply with units ********
93
+
94
+ @implements('angle')
95
+ def _angle(dwa: 'DataWithAxes', *args, **kwargs):
96
+
97
+ dwa_func = dwa.deepcopy_with_new_data(
98
+ data=[np.angle(array, *args, **kwargs) for array in dwa.data])
99
+ dwa_func.name += f"_{'angle'}"
100
+ return dwa_func
101
+
102
+
103
+ @implements('real')
104
+ def _real(dwa: 'DataWithAxes', *args, **kwargs):
105
+
106
+ dwa_func = dwa.deepcopy_with_new_data(
107
+ data=[np.real(array, *args, **kwargs) for array in dwa.data])
108
+ dwa_func.name += f"_{'real'}"
109
+ return dwa_func
110
+
111
+
112
+ @implements('imag')
113
+ def _imag(dwa: 'DataWithAxes', *args, **kwargs):
114
+
115
+ dwa_func = dwa.deepcopy_with_new_data(
116
+ data=[np.imag(array, *args, **kwargs) for array in dwa.data])
117
+ dwa_func.name += f"_{'imag'}"
118
+ return dwa_func
119
+
120
+
121
+ @implements('absolute')
122
+ def _absolute(dwa: 'DataWithAxes', *args, **kwargs):
123
+
124
+ dwa_func = dwa.deepcopy_with_new_data(
125
+ data=[np.absolute(array, *args, **kwargs) for array in dwa.data])
126
+ dwa_func.name += f"_{'absolute'}"
127
+ return dwa_func
128
+
129
+
130
+ @implements('abs')
131
+ def _abs(dwa: 'DataWithAxes', *args, **kwargs):
132
+ return np.absolute(dwa, *args, **kwargs)
133
+
134
+
135
+ # ******** functions that return booleans ***********
136
+ @implements('all')
137
+ def _all(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
138
+ return process_with_reduced_dimensions(np.all, dwa, *args, axis=axis, **kwargs)
139
+
140
+
141
+ @implements('any')
142
+ def _any(dwa: 'DataWithAxes', *args, axis: Optional[Union[int, Iterable[int]]] = None, **kwargs):
143
+ return process_with_reduced_dimensions(np.any, dwa, *args, axis=axis, **kwargs)
144
+
145
+
146
+ @implements('allclose')
147
+ def _allclose(dwa_a: 'DataWithAxes', dwa_b: 'DataWithAxes', *args,
148
+ **kwargs):
149
+ if dwa_a.size != dwa_b.size or dwa_a.length != dwa_b.length or dwa_a.shape != dwa_b.shape:
150
+ raise ValueError("The two DataWithAxes objects doesn't have arrays of same shape, "
151
+ "size or length")
152
+ dwa = data_mod.DataCalculated(
153
+ f'allclose_{dwa_a.name}_{dwa_b.name}',
154
+ data=[np.atleast_1d(np.allclose(Q_(dwa_a[ind], dwa_a.units),
155
+ Q_(dwa_b[ind], dwa_b.units),
156
+ *args, **kwargs)) for ind in range(len(dwa_a))])
157
+
158
+ return dwa
File without changes
File without changes