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.
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/PKG-INFO +1 -1
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/pyproject.toml +1 -1
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/__init__.py +15 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/characterize/angle/__init__.py +1 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/characterize/angle/result.py +8 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/measurement.py +1 -1
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/ratio.py +25 -12
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/result.py +25 -8
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/utils.py +14 -10
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/vector.py +7 -4
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/__init__.py +4 -1
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/__init__.py +11 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/path.py +118 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/motor/step_numbers.py +12 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/hardware/optic/__init__.py +13 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/__init__.py +15 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/__init__.py +7 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/measurement.py +156 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/hdf5/utils.py +322 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/__init__.py +16 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/default.py +5 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/__init__.py +9 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/unified_read_write.py +108 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/interface/unified_read_write_method.py +27 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_input_registry.py +226 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_io_registry.py +43 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_io_registry_base.py +586 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/io/registry/unified_output_registry.py +183 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/scale.py +10 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/__init__.py +11 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/__init__.py +7 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/distance.py +27 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/__init__.py +11 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/no.py +16 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/state.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/motor/yes.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/distance/state.py +31 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/__init__.py +7 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/__init__.py +7 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/no.py +14 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/state.py +25 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/angle/yes.py +29 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/__init__.py +7 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/no.py +14 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/state.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/camera/yes.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/connect.py +50 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/measurement.py +137 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/__init__.py +11 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/no.py +14 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/state.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/motor/yes.py +20 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/measurement/state.py +92 -0
- backscattermeter_analyzer-2.1.0/src/backscattermeter_analyzer/utils/utils.py +124 -0
- backscattermeter_analyzer-1.0.2/src/backscattermeter_analyzer/__init__.py +0 -9
- backscattermeter_analyzer-1.0.2/src/backscattermeter_analyzer/hardware/motor/__init__.py +0 -7
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/LICENSE +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/README.md +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/display.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/justfile +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/mise.toml +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/axis.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/__init__.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/__init__.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/characterize/distance/distance.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/direction.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/backscattermeter.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/camera/__init__.py +0 -0
- {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.1.0}/src/backscattermeter_analyzer/hardware/motor/jump.py +0 -0
- {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
|
|
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
|
|
@@ -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."""
|
|
@@ -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 #
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
measurement.data,
|
|
29
|
-
len(
|
|
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(
|
|
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[
|
|
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], #
|
|
101
|
-
data: TimeSeriesDict, #
|
|
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]: #
|
|
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
|
|
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
|
-
) #
|
|
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
|
-
|
|
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,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,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,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)
|