pymodaq_data 5.2.12__tar.gz → 5.3.0__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 (33) hide show
  1. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/PKG-INFO +12 -12
  2. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/README.rst +11 -11
  3. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/data.py +100 -45
  4. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/backends.py +96 -41
  5. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/data_saving.py +87 -19
  6. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/saving.py +28 -9
  7. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +14 -3
  8. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/plotting/utils.py +2 -1
  9. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/resources/config_template.toml +1 -0
  10. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/.gitignore +0 -0
  11. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/LICENSE +0 -0
  12. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/pyproject.toml +0 -0
  13. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/__init__.py +0 -0
  14. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/config.py +0 -0
  15. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/__init__.py +0 -0
  16. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/browsing.py +0 -0
  17. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/exporter.py +0 -0
  18. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/exporters/__init__.py +0 -0
  19. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/exporters/base.py +0 -0
  20. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/exporters/flimj.py +0 -0
  21. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/exporters/hyperspy.py +0 -0
  22. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/swmr.py +0 -0
  23. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/h5modules/utils.py +0 -0
  24. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/icon.ico +0 -0
  25. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/numpy_func.py +0 -0
  26. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/plotting/__init__.py +0 -0
  27. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/plotting/plotter/plotter.py +0 -0
  28. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
  29. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/post_treatment/__init__.py +0 -0
  30. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/post_treatment/process_to_scalar.py +0 -0
  31. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/resources/__init__.py +0 -0
  32. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/slicing.py +0 -0
  33. {pymodaq_data-5.2.12 → pymodaq_data-5.3.0}/src/pymodaq_data/splash.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pymodaq_data
3
- Version: 5.2.12
3
+ Version: 5.3.0
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
@@ -71,11 +71,11 @@ PyMoDAQ Data
71
71
  :target: https://pypi.org/project/pymodaq_data/
72
72
  :alt: Latest Version
73
73
 
74
- .. image:: https://readthedocs.org/projects/pymodaq/badge/?version=dev
75
- :target: https://pymodaq.readthedocs.io/en/stable/?badge=dev
74
+ .. image:: https://readthedocs.org/projects/pymodaq/badge/?version=patch-3
75
+ :target: https://pymodaq.readthedocs.io/en/stable/?badge=patch-3
76
76
  :alt: Documentation Status
77
77
 
78
- .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/dev/graph/badge.svg?token=IQNJRCQDM2
78
+ .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/patch-3/graph/badge.svg?token=IQNJRCQDM2
79
79
  :target: https://codecov.io/gh/PyMoDAQ/PyMoDAQ
80
80
 
81
81
  +-------------+-------------+---------------+
@@ -94,28 +94,28 @@ PyMoDAQ Data
94
94
 
95
95
 
96
96
 
97
- .. |310-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.10.svg
97
+ .. |310-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.10.svg
98
98
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
99
99
 
100
- .. |311-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.11.svg
100
+ .. |311-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.11.svg
101
101
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
102
102
 
103
- .. |312-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.12.svg
103
+ .. |312-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.12.svg
104
104
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
105
105
 
106
- .. |313-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.13.svg
106
+ .. |313-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.13.svg
107
107
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
108
108
 
109
- .. |310-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.10.svg
109
+ .. |310-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.10.svg
110
110
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
111
111
 
112
- .. |311-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.11.svg
112
+ .. |311-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.11.svg
113
113
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
114
114
 
115
- .. |312-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.12.svg
115
+ .. |312-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.12.svg
116
116
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
117
117
 
118
- .. |313-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.13.svg
118
+ .. |313-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.13.svg
119
119
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
120
120
 
121
121
 
@@ -11,11 +11,11 @@ PyMoDAQ Data
11
11
  :target: https://pypi.org/project/pymodaq_data/
12
12
  :alt: Latest Version
13
13
 
14
- .. image:: https://readthedocs.org/projects/pymodaq/badge/?version=dev
15
- :target: https://pymodaq.readthedocs.io/en/stable/?badge=dev
14
+ .. image:: https://readthedocs.org/projects/pymodaq/badge/?version=patch-3
15
+ :target: https://pymodaq.readthedocs.io/en/stable/?badge=patch-3
16
16
  :alt: Documentation Status
17
17
 
18
- .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/dev/graph/badge.svg?token=IQNJRCQDM2
18
+ .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/patch-3/graph/badge.svg?token=IQNJRCQDM2
19
19
  :target: https://codecov.io/gh/PyMoDAQ/PyMoDAQ
20
20
 
21
21
  +-------------+-------------+---------------+
@@ -34,28 +34,28 @@ PyMoDAQ Data
34
34
 
35
35
 
36
36
 
37
- .. |310-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.10.svg
37
+ .. |310-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.10.svg
38
38
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
39
39
 
40
- .. |311-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.11.svg
40
+ .. |311-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.11.svg
41
41
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
42
42
 
43
- .. |312-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.12.svg
43
+ .. |312-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.12.svg
44
44
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
45
45
 
46
- .. |313-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Linux_3.13.svg
46
+ .. |313-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Linux_3.13.svg
47
47
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
48
48
 
49
- .. |310-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.10.svg
49
+ .. |310-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.10.svg
50
50
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
51
51
 
52
- .. |311-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.11.svg
52
+ .. |311-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.11.svg
53
53
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
54
54
 
55
- .. |312-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.12.svg
55
+ .. |312-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.12.svg
56
56
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
57
57
 
58
- .. |313-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/dev/tests_Windows_3.13.svg
58
+ .. |313-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_data/patch-3/tests_Windows_3.13.svg
59
59
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
60
60
 
61
61
 
@@ -86,7 +86,9 @@ def dimensionless_aware_reduce_units(q: Type[Q_]) -> Type[Q_]:
86
86
  -------
87
87
  The reduced quantity
