backscattermeter_analyzer 1.0.2__tar.gz → 2.1.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 (70) hide show
  1. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/PKG-INFO +1 -1
  2. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/pyproject.toml +1 -1
  3. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/__init__.py +15 -0
  4. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/characterize/angle/__init__.py +1 -0
  5. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/characterize/angle/result.py +8 -0
  6. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/measurement.py +1 -1
  7. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/ratio.py +25 -12
  8. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/result.py +25 -8
  9. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/utils.py +14 -10
  10. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/vector.py +7 -4
  11. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/__init__.py +4 -1
  12. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/__init__.py +11 -0
  13. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/path.py +118 -0
  14. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/step_numbers.py +12 -0
  15. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/optic/__init__.py +13 -0
  16. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/__init__.py +15 -0
  17. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/__init__.py +7 -0
  18. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/measurement.py +156 -0
  19. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/utils.py +322 -0
  20. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/__init__.py +16 -0
  21. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/default.py +5 -0
  22. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/__init__.py +9 -0
  23. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/unified_read_write.py +108 -0
  24. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/unified_read_write_method.py +27 -0
  25. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_input_registry.py +226 -0
  26. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_io_registry.py +43 -0
  27. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_io_registry_base.py +586 -0
  28. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_output_registry.py +183 -0
  29. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/scale.py +10 -0
  30. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/__init__.py +11 -0
  31. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/__init__.py +7 -0
  32. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/distance.py +27 -0
  33. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/__init__.py +11 -0
  34. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/no.py +16 -0
  35. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/state.py +20 -0
  36. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/yes.py +20 -0
  37. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/state.py +31 -0
  38. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/__init__.py +7 -0
  39. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/__init__.py +7 -0
  40. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/no.py +14 -0
  41. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/state.py +25 -0
  42. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/yes.py +29 -0
  43. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/__init__.py +7 -0
  44. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/no.py +14 -0
  45. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/state.py +20 -0
  46. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/yes.py +20 -0
  47. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/connect.py +50 -0
  48. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/measurement.py +137 -0
  49. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/__init__.py +11 -0
  50. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/no.py +14 -0
  51. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/state.py +20 -0
  52. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/yes.py +20 -0
  53. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/state.py +92 -0
  54. backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/utils.py +124 -0
  55. backscattermeter_analyzer-1.0.2/src/backscattermeter_analyzer/__init__.py +0 -9
  56. backscattermeter_analyzer-1.0.2/src/backscattermeter_analyzer/hardware/motor/__init__.py +0 -7
  57. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/LICENSE +0 -0
  58. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/README.md +0 -0
  59. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/display.py +0 -0
  60. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/justfile +0 -0
  61. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/mise.toml +0 -0
  62. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/axis.py +0 -0
  63. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/__init__.py +0 -0
  64. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/__init__.py +0 -0
  65. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/distance.py +0 -0
  66. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/direction.py +0 -0
  67. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/backscattermeter.py +0 -0
  68. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/camera/__init__.py +0 -0
  69. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/motor/jump.py +0 -0
  70. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: backscattermeter_analyzer
3
- Version: 1.0.2
3
+ Version: 2.1.0
4
4
  Summary: Tool to work on the backscattermeter at LAPP
5
5
  Project-URL: Documentation, https://git.linarphy.net/linarphy/backscattermeter_analyzer/wiki
6
6
  Project-URL: Homepage, https://git.linarphy.net/linarphy/backscattermeter_analyzer
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "backscattermeter_analyzer"
3
- version = "1.0.2"
3
+ version = "2.1.0"
4
4
  description = "Tool to work on the backscattermeter at LAPP"
5
5
  authors = [{ name = "linarphy", email = "linarphy@linarphy.net" }]
6
6
  readme = "README.md"
