horiba-sdk 0.5.4__tar.gz → 0.7.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.
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/PKG-INFO +2 -1
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/communication/messages.py +7 -3
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/communication/websocket_communicator.py +1 -1
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/acquisition_format.py +1 -2
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/__init__.py +6 -0
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/labspec6_spectra_stitch.py +90 -0
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/linear_spectra_stitch.py +107 -0
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/simple_cut_spectra_stitch.py +84 -0
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/spectra_stitch.py +16 -0
- horiba_sdk-0.7.0/horiba_sdk/core/stitching/y_displacement_spectra_stitch.py +87 -0
- horiba_sdk-0.7.0/horiba_sdk/core/trigger_input_polarity.py +6 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/device_manager.py +19 -3
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/fake_icl_server.py +7 -0
- horiba_sdk-0.7.0/horiba_sdk/devices/fake_responses/spectracq3.json +217 -0
- {horiba_sdk-0.5.4/horiba_sdk/sync → horiba_sdk-0.7.0/horiba_sdk}/devices/single_devices/__init__.py +2 -1
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/single_devices/ccd.py +4 -2
- horiba_sdk-0.7.0/horiba_sdk/devices/single_devices/spectracq3.py +392 -0
- horiba_sdk-0.7.0/horiba_sdk/devices/spectracq3_discovery.py +55 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/pyproject.toml +5 -1
- horiba_sdk-0.5.4/horiba_sdk/devices/single_devices/__init__.py +0 -5
- horiba_sdk-0.5.4/horiba_sdk/sync/__init__.py +0 -0
- horiba_sdk-0.5.4/horiba_sdk/sync/communication/__init__.py +0 -7
- horiba_sdk-0.5.4/horiba_sdk/sync/communication/abstract_communicator.py +0 -47
- horiba_sdk-0.5.4/horiba_sdk/sync/communication/test_client.py +0 -16
- horiba_sdk-0.5.4/horiba_sdk/sync/communication/websocket_communicator.py +0 -232
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/__init__.py +0 -15
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/abstract_device_discovery.py +0 -17
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/abstract_device_manager.py +0 -68
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/device_discovery.py +0 -58
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/device_manager.py +0 -213
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/fake_device_manager.py +0 -91
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/fake_icl_server.py +0 -82
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/single_devices/abstract_device.py +0 -87
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/single_devices/ccd.py +0 -674
- horiba_sdk-0.5.4/horiba_sdk/sync/devices/single_devices/monochromator.py +0 -413
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/LICENSE +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/README.md +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/__init__.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/communication/__init__.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/communication/abstract_communicator.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/communication/communication_exception.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/__init__.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/clean_count_mode.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/resolution.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/timer_resolution.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/core/x_axis_conversion_type.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/__init__.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/abstract_device_discovery.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/abstract_device_manager.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/ccd_discovery.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/fake_device_manager.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/fake_responses/ccd.json +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/fake_responses/icl.json +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/fake_responses/monochromator.json +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/monochromator_discovery.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/single_devices/abstract_device.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/devices/single_devices/monochromator.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/__init__.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/abstract_error.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/abstract_error_db.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/error_list.json +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/icl_error.py +0 -0
- {horiba_sdk-0.5.4 → horiba_sdk-0.7.0}/horiba_sdk/icl_error/icl_error_db.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: horiba-sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0
|
4
4
|
Summary: 'horiba-sdk' is a package that provides source code for the development with Horiba devices
|
5
5
|
Home-page: https://github.com/ThatsTheEnd/horiba-python-sdk
|
6
6
|
License: MIT
|
@@ -21,6 +21,7 @@ Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
21
21
|
Requires-Dist: overrides (>=7.4.0,<8.0.0)
|
22
22
|
Requires-Dist: pint (>=0.23,<0.24)
|
23
23
|
Requires-Dist: psutil (>=5.9.7,<6.0.0)
|
24
|
+
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
24
25
|
Requires-Dist: websockets (>=12.0,<13.0)
|
25
26
|
Project-URL: Repository, https://github.com/ThatsTheEnd/horiba-python-sdk
|
26
27
|
Description-Content-Type: text/markdown
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
from itertools import count
|
3
|
-
from typing import Any,
|
3
|
+
from typing import Any, Optional, Union, final
|
4
4
|
|
5
5
|
|
6
6
|
class Command:
|
@@ -21,7 +21,7 @@ class Command:
|
|
21
21
|
|
22
22
|
_id_counter = count(start=1) # Starts counting from 1
|
23
23
|
|
24
|
-
def __init__(self, command: str, parameters:
|
24
|
+
def __init__(self, command: str, parameters: dict[str, Any]):
|
25
25
|
self.id = next(self._id_counter) # Automatically assigns the next unique ID
|
26
26
|
self.command = command
|
27
27
|
self.parameters = parameters
|
@@ -43,7 +43,11 @@ class Response:
|
|
43
43
|
"""
|
44
44
|
|
45
45
|
def __init__(
|
46
|
-
self,
|
46
|
+
self,
|
47
|
+
id: int,
|
48
|
+
command: str,
|
49
|
+
results: Optional[Union[dict[str, Any], Any]] = None,
|
50
|
+
errors: Optional[list[str]] = None,
|
47
51
|
):
|
48
52
|
self.id = id
|
49
53
|
self.command = command
|
@@ -63,7 +63,7 @@ class WebsocketCommunicator(AbstractCommunicator):
|
|
63
63
|
raise CommunicationException(None, 'websocket already opened')
|
64
64
|
|
65
65
|
try:
|
66
|
-
self.websocket = await websockets.connect(self.uri)
|
66
|
+
self.websocket = await websockets.connect(self.uri, max_size=None) # type: ignore
|
67
67
|
# self.flush_incoming_messages(self) # Flush any incoming messages (if any
|
68
68
|
|
69
69
|
except websockets.WebSocketException as e:
|
@@ -0,0 +1,6 @@
|
|
1
|
+
from .linear_spectra_stitch import LinearSpectraStitch
|
2
|
+
from .simple_cut_spectra_stitch import SimpleCutSpectraStitch
|
3
|
+
from .spectra_stitch import SpectraStitch
|
4
|
+
from .y_displacement_spectra_stitch import YDisplacementSpectraStitch
|
5
|
+
|
6
|
+
__all__ = ['LinearSpectraStitch', 'SimpleCutSpectraStitch', 'YDisplacementSpectraStitch', 'SpectraStitch']
|
@@ -0,0 +1,90 @@
|
|
1
|
+
from typing import Any, List
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
from numpy import array, concatenate, dtype, ndarray
|
5
|
+
from overrides import override
|
6
|
+
|
7
|
+
from horiba_sdk.core.stitching.spectra_stitch import SpectraStitch
|
8
|
+
|
9
|
+
|
10
|
+
class LabSpec6SpectraStitch(SpectraStitch):
|
11
|
+
"""Stitches a list of spectra using a weighted average as in LabSpec6"""
|
12
|
+
|
13
|
+
def __init__(self, spectra_list: List[List[List[float]]]) -> None:
|
14
|
+
"""Constructs a linear stitch of spectra.
|
15
|
+
|
16
|
+
.. warning:: The spectra in the list must overlap
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
spectra_list : List[List[List[float]]] List of spectra to stitch in the form [[x1_values, y1_values],
|
20
|
+
[x2_values, y2_values], etc].
|
21
|
+
"""
|
22
|
+
stitched_spectrum = spectra_list[0]
|
23
|
+
|
24
|
+
for i in range(1, len(spectra_list)):
|
25
|
+
stitched_spectrum = self._stitch_spectra(stitched_spectrum, spectra_list[i])
|
26
|
+
|
27
|
+
self._stitched_spectrum: List[List[float]] = stitched_spectrum
|
28
|
+
|
29
|
+
@override
|
30
|
+
def stitch_with(self, other_stitch: SpectraStitch) -> SpectraStitch:
|
31
|
+
"""Stitches this stitch with another stitch.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
other_stitch : SpectraStitch The other stitch to stitch with
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
SpectraStitch: The stitched spectra.
|
38
|
+
"""
|
39
|
+
new_stitch = LabSpec6SpectraStitch([self.stitched_spectra(), other_stitch.stitched_spectra()])
|
40
|
+
return new_stitch
|
41
|
+
|
42
|
+
@override
|
43
|
+
def stitched_spectra(self) -> Any:
|
44
|
+
"""Returns the raw data of the stitched spectra.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Any: The stitched spectra.
|
48
|
+
"""
|
49
|
+
return self._stitched_spectrum
|
50
|
+
|
51
|
+
def _stitch_spectra(self, spectrum1: List[List[float]], spectrum2: List[List[float]]) -> List[List[float]]:
|
52
|
+
fx1, fy1 = spectrum1
|
53
|
+
fx2, fy2 = spectrum2
|
54
|
+
|
55
|
+
x1: ndarray[Any, dtype[Any]] = array(fx1)
|
56
|
+
x2: ndarray[Any, dtype[Any]] = array(fx2)
|
57
|
+
y1: ndarray[Any, dtype[Any]] = array(fy1)
|
58
|
+
y2: ndarray[Any, dtype[Any]] = array(fy2)
|
59
|
+
|
60
|
+
overlap_start = max(x1[0], x2[0])
|
61
|
+
overlap_end = min(x1[-1], x2[-1])
|
62
|
+
|
63
|
+
if overlap_start >= overlap_end:
|
64
|
+
logger.error(f'No overlap between two spectra: {spectrum1}, {spectrum2}')
|
65
|
+
raise Exception('No overlapping region between spectra')
|
66
|
+
|
67
|
+
mask1 = (x1 >= overlap_start) & (x1 <= overlap_end)
|
68
|
+
mask2 = (x2 >= overlap_start) & (x2 <= overlap_end)
|
69
|
+
|
70
|
+
x1_overlap = x1[mask1]
|
71
|
+
y1_overlap = y1[mask1]
|
72
|
+
|
73
|
+
x2_overlap = x2[mask2]
|
74
|
+
y2_overlap = y2[mask2]
|
75
|
+
|
76
|
+
A = (x1_overlap - overlap_start) / (overlap_end - overlap_start)
|
77
|
+
B = (overlap_end - x2_overlap) / (overlap_end - overlap_start)
|
78
|
+
|
79
|
+
y_stitched = (y1_overlap * A + y2_overlap * B) / (A + B)
|
80
|
+
|
81
|
+
x_before = x1[x1 < overlap_start]
|
82
|
+
y_before = y1[x1 < overlap_start]
|
83
|
+
|
84
|
+
x_after = x2[x2 > overlap_end]
|
85
|
+
y_after = y2[x2 > overlap_end]
|
86
|
+
|
87
|
+
x_stitched = concatenate([x_before, x1_overlap, x_after])
|
88
|
+
y_stitched_final = concatenate([y_before, y_stitched, y_after])
|
89
|
+
|
90
|
+
return [x_stitched.tolist(), y_stitched_final.tolist()]
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
from numpy import argsort, array, concatenate, dtype, interp, ndarray
|
5
|
+
from overrides import override
|
6
|
+
|
7
|
+
from horiba_sdk.core.stitching.spectra_stitch import SpectraStitch
|
8
|
+
|
9
|
+
|
10
|
+
class LinearSpectraStitch(SpectraStitch):
|
11
|
+
"""Stitches a list of spectra using a linear model"""
|
12
|
+
|
13
|
+
def __init__(self, spectra_list: list[list[list[float]]]) -> None:
|
14
|
+
"""Constructs a linear stitch of spectra.
|
15
|
+
|
16
|
+
.. warning:: The spectra in the list must overlap
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
spectra_list : List[List[List[float]]] List of spectra to stitch in the form [[x1_values, y1_values],
|
20
|
+
[x2_values, y2_values], etc].
|
21
|
+
"""
|
22
|
+
stitched_spectrum = spectra_list[0]
|
23
|
+
|
24
|
+
for i in range(1, len(spectra_list)):
|
25
|
+
stitched_spectrum = self._stitch_spectra(stitched_spectrum, spectra_list[i])
|
26
|
+
|
27
|
+
self._stitched_spectrum: list[list[float]] = stitched_spectrum
|
28
|
+
|
29
|
+
@override
|
30
|
+
def stitch_with(self, other_stitch: SpectraStitch) -> SpectraStitch:
|
31
|
+
"""Stitches this stitch with another stitch.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
other_stitch : SpectraStitch The other stitch to stitch with
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
SpectraStitch: The stitched spectra.
|
38
|
+
"""
|
39
|
+
new_stitch = LinearSpectraStitch([self.stitched_spectra(), other_stitch.stitched_spectra()])
|
40
|
+
return new_stitch
|
41
|
+
|
42
|
+
@override
|
43
|
+
def stitched_spectra(self) -> Any:
|
44
|
+
"""Returns the raw data of the stitched spectra.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Any: The stitched spectra.
|
48
|
+
"""
|
49
|
+
return self._stitched_spectrum
|
50
|
+
|
51
|
+
def _stitch_spectra(self, spectrum1: list[list[float]], spectrum2: list[list[float]]) -> list[list[float]]:
|
52
|
+
fx1 = spectrum1[0]
|
53
|
+
fy1 = spectrum1[1][0]
|
54
|
+
fx2 = spectrum2[0]
|
55
|
+
fy2 = spectrum2[1][0]
|
56
|
+
|
57
|
+
# Convert to numpy arrays
|
58
|
+
x1: ndarray[Any, dtype[Any]] = array(fx1)
|
59
|
+
x2: ndarray[Any, dtype[Any]] = array(fx2)
|
60
|
+
y1: ndarray[Any, dtype[Any]] = array(fy1)
|
61
|
+
y2: ndarray[Any, dtype[Any]] = array(fy2)
|
62
|
+
|
63
|
+
# Sort spectra while maintaining x-y correspondence
|
64
|
+
sort1 = argsort(x1)
|
65
|
+
sort2 = argsort(x2)
|
66
|
+
|
67
|
+
# Create sorted views of both arrays
|
68
|
+
x1_sorted = x1[sort1]
|
69
|
+
y1_sorted = y1[sort1]
|
70
|
+
x2_sorted = x2[sort2]
|
71
|
+
y2_sorted = y2[sort2]
|
72
|
+
|
73
|
+
# Calculate true overlap region
|
74
|
+
x1_min, x1_max = x1_sorted[0], x1_sorted[-1]
|
75
|
+
x2_min, x2_max = x2_sorted[0], x2_sorted[-1]
|
76
|
+
|
77
|
+
overlap_start = max(x1_min, x2_min)
|
78
|
+
overlap_end = min(x1_max, x2_max)
|
79
|
+
|
80
|
+
logger.debug(f'Spectrum 1 range: {x1_min} to {x1_max}')
|
81
|
+
logger.debug(f'Spectrum 2 range: {x2_min} to {x2_max}')
|
82
|
+
logger.debug(f'Overlap region: {overlap_start} to {overlap_end}')
|
83
|
+
|
84
|
+
if overlap_start >= overlap_end:
|
85
|
+
logger.error(f'No overlap between spectra: [{x1_min}, {x1_max}] and [{x2_min}, {x2_max}]')
|
86
|
+
raise Exception('No overlapping region between spectra')
|
87
|
+
|
88
|
+
# Create masks for overlapping regions using sorted arrays
|
89
|
+
mask1 = (x1_sorted >= overlap_start) & (x1_sorted <= overlap_end)
|
90
|
+
mask2 = (x2_sorted >= overlap_start) & (x2_sorted <= overlap_end)
|
91
|
+
|
92
|
+
# Interpolate second spectrum onto first spectrum's x points in overlap region
|
93
|
+
y2_interp = interp(x1_sorted[mask1], x2_sorted[mask2], y2_sorted[mask2])
|
94
|
+
|
95
|
+
# Average the overlapping region
|
96
|
+
y_combined_overlap = (y1_sorted[mask1] + y2_interp) / 2
|
97
|
+
|
98
|
+
# Combine non-overlapping and overlapping regions
|
99
|
+
x_combined = concatenate((x1_sorted[~mask1], x1_sorted[mask1], x2_sorted[~mask2]))
|
100
|
+
y_combined = concatenate((y1_sorted[~mask1], y_combined_overlap, y2_sorted[~mask2]))
|
101
|
+
|
102
|
+
# Ensure final result is sorted
|
103
|
+
sort_indices = argsort(x_combined)
|
104
|
+
x_combined = x_combined[sort_indices]
|
105
|
+
y_combined = y_combined[sort_indices]
|
106
|
+
|
107
|
+
return [x_combined.tolist(), [y_combined.tolist()]]
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from typing import Any, List
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
from numpy import array, concatenate, dtype, ndarray
|
5
|
+
from overrides import override
|
6
|
+
|
7
|
+
from horiba_sdk.core.stitching.spectra_stitch import SpectraStitch
|
8
|
+
|
9
|
+
|
10
|
+
class SimpleCutSpectraStitch(SpectraStitch):
|
11
|
+
"""Stitches a list of spectra by always keeping the values from the next spectrum in the overlap region.
|
12
|
+
|
13
|
+
.. warning:: Produces a stitched spectrum with stairs
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, spectra_list: List[List[List[float]]]) -> None:
|
17
|
+
"""Constructs a linear stitch of spectra.
|
18
|
+
|
19
|
+
.. warning:: The spectra in the list must overlap
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
spectra_list : List[List[List[float]]] List of spectra to stitch in the form [[x1_values, y1_values],
|
23
|
+
[x2_values, y2_values], etc].
|
24
|
+
"""
|
25
|
+
stitched_spectrum = spectra_list[0]
|
26
|
+
|
27
|
+
for i in range(1, len(spectra_list)):
|
28
|
+
stitched_spectrum = self._stitch_spectra(stitched_spectrum, spectra_list[i])
|
29
|
+
|
30
|
+
self._stitched_spectrum: List[List[float]] = stitched_spectrum
|
31
|
+
|
32
|
+
@override
|
33
|
+
def stitch_with(self, other_stitch: SpectraStitch) -> SpectraStitch:
|
34
|
+
"""Stitches this stitch with another stitch.
|
35
|
+
|
36
|
+
Parameters
|
37
|
+
other_stitch : SpectraStitch The other stitch to stitch with
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
SpectraStitch: The stitched spectra.
|
41
|
+
"""
|
42
|
+
new_stitch = SimpleCutSpectraStitch([self.stitched_spectra(), other_stitch.stitched_spectra()])
|
43
|
+
return new_stitch
|
44
|
+
|
45
|
+
@override
|
46
|
+
def stitched_spectra(self) -> Any:
|
47
|
+
"""Returns the raw data of the stitched spectra.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Any: The stitched spectra.
|
51
|
+
"""
|
52
|
+
return self._stitched_spectrum
|
53
|
+
|
54
|
+
def _stitch_spectra(self, spectrum1: List[List[float]], spectrum2: List[List[float]]) -> List[List[float]]:
|
55
|
+
fx1, fy1 = spectrum1
|
56
|
+
fx2, fy2 = spectrum2
|
57
|
+
|
58
|
+
x1: ndarray[Any, dtype[Any]] = array(fx1)
|
59
|
+
x2: ndarray[Any, dtype[Any]] = array(fx2)
|
60
|
+
y1: ndarray[Any, dtype[Any]] = array(fy1)
|
61
|
+
y2: ndarray[Any, dtype[Any]] = array(fy2)
|
62
|
+
|
63
|
+
overlap_start = max(x1[0], x2[0])
|
64
|
+
overlap_end = min(x1[-1], x2[-1])
|
65
|
+
|
66
|
+
if overlap_start >= overlap_end:
|
67
|
+
logger.error(f'No overlap between two spectra: {spectrum1}, {spectrum2}')
|
68
|
+
raise Exception('No overlapping region between spectra')
|
69
|
+
|
70
|
+
mask2 = (x2 >= overlap_start) & (x2 <= overlap_end)
|
71
|
+
|
72
|
+
x2_overlap = x2[mask2]
|
73
|
+
y2_overlap = y2[mask2]
|
74
|
+
|
75
|
+
x1_before_overlap = x1[x1 < overlap_start]
|
76
|
+
y1_before_overlap = y1[x1 < overlap_start]
|
77
|
+
|
78
|
+
x2_after_overlap = x2[x2 > overlap_end]
|
79
|
+
y2_after_overlap = y2[x2 > overlap_end]
|
80
|
+
|
81
|
+
x_stitched = concatenate([x1_before_overlap, x2_overlap, x2_after_overlap])
|
82
|
+
y_stitched = concatenate([y1_before_overlap, y2_overlap, y2_after_overlap])
|
83
|
+
|
84
|
+
return [x_stitched.tolist(), y_stitched.tolist()]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
class SpectraStitch(ABC):
|
6
|
+
"""Stitches multiple spectra into a single spectrum."""
|
7
|
+
|
8
|
+
@abstractmethod
|
9
|
+
def stitch_with(self, other_stitch: 'SpectraStitch') -> 'SpectraStitch':
|
10
|
+
"""Stitches this stitch with another stitch."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
@abstractmethod
|
14
|
+
def stitched_spectra(self) -> Any:
|
15
|
+
"""Returns the raw data of the stitched spectra."""
|
16
|
+
pass
|
@@ -0,0 +1,87 @@
|
|
1
|
+
from typing import Any, List
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
from numpy import array, concatenate, dtype, ndarray
|
5
|
+
from overrides import override
|
6
|
+
|
7
|
+
from horiba_sdk.core.stitching.spectra_stitch import SpectraStitch
|
8
|
+
|
9
|
+
|
10
|
+
class YDisplacementSpectraStitch(SpectraStitch):
|
11
|
+
"""Stiches a list of spectra using a linear model"""
|
12
|
+
|
13
|
+
def __init__(self, spectrum1: List[List[float]], spectrum2: List[List[float]], y_displacement_count: int) -> None:
|
14
|
+
"""Constructs a linear stitch of spectra.
|
15
|
+
|
16
|
+
.. warning:: The spectra in the list must overlap
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
spectra_list : List[List[List[float]]] List of spectra to stitch in the form [[x1_values, y1_values],
|
20
|
+
[x2_values, y2_values], etc].
|
21
|
+
y_displacement_count : int The amount of displacement in the y direction for the second spectrum
|
22
|
+
"""
|
23
|
+
self._y_displacement_count = y_displacement_count
|
24
|
+
stitched_spectrum = self._stitch_spectra(spectrum1, spectrum2)
|
25
|
+
|
26
|
+
self._stitched_spectrum: List[List[float]] = stitched_spectrum
|
27
|
+
|
28
|
+
@override
|
29
|
+
def stitch_with(self, other_stitch: SpectraStitch) -> SpectraStitch:
|
30
|
+
"""Stitches this stitch with another stitch.
|
31
|
+
|
32
|
+
Parameters
|
33
|
+
other_stitch : SpectraStitch The other stitch to stitch with
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
SpectraStitch: The stitched spectra.
|
37
|
+
"""
|
38
|
+
new_stitch = YDisplacementSpectraStitch([self.stitched_spectra(), other_stitch.stitched_spectra()])
|
39
|
+
return new_stitch
|
40
|
+
|
41
|
+
@override
|
42
|
+
def stitched_spectra(self) -> Any:
|
43
|
+
"""Returns the raw data of the stitched spectra.
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
Any: The stitched spectra.
|
47
|
+
"""
|
48
|
+
return self._stitched_spectrum
|
49
|
+
|
50
|
+
def _stitch_spectra(self, spectrum1: List[List[float]], spectrum2: List[List[float]]) -> List[List[float]]:
|
51
|
+
fx1, fy1 = spectrum1
|
52
|
+
fx2, fy2 = spectrum2
|
53
|
+
|
54
|
+
x1: ndarray[Any, dtype[Any]] = array(fx1)
|
55
|
+
x2: ndarray[Any, dtype[Any]] = array(fx2)
|
56
|
+
y1: ndarray[Any, dtype[Any]] = array(fy1)
|
57
|
+
y2: ndarray[Any, dtype[Any]] = array(fy2)
|
58
|
+
|
59
|
+
overlap_start = max(x1[0], x2[0])
|
60
|
+
overlap_end = min(x1[-1], x2[-1])
|
61
|
+
|
62
|
+
if overlap_start >= overlap_end:
|
63
|
+
logger.error(f'No overlap between two spectra: {spectrum1}, {spectrum2}')
|
64
|
+
raise Exception('No overlapping region between spectra')
|
65
|
+
|
66
|
+
# Masks for overlapping region
|
67
|
+
mask1 = (x1 >= overlap_start) & (x1 <= overlap_end)
|
68
|
+
mask2 = (x2 >= overlap_start) & (x2 <= overlap_end)
|
69
|
+
|
70
|
+
x1_overlap = x1[mask1]
|
71
|
+
y1_overlap = y1[mask1]
|
72
|
+
|
73
|
+
y2_displaced = y2 + self._y_displacement_count
|
74
|
+
y2_overlap = y2_displaced[mask2]
|
75
|
+
|
76
|
+
y_stitched_overlap = (y1_overlap + y2_overlap) / 2
|
77
|
+
|
78
|
+
x1_before_overlap = x1[x1 < overlap_start]
|
79
|
+
y1_before_overlap = y1[x1 < overlap_start]
|
80
|
+
|
81
|
+
x2_after_overlap = x2[x2 > overlap_end]
|
82
|
+
y2_after_overlap = y2_displaced[x2 > overlap_end]
|
83
|
+
|
84
|
+
x_stitched = concatenate([x1_before_overlap, x1_overlap, x2_after_overlap])
|
85
|
+
y_stitched = concatenate([y1_before_overlap, y_stitched_overlap, y2_after_overlap])
|
86
|
+
|
87
|
+
return [x_stitched.tolist(), y_stitched.tolist()]
|
@@ -50,7 +50,8 @@ from horiba_sdk.communication import (
|
|
50
50
|
from horiba_sdk.devices import AbstractDeviceManager
|
51
51
|
from horiba_sdk.devices.ccd_discovery import ChargeCoupledDevicesDiscovery
|
52
52
|
from horiba_sdk.devices.monochromator_discovery import MonochromatorsDiscovery
|
53
|
-
from horiba_sdk.devices.single_devices import ChargeCoupledDevice, Monochromator
|
53
|
+
from horiba_sdk.devices.single_devices import ChargeCoupledDevice, Monochromator, SpectrAcq3
|
54
|
+
from horiba_sdk.devices.spectracq3_discovery import SpectrAcq3Discovery
|
54
55
|
from horiba_sdk.icl_error import AbstractError, AbstractErrorDB, ICLErrorDB
|
55
56
|
|
56
57
|
|
@@ -66,7 +67,7 @@ class DeviceManager(AbstractDeviceManager):
|
|
66
67
|
icl_ip: str = '127.0.0.1',
|
67
68
|
icl_port: str = '25010',
|
68
69
|
enable_binary_messages: bool = True,
|
69
|
-
enable_logging: bool = False
|
70
|
+
enable_logging: bool = False,
|
70
71
|
):
|
71
72
|
"""
|
72
73
|
Initializes the DeviceManager with the specified communicator class.
|
@@ -81,7 +82,7 @@ class DeviceManager(AbstractDeviceManager):
|
|
81
82
|
# By default, logging is disabled for a library, so if desired it can be enabled
|
82
83
|
root_name_space: str = __name__.split('.')[0]
|
83
84
|
if enable_logging:
|
84
|
-
logger.info(f
|
85
|
+
logger.info(f'Initializing logger for namespace: {root_name_space}')
|
85
86
|
logger.enable(root_name_space)
|
86
87
|
else:
|
87
88
|
logger.disable(root_name_space)
|
@@ -95,6 +96,7 @@ class DeviceManager(AbstractDeviceManager):
|
|
95
96
|
self._binary_messages: bool = enable_binary_messages
|
96
97
|
self._charge_coupled_devices: list[ChargeCoupledDevice] = []
|
97
98
|
self._monochromators: list[Monochromator] = []
|
99
|
+
self._spectracq3_devices: list[SpectrAcq3] = []
|
98
100
|
|
99
101
|
error_list_path: Path = Path(str(importlib.resources.files('horiba_sdk.icl_error') / 'error_list.json'))
|
100
102
|
self._icl_error_db: AbstractErrorDB = ICLErrorDB(error_list_path)
|
@@ -230,6 +232,10 @@ class DeviceManager(AbstractDeviceManager):
|
|
230
232
|
await monochromators_discovery.execute(error_on_no_device)
|
231
233
|
self._monochromators = monochromators_discovery.monochromators()
|
232
234
|
|
235
|
+
spectracq3_discovery: SpectrAcq3Discovery = SpectrAcq3Discovery(self._icl_communicator, self._icl_error_db)
|
236
|
+
await spectracq3_discovery.execute(error_on_no_device)
|
237
|
+
self._spectracq3_devices = spectracq3_discovery.spectracq3_devices()
|
238
|
+
|
233
239
|
@property
|
234
240
|
@override
|
235
241
|
def communicator(self) -> AbstractCommunicator:
|
@@ -262,3 +268,13 @@ class DeviceManager(AbstractDeviceManager):
|
|
262
268
|
List[ChargeCoupledDevice]: The detected CCDS.
|
263
269
|
"""
|
264
270
|
return self._charge_coupled_devices
|
271
|
+
|
272
|
+
@property
|
273
|
+
def spectracq3_devices(self) -> list[SpectrAcq3]:
|
274
|
+
"""
|
275
|
+
The detected SpectrAcq3 devices, should be called after :meth:`discover_devices`
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
List[SpectrAcq3]: The detected SpectrAcq3 devices.
|
279
|
+
"""
|
280
|
+
return self._spectracq3_devices
|
@@ -26,6 +26,10 @@ class FakeICLServer:
|
|
26
26
|
with open(ccd_fake_responses_path) as json_file:
|
27
27
|
self.ccd_responses = json.load(json_file)
|
28
28
|
|
29
|
+
spectracq3_fake_responses_path = os.path.join(fake_responses_path, 'spectracq3.json')
|
30
|
+
with open(spectracq3_fake_responses_path) as json_file:
|
31
|
+
self.spectracq3_responses = json.load(json_file)
|
32
|
+
|
29
33
|
async def echo(self, websocket):
|
30
34
|
async for message in websocket:
|
31
35
|
logger.info('received: {message}', message=message)
|
@@ -43,6 +47,9 @@ class FakeICLServer:
|
|
43
47
|
elif command['command'].startswith('ccd_'):
|
44
48
|
response = json.dumps(self.ccd_responses[command['command']])
|
45
49
|
await websocket.send(response)
|
50
|
+
elif command['command'].startswith('saq3_'):
|
51
|
+
response = json.dumps(self.spectracq3_responses[command['command']])
|
52
|
+
await websocket.send(response)
|
46
53
|
else:
|
47
54
|
logger.info('unknown command, responding with message')
|
48
55
|
await websocket.send(message)
|