88
88
  """
89
-
89
+ # this may fire a pint.errors.UndefinedBehavior warning when the magnitude is a numpy array
90
+ # but pint https://github.com/hgrecco/pint/issues/2274 fixed this in July 2026
91
+ # at the moment, it just returns the same quantity layout
90
92
  return q.to_compact() if q.dimensionless else q.to_reduced_units()
91
93
 
92
94
  def check_units(units: str):
@@ -529,6 +531,16 @@ class Axis(SerializableBase):
529
531
  if self._data is None:
530
532
  self._size = _size
531
533
 
534
+ @property
535
+ def axis_width(self) -> Q_:
536
+ """get the width of the axis in axis unit
537
+
538
+ That is the difference between the max value and the min value of the axis
539
+ """
540
+ if self.scaling is not None:
541
+ return Q_(self.size * self.scaling, self.units)
542
+ else:
543
+ return Q_(self.get_data().max() - self.get_data().min(), self.units)
532
544
  @staticmethod
533
545
  def _check_index_valid(index: int):
534
546
  if not isinstance(index, int):
@@ -653,10 +665,10 @@ class Axis(SerializableBase):
653
665
  """find the index of the threshold value within the axis"""
654
666
  if isinstance(threshold, Q_):
655
667
  threshold = threshold.m_as(self.units)
656
- if threshold < self.min():
668
+ if threshold <= self.min():
657
669
  return 0
658
- elif threshold > self.max():
659
- return len(self) - 1
670
+ elif threshold >= self.max():
671
+ return len(self)
660
672
  elif self._data is not None:
661
673
  return mutils.find_index(self._data, threshold)[0][0]
662
674
  else:
@@ -702,7 +714,7 @@ class DataLowLevel:
702
714
 
703
715
  @name.setter
704
716
  def name(self, other_name: str):
705
- self._name = other_name
717
+ self._name = str(other_name)
706
718
 
707
719
  @property
708
720
  def timestamp(self):
@@ -2141,6 +2153,10 @@ class DataWithAxes(DataBase, SerializableBase):
2141
2153
  dwa.timestamp = timestamp
2142
2154
  return dwa, remaining_bytes
2143
2155
 
2156
+ def get_axes_sizes(self) -> Iterable[Q_]:
2157
+ self.create_missing_axes()
2158
+ return [self.get_axis_from_index(index)[0].axis_width for index in range(len(self.axes))]
2159
+
2144
2160
  def check_axes_linear(self, axes: List[Axis] = None) -> bool:
2145
2161
  """ Check if any axis may be non linear
2146
2162
 
@@ -3390,19 +3406,67 @@ class DataToExport(DataLowLevel, SerializableBase):
3390
3406
  if dat.origin is None or dat.origin == '':
3391
3407
  dat.origin = self.name
3392
3408
 
3409
+ def _combine(self, other: object, op: Callable, reflected: bool = False) -> 'DataToExport':
3410
+ """Combine self and other elementwise, applying ``op`` to every element.
3411
+
3412
+ *other* may be:
3413
+
3414
+ - a plain number: broadcast to every element. Whether this is
3415
+ actually valid is decided by DataWithAxes/pint arithmetic itself,
3416
+ not pre-judged here — e.g. adding a bare number only makes sense
3417
+ for dimensionless data (pint raises otherwise), while multiplying
3418
+ by one is always valid regardless of units.
3419
+ - another DataToExport of the same length: paired by list position
3420
+ (index i of self with index i of other), not by name.
3421
+ DataToExport is used to combine structurally-parallel containers
3422
+ (e.g. successive snapshots from the same DAQ_Scan/ModulesManager
3423
+ run, always iterated in the same order), and existing callers rely
3424
+ on being able to combine elements whose *names* legitimately
3425
+ differ between the two operands (e.g. a signal container and a
3426
+ differently-named background container). Only the count and,
3427
+ per-pair, the array shapes/units need to match — the latter
3428
+ enforced by DataWithAxes arithmetic itself.
3429
+
3430
+ Parameters
3431
+ ----------
3432
+ other: DataToExport or numbers.Number
3433
+ op: Callable
3434
+ Binary operation applied to each pair, e.g. ``np.subtract``. Its
3435
+ ``__name__`` is used in error messages.
3436
+ reflected: bool
3437
+ If True, apply ``op(other_i, self_i)`` instead of
3438
+ ``op(self_i, other_i)``, for the non-commutative ``__r*__`` methods.
3439
+ """
3440
+ if isinstance(other, numbers.Number):
3441
+ others = [other] * len(self)
3442
+ elif not isinstance(other, DataToExport):
3443
+ raise TypeError(f'Could not {op.__name__} a {self.__class__.__name__} and a '
3444
+ f'{other.__class__.__name__}: only a number or another '
3445
+ f'{self.__class__.__name__} is supported')
3446
+ elif len(other) != len(self):
3447
+ raise TypeError(f'Could not {op.__name__} a {other.__class__.__name__} with a '
3448
+ f'{self.__class__.__name__} of a different length')
3449
+ else:
3450
+ others = other
3451
+ new_data = copy.deepcopy(self)
3452
+ for ind_dfp, (dwa, other_dwa) in enumerate(zip(self, others)):
3453
+ new_data[ind_dfp] = op(other_dwa, dwa) if reflected else op(dwa, other_dwa)
3454
+ return new_data
3455
+
3393
3456
  def __sub__(self, other: object):
3394
- if isinstance(other, DataToExport) and len(other) == len(self):
3395
- new_data = copy.deepcopy(self)
3396
- for ind_dfp in range(len(self)):
3397
- new_data[ind_dfp] = self[ind_dfp] - other[ind_dfp]
3398
- return new_data
3399
- else:
3400
- raise TypeError(f'Could not substract a {other.__class__.__name__} or a {self.__class__.__name__} '
3401
- f'of a different length')
3457
+ return self._combine(other, np.subtract)
3458
+
3459
+ def __rsub__(self, other: object):
3460
+ return self._combine(other, np.subtract, reflected=True)
3402
3461
 
3403
3462
  def __eq__(self, other):
3404
3463
  if not isinstance(other, DataToExport):
3405
3464
  return False