@@ -0,0 +1,15 @@
1
+ """Entry-point package."""
2
+
3
+ from .axis import Axis
4
+ from .direction import Direction
5
+ from .io import register_measurement_hdf5
6
+ from .utils import Distance, Measurement
7
+
8
+ register_measurement_hdf5()
9
+
10
+ __all__ = [
11
+ "Axis",
12
+ "Direction",
13
+ "Distance",
14
+ "Measurement",
15
+ ]
@@ -0,0 +1 @@
1
+ """Sub-package used to measure and analyze motor action on angle."""
@@ -0,0 +1,8 @@
1
+ """Result class."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class Result:
8
+ """Result of an analysis of an angle measurement."""
@@ -16,7 +16,7 @@ from backscattermeter_analyzer.hardware import (
16
16
  class Measurement:
17
17
  """Measurement of a distance scan with a camera."""
18
18
 
19
- data: TimeSeriesDict # ty: ignore[invalid-type-form]
19
+ data: TimeSeriesDict # type: ignore[ty:invalid-type-form]
20
20
  camera: Camera
21
21
  backscattermeter: Backscattermeter
22
22
  motor_path: list[MotorJump]
@@ -5,15 +5,17 @@ from typing import TYPE_CHECKING
5
5
  from uuid import uuid4
6
6
 
7
7
  from astropy.uncertainty import Distribution, normal
8
+ from numpy import sqrt
8
9
  from numpy.linalg import norm
9
10
 
10
11
  from backscattermeter_analyzer import Axis, Direction
12
+ from backscattermeter_analyzer.utils.distance.motor import WithMotorJump
11
13
 
12
14
  from .result import Result
13
15
  from .vector import Vector
14
16
 
15
17
  if TYPE_CHECKING:
16
- from .distance import Distance
18
+ from backscattermeter_analyzer.utils.distance import Distance
17
19
 
18
20
 
19
21
  @dataclass
@@ -44,15 +46,33 @@ class Ratio:
44
46
  }
45
47
  for result in results:
46
48
  for distance in result.distances:
49
+ motor_state = distance.state.motor
50
+ if not isinstance(motor_state, WithMotorJump):
51
+ raise TypeError
47
52
  distances[
48
- distance.jump.axis, distance.jump.direction
53
+ motor_state.jump.axis, motor_state.jump.direction
49
54
  ].append(distance)
50
55
 
51
56
  reference_vector = Vector(
52
57
  axis=reference[0],
53
58
  direction=reference[1],
54
59
  distances=distances[reference],
55
- ratio=normal(center=1, std=0.01, n_samples=1000),
60
+ )
61
+
62
+ reference_vector_norm = Distribution(
63
+ norm(
64
+ [
65
+ value.distribution
66
+ for value in reference_vector.step.values()
67
+ ],
68
+ axis=0,
69
+ )
70
+ )
71
+ reference_vector.ratio = normal(
72
+ center=1.0,
73
+ std=reference_vector_norm.pdf_std()
74
+ / sqrt(len(distances[reference])),
75
+ n_samples=1000,
56
76
  )
57
77
 
58
78
  vectors = [
@@ -65,22 +85,15 @@ class Ratio:
65
85
  ]
66
86
 
67
87
  for vector in vectors:
68
- vector.ratio = Distribution(
88
+ vector.ratio = reference_vector_norm / Distribution(
69
89
  norm(
70
- [
71
- value.distribution
72
- for value in reference_vector.step.values()
73
- ],
74
- axis=0,
75
- )
76
- / norm(
77
90
  [
78
91
  value.distribution
79
92
  for value in vector.step.values()
80
93
  ],
81
94
  axis=0,
82
95
  )
83
- )
96
+ ) # type: ignore[ty:unsupported-operator]
84
97
 
85
98
  return Ratio(
86
99
  results=results,
@@ -3,12 +3,16 @@
3
3
  from dataclasses import dataclass, field
4
4
  from uuid import uuid4
5
5
 
6
+ from astropy.time import TimeDelta
6
7
  from astropy.uncertainty import normal
7
- from astropy.units import Quantity
8
8
  from numpy import mean, std
9
9
 
10
- from .distance import Distance
11
- from .measurement import Measurement
10
+ from backscattermeter_analyzer.utils.distance import Distance
11
+ from backscattermeter_analyzer.utils.measurement import Measurement
12
+ from backscattermeter_analyzer.utils.measurement.motor import (
13
+ WithMotorPath,
14
+ )
15
+
12
16
  from .utils import split_stable_segment
13
17
 
14
18
 
@@ -23,10 +27,23 @@ class Result:
23
27
  @staticmethod
24
28
  def analyze(measurement: Measurement) -> "Result":
25
29
  """Generate a result from a measurement by analysing it."""
30
+ motor_state = measurement.state.motor
31
+ if not isinstance(motor_state, WithMotorPath):
32
+ raise TypeError
33
+
34
+ jumps = [element[1] for element in motor_state.path]
35
+ times = [TimeDelta(0.0, format="sec")]
36
+ times.extend(element[0] for element in motor_state.path)
37
+ durations = [
38
+ times[i + 1] - times[i] for i in range(len(times) - 1)
39
+ ]
40
+
41
+ transition_duration = mean(durations) / 4
42
+
26
43
  segments = split_stable_segment(
27
- Quantity(2, "s"),
28
- measurement.data,
29
- len(measurement.motor_path),
44
+ transition_duration=transition_duration.to("s"),
45
+ data=measurement.data,
46
+ transition_number=len(jumps),
30
47
  )
31
48
 
32
49
  positions = [
@@ -49,10 +66,10 @@ class Result:
49
66
  ]
50
67
  vectors = [
51
68
  Distance(
52
- jump=measurement.motor_path[i],
53
69
  distance=distances[i],
70
+ motor_jump=jumps[i],
54
71
  )
55
- for i in range(len(measurement.motor_path))
72
+ for i in range(len(jumps))
56
73
  ]
57
74
 
58
75
  return Result(
@@ -44,8 +44,8 @@ def _find_transition_indices(
44
44
  max_index = None
45
45
  for index in sorted_transition_indices:
46
46
  if (
47
- index - half_transition_samples not in processed_indices
48
- and index + half_transition_samples
47
+ int(index - half_transition_samples) not in processed_indices
48
+ and int(index + half_transition_samples)
49
49
  not in processed_indices
50
50
  ):
51
51
  max_index = index
@@ -61,6 +61,14 @@ def _find_transition_indices(
61
61
  left_index = int(max_index - half_transition_samples)
62
62
  right_index = int(max_index + half_transition_samples)
63
63
 
64
+ left_indices.append(left_index)
65
+ right_indices.append(right_index)
66
+
67
+ left_indices.sort()
68
+ right_indices.sort()
69
+
70
+ left_index_index = left_indices.index(left_index)
71
+
64
72
  if left_index <= 0:
65
73
  message = (
66
74
  "The measurement does not have enough data before a "
@@ -76,16 +84,13 @@ def _find_transition_indices(
76
84
 
77
85
  if (
78
86
  len(right_indices) > 1
79
- and left_index - right_indices[-1] < 2 # noqa: PLR2004
87
+ and left_index - right_indices[left_index_index] < 2 # noqa: PLR2004
80
88
  ):
81
89
  message = (
82
90
  "Transitions are too close in the given measurement."
83
91
  )
84
92
  raise ValueError(message)
85
93
 
86
- left_indices.append(left_index)
87
- right_indices.append(right_index)
88
-
89
94
  processed_indices.update(range(left_index, right_index + 1))
90
95
 
91
96
  left_indices.append(data_length)
@@ -97,10 +102,10 @@ def _find_transition_indices(
97
102
 
98
103
 
99
104
  def split_stable_segment(
100
- transition_duration: Quantity[s], #ty: ignore[not-subscriptable]
101
- data: TimeSeriesDict, # ty: ignore[invalid-type-form]
105
+ transition_duration: Quantity[s], #type: ignore[ty:not-subscriptable]
106
+ data: TimeSeriesDict, # type: ignore[ty:invalid-type-form]
102
107
  transition_number: int,
103
- ) -> list[TimeSeriesDict]: # ty: ignore[invalid-type-form]
108
+ ) -> list[TimeSeriesDict]: # type: ignore[ty:invalid-type-form]
104
109
  """
