backscattermeter_analyzer 1.0.2__tar.gz → 2.0.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 (53) hide show
  1. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/PKG-INFO +1 -1
  2. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/pyproject.toml +1 -1
  3. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/characterize/angle/__init__.py +1 -0
  4. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/characterize/angle/result.py +8 -0
  5. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/measurement.py +1 -1
  6. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/ratio.py +25 -12
  7. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/result.py +25 -8
  8. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/utils.py +14 -10
  9. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/vector.py +7 -4
  10. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/hardware/__init__.py +4 -1
  11. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/hardware/motor/__init__.py +11 -0
  12. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/hardware/motor/path.py +118 -0
  13. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/hardware/motor/step_numbers.py +12 -0
  14. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/hardware/optic/__init__.py +13 -0
  15. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/__init__.py +7 -0
  16. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/__init__.py +7 -0
  17. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/distance.py +27 -0
  18. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/motor/__init__.py +11 -0
  19. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/motor/no.py +16 -0
  20. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/motor/state.py +20 -0
  21. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/motor/yes.py +20 -0
  22. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/distance/state.py +31 -0
  23. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/__init__.py +7 -0
  24. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/angle/__init__.py +7 -0
  25. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/angle/no.py +14 -0
  26. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/angle/state.py +25 -0
  27. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/angle/yes.py +29 -0
  28. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/camera/__init__.py +7 -0
  29. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/camera/no.py +14 -0
  30. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/camera/state.py +20 -0
  31. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/camera/yes.py +20 -0
  32. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/measurement.py +34 -0
  33. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/motor/__init__.py +11 -0
  34. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/motor/no.py +14 -0
  35. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/motor/state.py +20 -0
  36. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/motor/yes.py +20 -0
  37. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/state.py +92 -0
  38. backscattermeter_analyzer-1.0.2/src/backscattermeter_analyzer/hardware/motor/__init__.py +0 -7
  39. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/LICENSE +0 -0
  40. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/README.md +0 -0
  41. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/display.py +0 -0
  42. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/justfile +0 -0
  43. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/mise.toml +0 -0
  44. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/__init__.py +0 -0
  45. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/axis.py +0 -0
  46. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/__init__.py +0 -0
  47. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/__init__.py +0 -0
  48. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/characterize/distance/distance.py +0 -0
  49. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/direction.py +0 -0
  50. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/hardware/backscattermeter.py +0 -0
  51. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/hardware/camera/__init__.py +0 -0
  52. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.0}/src/backscattermeter_analyzer/hardware/motor/jump.py +0 -0
  53. {backscattermeter_analyzer-1.0.2 → backscattermeter_analyzer-2.0.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.0.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.0.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 @@
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,7 @@
1
+ """Utils classes and functions for characterization."""
2
+
3
+ from .distance import Distance
4
+
5
+ __all__ = [
6
+ "Distance",
7
+ ]
@@ -0,0 +1,7 @@
1
+ """Manage distance states."""
2
+
3
+ from .distance import Distance
4
+
5
+ __all__ = [
6
+ "Distance",
7
+ ]
@@ -0,0 +1,27 @@
1
+ """Distance class."""
2
+
3
+ from dataclasses import InitVar, dataclass, field
4
+ from uuid import UUID, uuid4
5
+
6
+ from astropy.uncertainty import Distribution
7
+ from gwpy.detector.channel import Channel
8
+
9
+ from backscattermeter_analyzer.hardware import MotorJump
10
+
11
+ from .state import State
12
+
13
+
14
+ @dataclass
15
+ class Distance:
16
+ """Distances between two points from multiple channels/axis."""
17
+
18
+ distance: dict[str | Channel, Distribution]
19
+ state: State = field(init=False)
20
+ identifier: UUID = field(default_factory=uuid4)
21
+
22
+ motor_jump: InitVar[MotorJump | None] = None
23
+
24
+ def __post_init__(self, motor_jump: MotorJump | None) -> None:
25
+ """Post initialization."""
26
+
27
+ self.state = State(motor_jump=motor_jump)
@@ -0,0 +1,11 @@
1
+ """Manage states of the link between distance and motor jump."""
2
+
3
+ from .no import WithoutMotorJump
4
+ from .state import State
5
+ from .yes import WithMotorJump
6
+
7
+ __all__ = [
8
+ "State",
9
+ "WithMotorJump",
10
+ "WithoutMotorJump",
11
+ ]
@@ -0,0 +1,16 @@
1
+ """WithoutMotorJump class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from .state import State
7
+
8
+
9
+ @dataclass
10
+ class WithoutMotorJump(State):
11
+ """The distance is not linked to a motor jump."""
12
+
13
+ def has_motor_jump(self) -> Literal[False]:
14
+ """In this state, the distance is not linked to a motor jump."""
15
+
16
+ return False
@@ -0,0 +1,20 @@
1
+ """State class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from backscattermeter_analyzer.utils.distance.state import (
8
+ State as DistanceState,
9
+ )
10
+
11
+
12
+ @dataclass
13
+ class State:
14
+ """State of the link between distance and motor jump."""
15
+
16
+ state: "DistanceState"
17
+
18
+ def has_motor_jump(self) -> bool:
19
+ """Check the distance is linked to a motor jump."""
20
+ return NotImplemented
@@ -0,0 +1,20 @@
1
+ """WithMotorJump class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from backscattermeter_analyzer.hardware import MotorJump
7
+
8
+ from .state import State
9
+
10
+
11
+ @dataclass
12
+ class WithMotorJump(State):
13
+ """The distance is linked to a motor jump."""
14
+
15
+ jump: MotorJump
16
+
17
+ def has_motor_jump(self) -> Literal[True]:
18
+ """In this state, the distance is linked to a motor jump."""
19
+
20
+ return True
@@ -0,0 +1,31 @@
1
+ """State class."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from backscattermeter_analyzer.hardware import MotorJump
6
+
7
+ from .motor import State as MotorState
8
+ from .motor import WithMotorJump, WithoutMotorJump
9
+
10
+ if TYPE_CHECKING:
11
+ from .distance import Distance
12
+
13
+
14
+ class State:
15
+ """Distance state holder."""
16
+
17
+ distance: "Distance"
18
+ motor: MotorState
19
+
20
+ def __init__(self, motor_jump: MotorJump | None = None) -> None:
21
+ """Initialize distance state."""
22
+
23
+ if motor_jump is not None:
24
+ self.motor = WithMotorJump(state=self, jump=motor_jump)
25
+ else:
26
+ self.motor = WithoutMotorJump(state=self)
27
+
28
+ def has_motor_jump(self) -> bool:
29
+ """Check if the distance si linked to a motor jump."""
30
+
31
+ return self.motor.has_motor_jump()
@@ -0,0 +1,7 @@
1
+ """Manage measurement states."""
2
+
3
+ from .measurement import Measurement
4
+
5
+ __all__ = [
6
+ "Measurement",
7
+ ]
@@ -0,0 +1,7 @@
1
+ """Manage measurement states about manual change of angle."""
2
+
3
+ from .no import WithoutManualAngle
4
+ from .state import State as AngleState
5
+ from .yes import WithManualAngle
6
+
7
+ __all__ = ["AngleState", "WithManualAngle", "WithoutManualAngle"]
@@ -0,0 +1,14 @@
1
+ """WithoutManualAngle class."""
2
+
3
+ from typing import Literal
4
+
5
+ from .state import State
6
+
7
+
8
+ class WithoutManualAngle(State):
9
+ """The measurement was done without any change of angle."""
10
+
11
+ def has_manual_angle_change(self) -> Literal[False]:
12
+ """No manual change of angle in this state."""
13
+
14
+ return False
@@ -0,0 +1,25 @@
1
+ """State class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from backscattermeter_analyzer.utils.measurement.state import (
8
+ State as MeasurementState,
9
+ )
10
+
11
+
12
+ @dataclass
13
+ class State:
14
+ """Angle state for this measurement."""
15
+
16
+ state: "MeasurementState"
17
+
18
+ def has_manual_angle_change(self) -> bool:
19
+ """
20
+ Define if measurement is associated to a manual change of angle.
21
+
22
+ Check the measurement has been made during a manual change of
23
+ angle of an optic in the setup.
24
+ """
25
+ return NotImplemented
@@ -0,0 +1,29 @@
1
+ """WithManualAngle class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from astropy.uncertainty import Distribution
7
+
8
+ from backscattermeter_analyzer import Axis
9
+ from backscattermeter_analyzer.hardware import Optic
10
+
11
+ from .state import State
12
+
13
+
14
+ @dataclass
15
+ class WithManualAngle(State):
16
+ """The measurement was done with a manual change of angle."""
17
+
18
+ axis: Axis # axis of the angle
19
+ angle: Distribution # the angle difference
20
+ optic: Optic # the optic we changed the angle of
21
+ # we don't know exactly when the change of angle was made, so we
22
+ # cannot store even an approximation of the time during which the
23
+ # change of angle was done. This can be retrieved from the analysis
24
+ # through, but it's not very useful anyway.
25
+
26
+ def has_manual_angle_change(self) -> Literal[True]:
27
+ """Manual change of angle in this state."""
28
+
29
+ return True
@@ -0,0 +1,7 @@
1
+ """Manage measurement states about camera existence."""
2
+
3
+ from .no import WithoutCamera
4
+ from .state import State as CameraState
5
+ from .yes import WithCamera
6
+
7
+ __all__ = ["CameraState", "WithCamera", "WithoutCamera"]
@@ -0,0 +1,14 @@
1
+ """WithoutCamera class."""
2
+
3
+ from typing import Literal
4
+
5
+ from .state import State
6
+
7
+
8
+ class WithoutCamera(State):
9
+ """The measurement was done without any camera."""
10
+
11
+ def has_camera(self) -> Literal[False]:
12
+ """No camera if in this state."""
13
+
14
+ return False
@@ -0,0 +1,20 @@
1
+ """State class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from backscattermeter_analyzer.utils.measurement.state import (
8
+ State as MeasurementState,
9
+ )
10
+
11
+
12
+ @dataclass
13
+ class State:
14
+ """Camera state for this measurement."""
15
+
16
+ state: "MeasurementState"
17
+
18
+ def has_camera(self) -> bool:
19
+ """Check the measurement was made with a camera."""
20
+ return NotImplemented
@@ -0,0 +1,20 @@
1
+ """WithCamera class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from backscattermeter_analyzer.hardware import Camera
7
+
8
+ from .state import State
9
+
10
+
11
+ @dataclass
12
+ class WithCamera(State):
13
+ """The measurement was done with a camera."""
14
+
15
+ camera: Camera
16
+
17
+ def have_camera(self) -> Literal[True]:
18
+ """Camera used if in this state."""
19
+
20
+ return True
@@ -0,0 +1,34 @@
1
+ """Measurement class."""
2
+
3
+ from dataclasses import InitVar, dataclass, field
4
+ from uuid import UUID, uuid4
5
+
6
+ from gwpy.timeseries import TimeSeriesDict
7
+
8
+ from backscattermeter_analyzer.hardware import (
9
+ Backscattermeter,
10
+ Camera,
11
+ MotorPath,
12
+ )
13
+
14
+ from .state import State
15
+
16
+
17
+ @dataclass
18
+ class Measurement:
19
+ """A measurement on the backscattermeter."""
20
+
21
+ data: TimeSeriesDict # type: ignore[ty:invalid-type-form]
22
+ state: State = field(init=False)
23
+ backscattermeter: Backscattermeter
24
+ identifier: UUID = field(default_factory=uuid4)
25
+
26
+ camera: InitVar[Camera | None] = None
27
+ motor_path: InitVar[MotorPath | None] = None
28
+
29
+ def __post_init__(
30
+ self, camera: Camera | None, motor_path: MotorPath | None
31
+ ) -> None:
32
+ """Post initialization."""
33
+
34
+ self.state = State(camera=camera, motor_path=motor_path)
@@ -0,0 +1,11 @@
1
+ """Manage measurement states about the link with a motor path."""
2
+
3
+ from .no import WithoutMotorPath
4
+ from .state import State as MotorState
5
+ from .yes import WithMotorPath
6
+
7
+ __all__ = [
8
+ "MotorState",
9
+ "WithMotorPath",
10
+ "WithoutMotorPath",
11
+ ]
@@ -0,0 +1,14 @@
1
+ """WithoutMotorPath class."""
2
+
3
+ from typing import Literal
4
+
5
+ from .state import State
6
+
7
+
8
+ class WithoutMotorPath(State):
9
+ """The measurement is not linked to a motor path."""
10
+
11
+ def is_linked_to_a_motor_path(self) -> Literal[False]:
12
+ """In this state, measurement is not linked to a motor path."""
13
+
14
+ return False
@@ -0,0 +1,20 @@
1
+ """State class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from backscattermeter_analyzer.utils.measurement.state import (
8
+ State as MeasurementState,
9
+ )
10
+
11
+
12
+ @dataclass
13
+ class State:
14
+ """State of the link between the measurement and a motor path."""
15
+
16
+ state: "MeasurementState"
17
+
18
+ def is_linked_to_a_motor_path(self) -> bool:
19
+ """Answer: is this measurement linked to a motor path."""
20
+ return NotImplemented
@@ -0,0 +1,20 @@
1
+ """WithMotorPath class."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from backscattermeter_analyzer.hardware import MotorPath
7
+
8
+ from .state import State
9
+
10
+
11
+ @dataclass
12
+ class WithMotorPath(State):
13
+ """The measurement is linked to a motor path."""
14
+
15
+ path: MotorPath
16
+
17
+ def is_linked_to_a_motor_path(self) -> Literal[True]:
18
+ """In this state, the measurement is linked to a motor path."""
19
+
20
+ return True
@@ -0,0 +1,92 @@
1
+ """State class."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from astropy.uncertainty import Distribution
6
+
7
+ from backscattermeter_analyzer import Axis
8
+ from backscattermeter_analyzer.hardware import (
9
+ Camera,
10
+ MotorPath,
11
+ Optic,
12
+ )
13
+
14
+ from .angle import AngleState, WithManualAngle, WithoutManualAngle
15
+ from .camera import CameraState, WithCamera, WithoutCamera
16
+ from .motor import MotorState, WithMotorPath, WithoutMotorPath
17
+
18
+ if TYPE_CHECKING:
19
+ from .measurement import Measurement
20
+
21
+
22
+ class State:
23
+ """Measurement state holder."""
24
+
25
+ measurement: "Measurement"
26
+ angle: AngleState
27
+ camera: CameraState
28
+ motor: MotorState
29
+
30
+ def __init__(
31
+ self,
32
+ camera: Camera | None,
33
+ motor_path: MotorPath | None = None,
34
+ angle: Distribution | None = None,
35
+ angle_axis: Axis | None = None,
36
+ angle_optic: Optic | None = None,
37
+ ) -> None:
38
+ """
39
+ Initialize measurement state.
40
+
41
+ Parameters
42
+ ----------
43
+ camera: Camera | None
44
+ Camera used during the experiment, if it exists. Else, None.
45
+ motor_path: MotorPath | None
46
+ Chain of motor jump during the experiment, if it exists.
47
+ Else, None.
48
+ angle: Distribution | None
49
+ Estimation of the change in angle made during the
50
+ measurement, if it exisis. Else, None.
51
+ angle_axis: Axis | None
52
+ Must be set if angle is set. Define the axis of the angle.
53
+ angle_optic: Optic | None
54
+ Must be set if angle is set. Define the optic which was
55
+ tilted.
56
+
57
+ """
58
+
59
+ if camera is not None:
60
+ self.camera = WithCamera(state=self, camera=camera)
61
+ else:
62
+ self.camera = WithoutCamera(state=self)
63
+ if motor_path is not None:
64
+ self.motor = WithMotorPath(state=self, path=motor_path)
65
+ else:
66
+ self.motor = WithoutMotorPath(state=self)
67
+ if angle is not None:
68
+ if angle_axis is None or angle_optic is None:
69
+ raise ValueError
70
+ self.angle = WithManualAngle(
71
+ state=self,
72
+ axis=angle_axis,
73
+ angle=angle,
74
+ optic=angle_optic,
75
+ )
76
+ else:
77
+ self.angle = WithoutManualAngle(state=self)
78
+
79
+ def has_camera(self) -> bool:
80
+ """Check if this measurement is associated to a camera."""
81
+
82
+ return self.camera.has_camera()
83
+
84
+ def is_linked_to_a_motor_path(self) -> bool:
85
+ """Check if this measurement is linked to a motor path."""
86
+
87
+ return self.motor.is_linked_to_a_motor_path()
88
+
89
+ def has_manual_angle_change(self) -> bool:
90
+ """Is measurement is associated to a tilt of an optic."""
91
+
92
+ return self.angle.has_manual_angle_change()
@@ -1,7 +0,0 @@
1
- """Sub-package on motor."""
2
-
3
- from .jump import Jump as MotorJump
4
-
5
- __all__ = [
6
- "MotorJump",
7
- ]