3465
+ if len(self) != len(other):
3466
+ # zip() below would otherwise silently truncate to the shorter of
3467
+ # the two, letting a DataToExport compare equal to a strict
3468
+ # superset of itself.
3469
+ return False
3406
3470
 
3407
3471
  for dwa, other_dwa in zip(self, other):
3408
3472
  if dwa != other_dwa:
@@ -3417,31 +3481,20 @@ class DataToExport(DataLowLevel, SerializableBase):
3417
3481
 
3418
3482
 
3419
3483
  def __add__(self, other: object):
3420
- if isinstance(other, DataToExport) and len(other) == len(self):
3421
- new_data = copy.deepcopy(self)
3422
- for ind_dfp in range(len(self)):
3423
- new_data[ind_dfp] = self[ind_dfp] + other[ind_dfp]
3424
- return new_data
3425
- else:
3426
- raise TypeError(f'Could not add a {other.__class__.__name__} or a {self.__class__.__name__} '
3427
- f'of a different length')
3484
+ return self._combine(other, np.add)
3485
+
3486
+ __radd__ = __add__ # addition is commutative
3428
3487
 
3429
3488
  def __mul__(self, other: object):
3430
- if isinstance(other, numbers.Number):
3431
- new_data = copy.deepcopy(self)
3432
- for ind_dfp in range(len(self)):
3433
- new_data[ind_dfp] = self[ind_dfp] * other
3434
- return new_data
3435
- else:
3436
- raise TypeError(f'Could not multiply a {other.__class__.__name__} with a {self.__class__.__name__} '
3437
- f'of a different length')
3489
+ return self._combine(other, np.multiply)
3490
+
3491
+ __rmul__ = __mul__ # multiplication is commutative
3438
3492
 
3439
3493
  def __truediv__(self, other: object):
3440
- if isinstance(other, numbers.Number):
3441
- return self * (1 / other)
3442
- else:
3443
- raise TypeError(f'Could not divide a {other.__class__.__name__} with a {self.__class__.__name__} '
3444
- f'of a different length')
3494
+ return self._combine(other, np.true_divide)
3495
+
3496
+ def __rtruediv__(self, other: object):
3497
+ return self._combine(other, np.true_divide, reflected=True)
3445
3498
 
3446
3499
  def average(self, other: DataToExport, weight: int) -> DataToExport:
3447
3500
  """ Compute the weighted average between self and other DataToExport and attributes it to self
@@ -3453,14 +3506,10 @@ class DataToExport(DataLowLevel, SerializableBase):
3453
3506
  The weight the 'other_data' holds with respect to self
3454
3507
 
3455
3508
  """
3456
- if isinstance(other, DataToExport) and len(other) == len(self):
3457
- new_data = copy.copy(self)
3458
- for ind_dfp in range(len(self)):
3459
- new_data[ind_dfp] = self[ind_dfp].average(other[ind_dfp], weight)
3460
- return new_data
3461
- else:
3462
- raise TypeError(f'Could not average a {other.__class__.__name__} with a {self.__class__.__name__} '
3463
- f'of a different length')
3509
+ def average(dwa: DataWithAxes, other_dwa: DataWithAxes) -> DataWithAxes:
3510
+ return dwa.average(other_dwa, weight)
3511
+
3512
+ return self._combine(other, average)
3464
3513
 
3465
3514
  def merge_as_dwa(self, dim: Union[str, DataDim], name: str = None) -> DataRaw:
3466
3515
  """ attempt to merge filtered dwa into one
@@ -3747,9 +3796,16 @@ class DataToExport(DataLowLevel, SerializableBase):
3747
3796
  data, _ = find_objects_in_list_from_attr_name_val(self.data, 'name', name, return_first=True)
3748
3797
  return data
3749
3798
 
3750
- def get_data_from_names(self, names: List[str]) -> DataToExport:
3799
+ def get_data_from_names(self, names: List[str] | str) -> DataToExport:
3800
+ if isinstance(names, str):
3801
+ names = [names]
3751
3802
  return DataToExport(self.name, data=[dwa for dwa in self if dwa.name in names])
3752
3803
 
3804
+ def get_data_from_origins(self, origins: list[str] | str) -> DataToExport:
3805
+ if isinstance(origins, str):
3806
+ origins = [origins]
3807
+ return DataToExport(self.name, data=[dwa for dwa in self if dwa.origin in origins])
3808
+
3753
3809
  def get_data_from_name_origin(self, name: str, origin: str = '') -> DataWithAxes:
3754
3810
  """Get the data matching the given name and the given origin"""
3755
3811
  if origin == '':
@@ -3771,9 +3827,8 @@ class DataToExport(DataLowLevel, SerializableBase):
3771
3827
  return ind
3772
3828
  raise ValueError
3773
3829
 
3774
- def index_from_name_origin(self, name: str, origin: str = '') -> List[DataWithAxes]:
3830
+ def index_from_name_origin(self, name: str, origin: str = '') -> int:
3775
3831
  """Get the index of a given DataWithAxes within the list of data"""
3776
- """Get the data matching the given name and the given origin"""
3777
3832
  if origin == '':
3778
3833
  _, index = find_objects_in_list_from_attr_name_val(self.data, 'name', name, return_first=True)
3779
3834
  else:
@@ -242,15 +242,13 @@ class Node(object):
242
242
  return self.attrs['TITLE']
243
243
 
244
244
  @property
245
- def path(self):
245
+ def path(self) -> str:
246
246
  """return node path
247
- Parameters
248
- ----------
249
- node (str or node instance), see h5py and pytables documentation on nodes
250
247
 
251
248
  Returns
252
249
  -------