105
110
  Split N-dim time series into stable segments, excluding transition.
106
111
 
@@ -126,7 +131,6 @@ def split_stable_segment(
126
131
  of segments is transition_number + 1.
127
132
 
128
133
  """
129
-
130
134
  datum = next(iter(data.values())) # get a random TimeSeries in data
131
135
 
132
136
  if len(datum) <= 1:
@@ -8,8 +8,8 @@ from gwpy.detector.channel import Channel
8
8
  from numpy import mean, sqrt
9
9
 
10
10
  from backscattermeter_analyzer import Axis, Direction
11
-
12
- from .distance import Distance
11
+ from backscattermeter_analyzer.utils.distance import Distance
12
+ from backscattermeter_analyzer.utils.distance.motor import WithMotorJump
13
13
 
14
14
 
15
15
  @dataclass
@@ -28,7 +28,7 @@ class Vector:
28
28
  step: dict[str | Channel, Distribution] = field(
29
29
  init=False,
30
30
  doc="displacement for one step",
31
- ) # ty: ignore[no-matching-overload]
31
+ ) # type: ignore[ty:no-matching-overload]
32
32
  identifier: str = field(default_factory=lambda: str(uuid4()))
33
33
 
34
34
  def __post_init__(self) -> None:
@@ -37,7 +37,10 @@ class Vector:
37
37
  distances: dict[str | Channel, list[Distribution]] = {}
38
38
  for distance in self.distances:
39
39
  for key, length in distance.distance.items():
40
- real_distance = length / distance.jump.step_number # ty: ignore[unsupported-operator]
40
+ motor_state = distance.state.motor
41
+ if not isinstance(motor_state, WithMotorJump):
42
+ raise TypeError
43
+ real_distance = length / motor_state.jump.step_number # type: ignore[ty:unsupported-operator]
41
44
  if key not in distances:
42
45
  distances[key] = [real_distance]
43
46
  else:
@@ -2,10 +2,13 @@
2
2
 
3
3
  from .backscattermeter import Backscattermeter
4
4
  from .camera import Camera
5
- from .motor import MotorJump
5
+ from .motor import MotorJump, MotorPath
6
+ from .optic import Optic
6
7
 
7
8
  __all__ = [
8
9
  "Backscattermeter",
9
10
  "Camera",
10
11
  "MotorJump",
12
+ "MotorPath",
13
+ "Optic",
11
14
  ]
@@ -0,0 +1,11 @@
1
+ """Sub-package on motor."""
2
+
3
+ from .jump import Jump as MotorJump
4
+ from .path import Path as MotorPath
5
+ from .step_numbers import StepNumbers
6
+
7
+ __all__ = [
8
+ "MotorJump",
9
+ "MotorPath",
10
+ "StepNumbers",
11
+ ]
@@ -0,0 +1,118 @@
1
+ """Path class."""
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from astropy.time import TimeDelta
6
+
7
+ from backscattermeter_analyzer import Axis, Direction
8
+ from backscattermeter_analyzer.hardware.motor.step_numbers import (
9
+ StepNumbers,
10
+ )
11
+
12
+ from .jump import Jump
13
+
14
+
15
+ class Path(Iterable):
16
+ """A sequence of MotorJump."""
17
+
18
+ def __init__(self, data: list[tuple[TimeDelta, Jump]]) -> None:
19
+ """
20
+ Initialize a MotorPath.
21
+
22
+ Parameters
23
+ ----------
24
+ data: dict[Quantity[s], MotorJump]
25
+ Links between time in second from the start of the
26
+ TimeSeries and a motor jump.
27
+
28
+ """
29
+
30
+ self._inner = data
31
+ self._idx = 0
32
+
33
+ def __iter__(self) -> "Path":
34
+ """Default __iter__ function."""
35
+
36
+ return self
37
+
38
+ def __next__(self) -> tuple[TimeDelta, Jump]:
39
+ """Default __next__ function."""
40
+
41
+ self._idx += 1
42
+ try:
43
+ return self._inner[self._idx - 1]
44
+ except IndexError as exception:
45
+ self._idx = 0
46
+ raise StopIteration from exception
47
+
48
+ def __len__(self) -> int:
49
+ """Default __len__ function."""
50
+
51
+ return len(self._inner)
52
+
53
+ def __getitem__(
54
+ self, subscript: slice | int
55
+ ) -> tuple[TimeDelta, Jump] | list[tuple[TimeDelta, Jump]]:
56
+ """Default __getitem__ function."""
57
+
58
+ return self._inner[subscript]
59
+
60
+ @staticmethod
61
+ def snail(
62
+ number: int,
63
+ duration: TimeDelta,
64
+ speed: int,
65
+ step_numbers: StepNumbers,
66
+ ) -> "Path":
67
+ """Generate a motor path with a snail structure."""
68
+
69
+ x_p = Jump(
70
+ direction=Direction.POSITIVE,
71
+ axis=Axis.X,
72
+ step_number=step_numbers.xp,
73
+ speed=speed,
74
+ )
75
+ x_n = Jump(
76
+ direction=Direction.NEGATIVE,
77
+ axis=Axis.X,
78
+ step_number=step_numbers.xn,
79
+ speed=speed,
80
+ )
81
+ y_p = Jump(
82
+ direction=Direction.POSITIVE,
83
+ axis=Axis.Y,
84
+ step_number=step_numbers.yp,
85
+ speed=speed,
86
+ )
87
+ y_n = Jump(
88
+ direction=Direction.NEGATIVE,
89
+ axis=Axis.Y,
90
+ step_number=step_numbers.yn,
91
+ speed=speed,
92
+ )
93
+
94
+ result: list[tuple[TimeDelta, Jump]] = []
95
+ current_offset = TimeDelta(0.0, format="sec")
96
+
97
+ for i in range(number * 2):
98
+ for _ in range(i + 1):
99
+ current_offset += duration
100
+ if i % 2 == 0:
101
+ result.append((current_offset, x_p))
102
+ else:
103
+ result.append((current_offset, x_n))
104
+ for _ in range(i + 1):
105
+ current_offset += duration
106
+ if i % 2 == 0:
107
+ result.append((current_offset, y_p))
108
+ else:
109
+ result.append((current_offset, y_n))
110
+
111
+ for _ in range(number):
112
+ current_offset += duration
113
+ result.append((current_offset, x_p))
114
+ for _ in range(number):
115
+ current_offset += duration
116
+ result.append((current_offset, y_p))
117
+
118
+ return Path(data=result)
@@ -0,0 +1,12 @@
1
+ """StepNumbers class."""
2
+
3
+ from typing import NamedTuple
4
+
5
+
6
+ class StepNumbers(NamedTuple):
7
+ """Number of steps in each couple (direction, axis)."""
8
+
9
+ xp: int
10
+ xn: int
11
+ yp: int
12
+ yn: int
@@ -0,0 +1,13 @@
1
+ """Sub-package on optic."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class Optic:
8
+ """Information about an optic."""
9
+
10
+
11
+ __all__ = [
12
+ "Optic",
13
+ ]
@@ -0,0 +1,15 @@
1
+ """I/O package."""
2
+
3
+ from .hdf5 import register_measurement_hdf5
4
+ from .registry import (
5
+ UnifiedIORegistry,
6
+ UnifiedReadWrite,
7
+ UnifiedReadWriteMethod,
8
+ )
9
+
10
+ __all__ = [
11
+ "UnifiedIORegistry",
12
+ "UnifiedReadWrite",
13
+ "UnifiedReadWriteMethod",
14
+ "register_measurement_hdf5",
15
+ ]
@@ -0,0 +1,7 @@
1
+ """Sub-package for writing and parsing hdf5 files."""
2
+
3
+ from .measurement import (
4
+ register_measurement as register_measurement_hdf5,
5
+ )
6
+
7
+ __all__ = ["register_measurement_hdf5"]
@@ -0,0 +1,156 @@
1
+ """Hdf5 conversion for Measurement."""
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from h5py import File, Group
7
+
8
+ from backscattermeter_analyzer.hardware import Backscattermeter
9
+ from backscattermeter_analyzer.io.registry.default import (
10
+ default_registry,
11
+ )
12
+
13
+ from .utils import (
14
+ group_to_timeseriesdict,
15
+ is_hdf5,
16
+ timeseriesdict_to_group,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa:E501,TC004
21
+ Measurement,
22
+ )
23
+
24
+
25
+ def read(input_: Path | Group, path: str | None = None) -> Measurement:
26
+ """
27
+ Read a Measurement object from an HDF5 component/file.
28
+
29
+ Parameters
30
+ ----------
31
+ input_: Path | Group
32
+ If a string or a Path, the filename to read the table from.
33
+ If an h5py object, either the file or the group object to read
34
+ the Measurement from.
35
+ path: str | None
36
+ The path from which to read the table inside the HDF5 file.
37
+ This should be relative to the input if the input is a Group.
38
+
39
+ """
40
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa: PLC0415,E501
41
+ Measurement,
42
+ )
43
+
44
+ if isinstance(input_, Path):
45
+ input_ = File(input_)
46
+
47
+ # input_ is now a Group (a File is a Group)
48
+ if path is not None:
49
+ item = input_.get(path, default=None)
50
+ if item is None:
51
+ message = f"{path} is not a valid path in {input_}."
52
+ raise ValueError(message)
53
+ if not isinstance(item, Group):
54
+ message = (
55
+ f"{path} from {input_} is a {type(item)}, not a Group."
56
+ )
57
+ raise TypeError(message)
58
+ else:
59
+ item = input_
60
+
61
+ group_data = item.get("data", default=None)
62
+ if group_data is None:
63
+ message = f"No 'data' group in {path} from {input_}."
64
+ raise ValueError(message)
65
+ if not isinstance(group_data, Group):
66
+ message = f"'data' must be a group in {path} from {input_}."
67
+ raise TypeError(message)
68
+
69
+ measurement = Measurement(
70
+ data=group_to_timeseriesdict(group_data),
71
+ backscattermeter=Backscattermeter(),
72
+ )
73
+
74
+ if isinstance(input_, File):
75
+ input_.close()
76
+
77
+ return measurement
78
+
79
+
80
+ def write(
81
+ measurement: Measurement,
82
+ output: Path | Group,
83
+ path: str | None = None,
84
+ *,
85
+ append: bool = False,
86
+ overwrite: bool = False,
87
+ ) -> None:
88
+ """
89
+ Write a Measurement object to an HDF5 file.
90
+
91
+ Parameters
92
+ ----------
93
+ measurement: Measurement
94
+ Measurement that is to be written to file.
95
+ output: Path | Group
96
+ If a Path, the path of the file to write the measurement to. If
97
+ a Group, the group object to write the measurement to, depending
98
+ on `path` value.
99
+ path: str | None
100
+ The path to which to write the table inside the HDF5 file.
101
+ This should be relative to the output Group (or the root Group
102
+ if output is a Path). Default to None.
103
+ append: bool
104
+ Whether to append the Measurement to an existing HDF5 file.
105
+ Default to False. This can delete a previous measurement.
106
+ overwrite: bool
107
+ Whether to overwrite any existing file without warning. Default
108
+ to False. If `append=True` and `overwrite=True`, then only the
109
+ group will be replaced.
110
+
111
+ """
112
+
113
+ if isinstance(output, Path):
114
+ if overwrite and not append:
115
+ output = File(output, mode="w")
116
+ elif append:
117
+ output = File(output, mode="r+")
118
+ else:
119
+ output = File(output, mode="x")
120
+
121
+ # output is now a Group (a File is a Group)
122
+ if path is not None:
123
+ item = output.get(path, default=None)
124
+ if item is None:
125
+ message = f"{path} is not a valid path in {output}."
126
+ raise ValueError(message)
127
+ if not isinstance(item, Group):
128
+ message = (
129
+ f"{path} from {output} is a {type(item)}, not a Group."
130
+ )
131
+ raise TypeError(message)
132
+ else:
133
+ item = output
134
+
135
+ element_number = len(item.keys())
136
+ if element_number != 0 and append:
137
+ item.clear()
138
+ elif element_number != 0 and not append:
139
+ message = f"{path} from {output} is not an empty group."
140
+ raise ValueError(message)
141
+
142
+ timeseriesdict_to_group(item, "data", measurement.data)
143
+
144
+ if isinstance(output, File):
145
+ output.close()
146
+
147
+
148
+ def register_measurement() -> None:
149
+ """Register Measurement hdf5 conversion in Unified I/O."""
150
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa: PLC0415,E501
151
+ Measurement,
152
+ )
153
+
154
+ default_registry.register_identifier("hdf5", Measurement, is_hdf5)
155
+ default_registry.register_reader("hdf5", Measurement, read)
156
+ default_registry.register_writer("hdf5", Measurement, write)