253
- str : full path of the node
250
+ str
251
+ full path of the node
254
252
  """
255
253
  if self.backend == 'tables':
256
254
  return self._node._v_pathname
@@ -288,7 +286,8 @@ class GROUP(Node):
288
286
 
289
287
  Returns
290
288
  -------
291
- dict: keys are children node names, values are the children nodes
289
+ dict
290
+ keys are children node names, values are the children nodes
292
291
 
293
292
  See Also
294
293
  --------
@@ -387,8 +386,8 @@ class EARRAY(CARRAY):
387
386
  The data to append could be:
388
387
 
389
388
  * a single element (without the enlargeable shape index that is always the first
390
- index, that is of shape length n). In that case the first index of the enlargeable array
391
- is increased by one.
389
+ index, that is of shape length n). In that case the first index of the enlargeable array
390
+ is increased by one.
392
391
  * an ensemble of elements (a ndarray) of shape length of (n+1).
393
392
 
394
393
  Parameters
@@ -575,6 +574,11 @@ class H5Backend:
575
574
  self._swmr_enabled = False
576
575
  self.set_backend(backend)
577
576
 
577
+ @property
578
+ def swmr_mode(self) -> bool:
579
+ return self._swmr_mode
580
+
581
+
578
582
  def set_backend(self, backend: str):
579
583
  """Switch the active backend, closing any open file first.
580
584
 
@@ -658,7 +662,7 @@ class H5Backend:
658
662
  return self._h5file
659
663
  else:
660
664
  if swmr_mode and self.backend == 'h5py':
661
- if mode == 'w':
665
+ if mode in ('w', 'a'):
662
666
  kwargs['libver'] = 'latest'
663
667
  elif mode == 'r':
664
668
  kwargs['swmr'] = True
@@ -804,22 +808,24 @@ class H5Backend:
804
808
 
805
809
  def get_set_group(self, where, name, title='', **kwargs):
806
810
  """Retrieve or create (if absent) a node group
811
+
807
812
  Get attributed to the class attribute ``current_group``
808
813
 
809
814
  Parameters
810
815
  ----------
811
- where: str or node
812
- path or parent node instance
816
+ where: str or Node
817
+ path or parent node instance
813
818
  name: str
814
- group node name
819
+ group node name
815
820
  title: str
816
- node title
817
-
818
- Keyword Arguments:
821
+ node title
822
+ kwargs: dict
819
823
  any other metadata related to this node (for example: origin)
824
+
820
825
  Returns
821
826
  -------
822
- group: group node
827
+ GROUP
828
+ the group node
823
829
  """
824
830
 
825
831
  if isinstance(where, Node):
@@ -858,12 +864,13 @@ class H5Backend:
858
864
  def is_node_in_group(self, where, name):
859
865
  """
860
866
  Check if a given node with name is in the group defined by where (comparison on lower case strings)
867
+
861
868
  Parameters
862
869
  ----------
863
- where: (str or node)
864
- path or parent node instance
865
- name: (str)
866
- group node name
870
+ where: str or Node
871
+ path or parent node instance
872
+ name: str
873
+ group node name
867
874
 
868
875
  Returns
869
876
  -------
@@ -940,13 +947,16 @@ class H5Backend:
940
947
 
941
948
  def get_node_name(self, node):
942
949
  """return node name
950
+
943
951
  Parameters
944
952
  ----------
945
- node (str or node instance), see h5py and pytables documentation on nodes
953
+ node: str or Node
954
+ see h5py and pytables documentation on nodes
946
955
 
947
956
  Returns
948
957
  -------
949
- str: name of the node
958
+ str
959
+ name of the node
950
960
  """
951
961
  if isinstance(node, Node):
952
962
  node = node.node
@@ -954,13 +964,16 @@ class H5Backend:
954
964
 
955
965
  def get_node_path(self, node):
956
966
  """return node path
967
+
957
968
  Parameters
958
969
  ----------
959
- node (str or node instance), see h5py and pytables documentation on nodes
970
+ node: str or Node
971
+ see h5py and pytables documentation on nodes
960
972
 
961
973
  Returns
962
974
  -------
963
- str : full path of the node
975
+ str
976
+ full path of the node
964
977
  """
965
978
  if isinstance(node, Node):
966
979
  node = node.node
@@ -977,18 +990,19 @@ class H5Backend:
977
990
  else:
978
991
  return self.get_node(node.parent)
979
992
 
980
- def get_children(self, where):
993
+ def get_children(self, where) -> dict[str, Node]:
981
994
  """Get a dict containing all children node hanging from where with their name as keys and types among Node,
982
995
  CARRAY, EARRAY, VLARRAY or StringARRAY
983
996
 
984
997
  Parameters
985
998
  ----------
986
- where (str or node instance)
999
+ where: str or Node
987
1000
  see h5py and pytables documentation on nodes, and Node objects of this module
988
1001
 
989
1002
  Returns
990
1003
  -------
991
- dict: keys are children node names, values are the children nodes
1004
+ dict
1005
+ keys are children node names, values are the children nodes
992
1006
 
993
1007
  See Also
994
1008
  --------
@@ -1023,12 +1037,45 @@ class H5Backend:
1023
1037
  children[child_name] = _cls(child, self.backend)
1024
1038
  return children
1025
1039
 
1026
- def walk_nodes(self, where):
1040
+ def walk_nodes(self, where: str| Node, depth: int = None, only_groups=False):
1041
+ """ Node Generator recursively iterating in the tree starting from where down to the specified depth
1042
+ (counted from where).
1043
+
1044
+
1045
+ Parameters
1046
+ ----------
1047
+ where: str | Node
1048
+ The starting node of the iteration
1049
+ depth: int
1050
+ The depth of the iteration in the h5 tree
1051
+ only_groups: bool
1052
+ if False (default) return all nodes otherwise retruns only GROUP instances
1053
+
1054
+ Yields
1055
+ ------
1056
+ Node
1057
+ """
1027
1058
  where = self.get_node(where) # return a node object in case where is a string
1028
- yield where
1029
- for gr in self.walk_groups(where):
1030
- for child in self.get_children(gr).values():
1031
- yield child
1059
+ if only_groups and where.attrs['CLASS'] != 'GROUP':
1060
+ pass
1061
+ else:
1062
+ yield where
1063
+
1064
+ if depth == 0:
1065
+ return
1066
+ else:
1067
+ for gr in self.walk_groups(where):
1068
+ for child in self.get_children(gr).values():
1069
+ node_path = child.path[len(where.path):]
1070
+ if node_path[0] == '/':
1071
+ node_path = node_path[1:]
1072
+ if depth is not None and node_path.count('/') == depth:
1073
+ return
1074
+ else:
1075
+ if only_groups and child.attrs['CLASS'] != 'GROUP':
1076
+ pass
1077
+ else:
1078
+ yield child
1032
1079
 
1033
1080
  def walk_groups(self, where):
1034
1081
  where = self.get_node(where) # return a node object in case where is a string
@@ -1126,14 +1173,18 @@ class H5Backend:
1126
1173
 
1127
1174
  Parameters
1128
1175
  ----------
1129
- where: (str) group location in the file where to create the array node
1130
- name: (str) name of the array
1131
- dtype: (dtype) numpy dtype style, for particular case of strings, use dtype='string'
1132
- title: (str) node title attribute (written in capitals)
1176
+ where: str
1177
+ group location in the file where to create the array node
1178
+ name: str
1179
+ name of the array
1180
+ dtype: dtype
1181
+ numpy dtype style, for particular case of strings, use dtype='string'
1182
+ title: str
1183
+ node title attribute (written in capitals)
1133
1184
 
1134
1185
  Returns
1135
1186
  -------
1136
- array
1187
+ VLARRAY
1137
1188
 
1138
1189
  """
1139
1190
  if isinstance(where, Node):
@@ -1189,15 +1240,19 @@ class H5Backend:
1189
1240
  Add a node in the h5 file tree of the group type
1190
1241
  Parameters
1191
1242
  ----------
1192
- group_name: (str) a custom name for this group
1243
+ group_name: str
1244
+ a custom name for this group
1193
1245
  group_type: str or GroupType enum
1194
1246
  one of the possible values of GroupType, should be enforced by higher level modules not here
1195
- where: (str or node) parent node where to create the new group
1196
- metadata: (dict) extra metadata to be saved with this new group node
1247
+ where: str or Node
1248
+ parent node where to create the new group
1249
+ metadata: dict
1250
+ extra metadata to be saved with this new group node
1197
1251
 
1198
1252
  Returns
1199
1253
  -------
1200
- (node): newly created group node
1254
+ GROUP
1255
+ newly created group node
1201
1256
  """
1202
1257
  if metadata is None:
1203
1258
  metadata = {}
@@ -4,6 +4,10 @@ Created the 21/11/2022
4
4
 
5
5
  @author: Sebastien Weber
6
6
  """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
7
11
  from time import time
8
12
  from typing import Union, List, Tuple, Iterable
9
13
  from pathlib import Path
@@ -18,7 +22,7 @@ from .saving import DataType, H5SaverLowLevel
18
22
  from .backends import GROUP, CARRAY, Node, EARRAY, NodeError, GroupType # noqa: F401
19
23
  from pymodaq_utils.utils import capitalize
20
24
  from pymodaq_data.h5modules.saving import SaveType
21
-
25
+ from .. import DataToExport, DataDistribution
22
26
 
23
27
  SPECIAL_GROUP_NAMES = dict(nav_axes='NavAxes')
24
28
 
@@ -27,6 +31,21 @@ class AxisError(Exception):
27
31
  pass
28
32
 
29
33
 
34
+ @dataclass
35
+ class DataBundle:
36
+ """Convenience class to hold data to be saved or plotted"""
37
+ dte: DataToExport
38
+ node: str = None
39
+ settings: str = ''
40
+ indexes: list[int] = None # indexes within an eventual Extended array (see DAQ_Scan)
41
+ axis_values: list[float | np.ndarray] = None # axis values within an eventual Enlargeable array (see Optimizers)
42
+ distribution: DataDistribution = field(
43
+ default_factory=lambda: DataDistribution.uniform
44
+ ) # type of data to be saved
45
+ save_index: int = 0 # an index to know what step in the saving process we're in (see DAQ_Scan)
46
+
47
+
48
+
30
49
  class DataManagement(metaclass=ABCMeta):
31
50
  """Base abstract class to be used for all specialized object saving and loading data to/from a h5file
32
51
 
@@ -297,12 +316,17 @@ class DataSaverLoader(DataManagement):
297
316
  data_type = DataType['data']
298
317
 
299
318
  def __init__(self, h5saver: Union[H5SaverLowLevel, Path],
300
- new_file: bool = False, metadata: dict = None, save_type=SaveType.custom):
319
+ new_file: bool = False,
320
+ metadata: dict = None,
321
+ save_type=SaveType.custom,
322
+ swmr_mode=False):
301
323
  self.data_type = enum_checker(DataType, self.data_type)
302
324
 
303
325
  if isinstance(h5saver, (Path, str)):
304
326
  h5saver = H5SaverLowLevel.from_file(h5saver, save_type,
305
- new_file=new_file, metadata=metadata)
327
+ new_file=new_file,
328
+ metadata=metadata,
329
+ swmr_mode=swmr_mode)
306
330
 
307
331
  self._h5saver = h5saver
308
332
  self._axis_saver = AxisSaverLoader(self._h5saver)
@@ -708,7 +732,7 @@ class DataExtendedSaver(DataSaverLoader):
708
732
  Parameters
709
733
  ----------
710
734
  h5saver: H5SaverLowLevel
711
- extended_shape: Tuple[int]
735
+ extended_shape: Iterable[int]
712
736
  the extra shape compared to the data the h5array will have
713
737
 
714
738
  Attributes
@@ -718,7 +742,9 @@ class DataExtendedSaver(DataSaverLoader):
718
742
  """
719
743
  data_type = DataType['data']
720
744
 
721
- def __init__(self, h5saver: H5SaverLowLevel, extended_shape: Tuple[int], fill_value=None):
745
+ def __init__(self, h5saver: H5SaverLowLevel,
746
+ extended_shape: Iterable[int],
747
+ fill_value=None):
722
748
  super().__init__(h5saver)
723
749
  self.extended_shape = extended_shape
724
750
  self.fill_value = fill_value
@@ -784,7 +810,9 @@ class DataExtendedSaver(DataSaverLoader):
784
810
  # because there will be len(self.extended_shape) extra navigation axes
785
811
  self._axis_saver.add_axis(where, axis)
786
812
 
787
- def add_data(self, where: Union[Node, str], data: DataWithAxes, indexes: List[int],
813
+ def add_data(self, where: Union[Node, str],
814
+ data: DataWithAxes,
815
+ indexes: Iterable[int],
788
816
  distribution=DataDistribution['uniform']):
789
817
  """Adds given DataWithAxes at a location within the initialized h5 array
790
818
 
@@ -835,6 +863,14 @@ class DataToExportSaver:
835
863
  self._data_saver = DataSaverLoader(self._h5saver)
836
864
  self._bkg_saver = BkgSaver(self._h5saver)
837
865
 
866
+ @property
867
+ def h5saver(self) -> H5SaverLowLevel:
868
+ return self._h5saver
869
+
870
+ @h5saver.setter
871
+ def h5saver(self, h5saver: H5SaverLowLevel):
872
+ self._h5saver = h5saver
873
+
838
874
  def __getattr__(self, item):
839
875
  """ Allows to call attributes of the underlying H5Saver object"""
840
876
 
@@ -868,6 +904,9 @@ class DataToExportSaver:
868
904
  and formatted as below"""
869
905
  return f'CH{ind:02d}'
870
906
 
907
+ def add_data_bundle(self, data: DataBundle):
908
+ self.add_data(data.node, data.dte, data.settings)
909
+
871
910
  def add_data(self, where: Union[Node, str], data: DataToExport, settings_as_xml='', **kwargs):
872
911
  """
873
912
 
@@ -959,6 +998,9 @@ class DataToExportEnlargeableSaver(DataToExportSaver):
959
998
  self._data_saver = DataEnlargeableSaver(self._h5saver)
960
999
  self._nav_axis_saver = AxisSaverLoader(self._h5saver)
961
1000
 
1001
+ def add_data_bundle(self, data: DataBundle):
1002
+ self.add_data(data.node, data.dte, axis_values=data.axis_values, settings_as_xml=data.settings)
1003
+
962
1004
  def add_data(self, where: Union[Node, str], data: DataToExport,
963
1005
  axis_values: List[Union[float, np.ndarray]] = None,
964
1006
  axis_value: Union[float, np.ndarray] = None,
@@ -1022,6 +1064,9 @@ class DataToExportTimedSaver(DataToExportEnlargeableSaver):
1022
1064
  def add_data(self, where: Union[Node, str], data: DataToExport, settings_as_xml='', **kwargs):
1023
1065
  super().add_data(where, data, axis_values=[data.timestamp], settings_as_xml=settings_as_xml, **kwargs)
1024
1066
 
1067
+ def add_data_bundle(self, data: DataBundle):
1068
+ self.add_data(data.node, data.dte, settings_as_xml=data.settings)
1069
+
1025
1070
 
1026
1071
  class DataToExportExtendedSaver(DataToExportSaver):
1027
1072
  """Object to save DataToExport at given indexes within arrays including extended shape
@@ -1035,7 +1080,10 @@ class DataToExportExtendedSaver(DataToExportSaver):
1035
1080
  the extra shape compared to the data the h5array will have
1036
1081
  """
1037
1082
 
1038
- def __init__(self, h5saver: H5SaverLowLevel, extended_shape: Tuple[int], fill_value=None):
1083
+ def __init__(self,
1084
+ h5saver: H5SaverLowLevel,
1085
+ extended_shape: Iterable[int],
1086
+ fill_value=None):
1039
1087
  super().__init__(h5saver)
1040
1088
  self._data_saver = DataExtendedSaver(h5saver, extended_shape, fill_value=fill_value)
1041
1089
  self._nav_axis_saver = AxisSaverLoader(h5saver)
@@ -1054,7 +1102,12 @@ class DataToExportExtendedSaver(DataToExportSaver):
1054
1102
  for axis in axes:
1055
1103
  self._nav_axis_saver.add_axis(nav_group, axis)
1056
1104
 
1057
- def add_data(self, where: Union[Node, str], data: DataToExport, indexes: Iterable[int],
1105
+ def add_data_bundle(self, data: DataBundle):
1106
+ self.add_data(data.node, data.dte, data.indexes, data.distribution, data.settings)
1107
+
1108
+ def add_data(self, where: Union[Node, str],
1109
+ data: DataToExport,
1110
+ indexes: Iterable[int],
1058
1111
  distribution=DataDistribution.uniform,
1059
1112
  settings_as_xml='', **kwargs):
1060
1113
 
@@ -1095,7 +1148,7 @@ class DataToExportExtendedSaver(DataToExportSaver):
1095
1148
 
1096
1149
 
1097
1150
  class DataLoader:
1098
- """Specialized Object to load DataWithAxes object from a h5file
1151
+ """Specialized Object to load DataWithAxes / DataToExport objects from a h5file
1099
1152
 
1100
1153
  On the contrary to DataSaverLoader, does include navigation axes stored elsewhere in the h5file
1101
1154
  (for instance if saved from the DAQ_Scan)
@@ -1105,12 +1158,15 @@ class DataLoader:
1105
1158
  h5saver: H5SaverLowLevel or Path
1106
1159
  """
1107
1160
 
1108
- def __init__(self, h5saver: Union[H5SaverLowLevel, Path]):
1161
+ def __init__(self, h5saver: Union[H5SaverLowLevel, Path],
1162
+ swmr_mode = False):
1109
1163
  self._axis_loader: AxisSaverLoader = None
1110
1164
  self._data_loader: DataSaverLoader = None
1111
1165
 
1112
1166
  if isinstance(h5saver, (Path, str)):
1113
- h5saver = H5SaverLowLevel.from_file(h5saver)
1167
+ h5saver = H5SaverLowLevel.from_file(h5saver,
1168
+ swmr_mode=swmr_mode,
1169
+ mode='r')
1114
1170
 
1115
1171
  self.h5saver = h5saver
1116
1172
 
@@ -1129,8 +1185,8 @@ class DataLoader:
1129
1185
 
1130
1186
  def __exit__(self, exc_type, exc_val, exc_tb):
1131
1187
  self.close_file()
1132
- @property
1133
1188
 
1189
+ @property
1134
1190
  def raw_group(self) -> Node:
1135
1191
  """ Get the base RawGroup where raw data should be saved
1136
1192
 
@@ -1141,9 +1197,11 @@ class DataLoader:
1141
1197
  def close_file(self):
1142
1198
  self._h5saver.close_file()
1143
1199
 
1144
- def walk_nodes(self, where: Union[str, Node] = '/'):
1200
+ def walk_nodes(self, where: Union[str, Node] = '/', depth: int = None,
1201
+ only_groups=False) -> Iterable[Node]:
1145
1202
  """Return a Node generator iterating over the h5file content"""
1146
- return self.h5saver.walk_nodes(where)
1203
+ return self.h5saver.walk_nodes(where, depth=depth,
1204
+ only_groups=only_groups)
1147
1205
 
1148
1206
  def get_node(self, where: Union[Node, str], name: str = None) -> Node:
1149
1207
  """ Convenience method to get node"""
@@ -1159,8 +1217,8 @@ class DataLoader:
1159
1217
 
1160
1218
  Returns
1161
1219
  -------
1162
- GROUP: returns the group named SPECIAL_GROUP_NAMES['nav_axes'] holding all NavAxis for
1163
- those data
1220
+ GROUP
1221
+ the group named ``SPECIAL_GROUP_NAMES['nav_axes']`` holding all NavAxis for those data
1164
1222
 
1165
1223
  See Also
1166
1224
  --------
@@ -1198,17 +1256,25 @@ class DataLoader:
1198
1256
  if nav_group is not None:
1199
1257
  nav_axes = self._axis_loader.get_axes(nav_group)
1200
1258
  axes = data.axes[:]
1259
+ # in swmr mode, if at the time where the axis is read, a writer added stuff
1260
+ # then the axis and the data may not be consistent. Below is an attempt to correct this
1261
+ for ind_nav, nav_axe in enumerate(nav_axes):
1262
+ if nav_axe.size > data.shape[data.nav_indexes[nav_axe.index]]:
1263
+ nav_axe.data = nav_axe.get_data()[:data.shape[data.nav_indexes[nav_axe.index]]]
1201
1264
  axes.extend(nav_axes)
1202
1265
  data.axes = axes
1203
1266
  data.get_dim_from_data_axes()
1204
1267
  data.create_missing_axes()
1205
1268
  return data
1206
1269
 
1207
- def load_all(self, where: GROUP, data: DataToExport = None, with_bkg=False) -> DataToExport:
1270
+ def load_all(self, where: GROUP | str, data: DataToExport = None, with_bkg=False) -> DataToExport:
1208
1271
  if data is None:
1209
1272
  data = DataToExport('Loaded data')
1210
1273
  where = self._h5saver.get_node(where)
1211
- children_dict = where.children()
1274
+ try:
1275
+ children_dict = where.children()
1276
+ except AttributeError:
1277
+ children_dict = {where.name: where}
1212
1278
  data_list = []
1213
1279
  for child in children_dict:
1214
1280
  if isinstance(children_dict[child], GROUP):
@@ -1263,4 +1329,6 @@ class DataLoader:
1263
1329
  # if not returned so far raise a NameError
1264
1330
  raise NameError(f'No dwa matching this name: {name} and '
1265
1331
  f'origin: {origin} hanging from '
1266
- f'{where}')
1332
+ f'{where}')
1333
+
1334
+
@@ -92,11 +92,14 @@ class H5SaverLowLevel(H5Backend):
92
92
 
93
93
  @classmethod
94
94
  def from_file(cls, path: Union[Path, str], save_type: SaveType = 'scan',
95
- new_file: bool = False, metadata: dict = None) -> 'H5SaverLowLevel':
95
+ new_file: bool = False, metadata: dict = None,
96
+ swmr_mode: bool = False,
97
+ mode='a') -> 'H5SaverLowLevel':
96
98
  """Create and initialise an H5SaverLowLevel from a file path.
97
99
 
98
100
  Convenience factory that combines the constructor and :meth:`init_file`
99
101
  call into a single expression.
102
+
100
103
  Parameters
101
104
  ----------
102
105
  path: Path or str
@@ -108,6 +111,11 @@ class H5SaverLowLevel(H5Backend):
108
111
  opened for appending.
109
112
  metadata: dict or None
110
113
  Extra attributes written to the raw-data group on creation.
114
+ swmr_mode: bool
115
+ If True, prepare the file for SWMR (h5py backend only)
116
+ mode: str
117
+ valid if new_file is False. Then could be 'r' for readonly or 'a' to append data
118
+
111
119
 
112
120
  Returns
113
121
  -------
@@ -115,7 +123,11 @@ class H5SaverLowLevel(H5Backend):
115
123
  A fully initialised instance ready for reading/writing.
116
124
  """
117
125
  h5saver = cls(save_type)
118
- h5saver.init_file(file_name=Path(path), new_file=new_file, metadata=metadata)
126
+ h5saver.init_file(file_name=Path(path),
127
+ new_file=new_file,
128
+ metadata=metadata,
129
+ swmr_mode=swmr_mode,
130
+ mode=mode)
119
131
  return h5saver
120
132
 
121
133
  def set_swmr_flush_interval(self, interval: int):
@@ -149,8 +161,11 @@ class H5SaverLowLevel(H5Backend):
149
161
  def h5_file(self):
150
162
  return self._h5file
151
163
 
152
- def init_file(self, file_name: Path, raw_group_name='RawData', new_file=False,
153
- metadata: dict = None, swmr_mode: bool = False):
164
+ def init_file(self, file_name: Path, raw_group_name='RawData',
165
+ new_file=False,
166
+ metadata: dict = None,
167
+ swmr_mode: bool = False,
168
+ mode: str = 'a'):
154
169
  """Initializes a new h5 file,
155
170
 
156
171
  Should have an extension with h5 in it.
@@ -167,6 +182,8 @@ class H5SaverLowLevel(H5Backend):
167
182
  A dictionary to be saved as attributes
168
183
  swmr_mode: bool
169
184
  If True, prepare the file for SWMR (h5py backend only)
185
+ mode: str
186
+ valid if new_file is False. Then could be 'r' for readonly or 'a' to append data
170
187
 
171
188
  Returns
172
189
  -------
@@ -200,7 +217,7 @@ class H5SaverLowLevel(H5Backend):
200
217
  return
201
218
 
202
219
  self.close_file()
203
- self.open_file(self.h5_file_path.joinpath(self.h5_file_name), 'w' if new_file else 'a',
220
+ self.open_file(self.h5_file_path.joinpath(self.h5_file_name), 'w' if new_file else mode,
204
221
  title='PyMoDAQ file', swmr_mode=swmr_mode)
205
222
 
206
223
  self._raw_group = self.get_set_group(self.root(), raw_group_name, title='Data from PyMoDAQ modules')
@@ -267,6 +284,7 @@ class H5SaverLowLevel(H5Backend):
267
284
  title: str = '', metadata=dict([])):
268
285
 
269
286
  """save data arrays on the hdf5 file together with metadata
287
+
270
288
  Parameters
271
289
  ----------
272
290
  where: GROUP
@@ -278,7 +296,7 @@ class H5SaverLowLevel(H5Backend):
278
296
  data_shape: Iterable
279
297
  the shape of the array to save, mandatory if array_to_save is None
280
298
  data_dimension: DataDim
281
- The data's dimension
299
+ The data's dimension
282
300
  scan_shape: Iterable
283
301
  the shape of the scan dimensions
284
302
  title: str
@@ -294,12 +312,13 @@ class H5SaverLowLevel(H5Backend):
294
312
  if False, data are saved as a CARRAY, otherwise as a EARRAY (for ragged data, see add_string_array)
295
313
  metadata: dict
296
314
  dictionnary whose keys will be saved as the array attributes
297
- add_scan_dim: if True, the scan axes dimension (scan_shape iterable) is prepended to the array shape on the hdf5
298
- In that case, the array is usually initialized as zero and further populated
315
+ add_scan_dim: bool
316
+ if True, the scan axes dimension (scan_shape iterable) is prepended to the array shape on the hdf5
317
+ In that case, the array is usually initialized as zero and further populated
299
318
 
300
319
  Returns
301
320
  -------
302
- array (CARRAY or EARRAY)
321
+ CARRAY or EARRAY
303
322
 
304
323
  See Also
305
324
  --------
@@ -20,7 +20,10 @@ logger = set_logger(get_module_name(__file__))
20
20
  config = configmod.GlobalConfig()
21
21
 
22
22
  PLOT_COLORS = PlotColors()
23
- PLOT_COLORS.remove((255, 255, 255)) # remove white color as plotted on white background
23
+ try:
24
+ PLOT_COLORS.remove((255, 255, 255))# remove white color as plotted on white background
25
+ except ValueError:
26
+ pass # this color is not in PLOT_COLORS (maybe because light style is used)
24
27
  PLOT_COLORS = PlotColors([(np.array(color) / 255).tolist() for color in PLOT_COLORS]) # translation to matplotlib
25
28
 
26
29
 
@@ -38,18 +41,26 @@ class Plotter(PlotterBase):
38
41
  def plot(self, data: Union[DataWithAxes, DataToExport], *args, viewer=None,
39
42
  **kwargs) -> plt.Figure:
40
43
  fig = plt.figure()
44
+ if 'linewidth' in kwargs:
45
+ linewidth = kwargs['linewidth']
46
+ else:
47
+ linewidth = self.linewidth
41
48
 
42
49
  if isinstance(data, DataWithAxes):
43
50
  self.n_columns = len(data) if data.dim.name == 'Data2D' else 1
44
- self.plot_dwa(data, *args, **kwargs)
51
+ self.plot_dwa(data, *args, linewidth=linewidth, **kwargs)
45
52
  elif isinstance(data, DataToExport):
46
53
  self.n_columns = max([len(dwa) if dwa.dim.name == 'Data2D' else 1 for dwa in data])
47
54
  self.n_lines = len(data)
48
- self.plot_dte(data, *args, **kwargs)
55
+ self.plot_dte(data, *args, linewidth=linewidth, **kwargs)
49
56
  plt.tight_layout()
50
57
  fig.suptitle(f'{data.name} taken the {datetime.datetime.fromtimestamp(data.timestamp)}')
51
58
  return fig
52
59
 
60
+ @property
61
+ def linewidth(self) -> int:
62
+ return config('data', 'plotting', 'linewidth')
63
+
53
64
  def plot_dwa(self, dwa: DataWithAxes, *args, **kwargs):
54
65
  if dwa.dim.name == 'Data1D':
55
66
  if len(dwa.axes) == 0:
@@ -1,7 +1,8 @@
1
1
  import copy
2
2
  from typing import Iterable as IterableType, Iterable
3
3
 
4
- from pymodaq_utils.utils import config
4
+ from pymodaq_utils.config import GlobalConfig
5
+ config = GlobalConfig()
5
6
 
6
7
 
7
8
  class PlotColors:
@@ -26,3 +26,4 @@ backend = ['h5py', 'tables', 'h5pyd'] # first element is the default, mostly te
26
26
  backend = ['matplotlib'] # either 'matplotlib' or 'qt' or any other custom backend
27
27
  plot_colors = [[255, 255, 255], [255, 0, 0], [0, 255, 0], [0, 0, 255], [14, 207, 189], [207, 14, 166], [207, 204, 14]]
28
28
  process_roi = true # if True subdata is extracted from the ROI in the data viewers
29
+ linewidth = 1 # default linewidth of the plots
File without changes
File without changes