horiba-sdk 0.3.2__py3-none-any.whl
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/__init__.py +19 -0
- horiba_sdk/communication/__init__.py +44 -0
- horiba_sdk/communication/abstract_communicator.py +59 -0
- horiba_sdk/communication/communication_exception.py +19 -0
- horiba_sdk/communication/messages.py +87 -0
- horiba_sdk/communication/websocket_communicator.py +213 -0
- horiba_sdk/core/resolution.py +45 -0
- horiba_sdk/devices/__init__.py +11 -0
- horiba_sdk/devices/abstract_device_discovery.py +7 -0
- horiba_sdk/devices/abstract_device_manager.py +68 -0
- horiba_sdk/devices/ccd_discovery.py +57 -0
- horiba_sdk/devices/device_manager.py +250 -0
- horiba_sdk/devices/fake_device_manager.py +133 -0
- horiba_sdk/devices/fake_icl_server.py +56 -0
- horiba_sdk/devices/fake_responses/ccd.json +168 -0
- horiba_sdk/devices/fake_responses/icl.json +29 -0
- horiba_sdk/devices/fake_responses/monochromator.json +187 -0
- horiba_sdk/devices/monochromator_discovery.py +48 -0
- horiba_sdk/devices/single_devices/__init__.py +5 -0
- horiba_sdk/devices/single_devices/abstract_device.py +79 -0
- horiba_sdk/devices/single_devices/ccd.py +443 -0
- horiba_sdk/devices/single_devices/monochromator.py +395 -0
- horiba_sdk/icl_error/__init__.py +34 -0
- horiba_sdk/icl_error/abstract_error.py +65 -0
- horiba_sdk/icl_error/abstract_error_db.py +25 -0
- horiba_sdk/icl_error/error_list.json +265 -0
- horiba_sdk/icl_error/icl_error.py +30 -0
- horiba_sdk/icl_error/icl_error_db.py +81 -0
- horiba_sdk/sync/__init__.py +0 -0
- horiba_sdk/sync/communication/__init__.py +7 -0
- horiba_sdk/sync/communication/abstract_communicator.py +48 -0
- horiba_sdk/sync/communication/test_client.py +16 -0
- horiba_sdk/sync/communication/websocket_communicator.py +212 -0
- horiba_sdk/sync/devices/__init__.py +15 -0
- horiba_sdk/sync/devices/abstract_device_discovery.py +17 -0
- horiba_sdk/sync/devices/abstract_device_manager.py +68 -0
- horiba_sdk/sync/devices/device_discovery.py +82 -0
- horiba_sdk/sync/devices/device_manager.py +209 -0
- horiba_sdk/sync/devices/fake_device_manager.py +91 -0
- horiba_sdk/sync/devices/fake_icl_server.py +79 -0
- horiba_sdk/sync/devices/single_devices/__init__.py +5 -0
- horiba_sdk/sync/devices/single_devices/abstract_device.py +83 -0
- horiba_sdk/sync/devices/single_devices/ccd.py +219 -0
- horiba_sdk/sync/devices/single_devices/monochromator.py +150 -0
- horiba_sdk-0.3.2.dist-info/LICENSE +20 -0
- horiba_sdk-0.3.2.dist-info/METADATA +438 -0
- horiba_sdk-0.3.2.dist-info/RECORD +48 -0
- horiba_sdk-0.3.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from types import TracebackType
|
3
|
+
from typing import Any, Optional, Union, final
|
4
|
+
|
5
|
+
import pint
|
6
|
+
from loguru import logger
|
7
|
+
from overrides import override
|
8
|
+
|
9
|
+
from horiba_sdk import ureg
|
10
|
+
from horiba_sdk.communication import Response
|
11
|
+
from horiba_sdk.core.resolution import Resolution
|
12
|
+
from horiba_sdk.icl_error import AbstractErrorDB
|
13
|
+
from horiba_sdk.sync.communication.abstract_communicator import AbstractCommunicator
|
14
|
+
from horiba_sdk.sync.devices.single_devices.abstract_device import AbstractDevice
|
15
|
+
|
16
|
+
|
17
|
+
@final
|
18
|
+
class ChargeCoupledDevice(AbstractDevice):
|
19
|
+
"""Charge Coupled Device
|
20
|
+
|
21
|
+
This class should not be instanced by the end user. Instead, the :class:`horiba_sdk.devices.DeviceManager`
|
22
|
+
should be used to access the detected CCDs on the system.
|
23
|
+
"""
|
24
|
+
|
25
|
+
@final
|
26
|
+
class XAxisConversionType(Enum):
|
27
|
+
NONE = 0
|
28
|
+
FROM_CCD_FIRMWARE = 1
|
29
|
+
FROM_ICL_SETTINGS_INI = 2
|
30
|
+
|
31
|
+
def __init__(self, device_id: int, communicator: AbstractCommunicator, error_db: AbstractErrorDB) -> None:
|
32
|
+
super().__init__(device_id, communicator, error_db)
|
33
|
+
|
34
|
+
def __enter__(self) -> 'ChargeCoupledDevice':
|
35
|
+
self.open()
|
36
|
+
return self
|
37
|
+
|
38
|
+
def __exit__(
|
39
|
+
self,
|
40
|
+
exc_type: Optional[type[BaseException]],
|
41
|
+
exc_value: Optional[BaseException],
|
42
|
+
traceback: Optional[TracebackType],
|
43
|
+
) -> None:
|
44
|
+
is_open = self.is_open()
|
45
|
+
if not is_open:
|
46
|
+
logger.debug('CCD is already closed')
|
47
|
+
return
|
48
|
+
|
49
|
+
self.close()
|
50
|
+
|
51
|
+
@override
|
52
|
+
def open(self) -> None:
|
53
|
+
"""Opens the connection to the Charge Coupled Device
|
54
|
+
|
55
|
+
Raises:
|
56
|
+
Exception: When an error occurred on the device side
|
57
|
+
"""
|
58
|
+
super().open()
|
59
|
+
super()._execute_command('ccd_open', {'index': self._id})
|
60
|
+
|
61
|
+
@override
|
62
|
+
def close(self) -> None:
|
63
|
+
"""Closes the connection to the ChargeCoupledDevice
|
64
|
+
|
65
|
+
Raises:
|
66
|
+
Exception: When an error occurred on the device side
|
67
|
+
"""
|
68
|
+
super()._execute_command('ccd_close', {'index': self._id})
|
69
|
+
|
70
|
+
def is_open(self) -> bool:
|
71
|
+
"""Checks if the connection to the charge coupled device is open.
|
72
|
+
|
73
|
+
Raises:
|
74
|
+
Exception: When an error occurred on the device side
|
75
|
+
"""
|
76
|
+
response: Response = super()._execute_command('ccd_isOpen', {'index': self._id})
|
77
|
+
return bool(response.results['open'])
|
78
|
+
|
79
|
+
def get_temperature(self) -> pint.Quantity:
|
80
|
+
"""Chip temperature of the CCD.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
pint.Quantity: chip's temperature in degree Celsius
|
84
|
+
|
85
|
+
Raises:
|
86
|
+
Exception: When an error occurred on the device side
|
87
|
+
"""
|
88
|
+
response: Response = super()._execute_command('ccd_getChipTemperature', {'index': self._id})
|
89
|
+
return ureg.Quantity(response.results['temperature'], ureg.degC) # type: ignore
|
90
|
+
|
91
|
+
def get_chip_size(self) -> Resolution:
|
92
|
+
"""Chip resolution of the CCD.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Resolution: chip resolution (width, height)
|
96
|
+
|
97
|
+
Raises:
|
98
|
+
Exception: When an error occurred on the device side
|
99
|
+
"""
|
100
|
+
response: Response = super()._execute_command('ccd_getChipSize', {'index': self._id})
|
101
|
+
width: int = response.results['x']
|
102
|
+
height: int = response.results['y']
|
103
|
+
resolution: Resolution = Resolution(width, height)
|
104
|
+
return resolution
|
105
|
+
|
106
|
+
def get_speed(self) -> Union[pint.Quantity, None]:
|
107
|
+
"""Chip transfer speed in kHz
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
pint.Quantity: Transfer speed in kilo Hertz
|
111
|
+
|
112
|
+
Raises:
|
113
|
+
Exception: When an error occurred on the device side
|
114
|
+
"""
|
115
|
+
response: Response = super()._execute_command('ccd_getSpeed', {'index': self._id})
|
116
|
+
return ureg(response.results['info'])
|
117
|
+
|
118
|
+
def get_exposure_time(self) -> Union[pint.Quantity, None]:
|
119
|
+
"""Returns the exposure time in ms
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
pint.Quantity: Exposure time in ms
|
123
|
+
Raises:
|
124
|
+
Exception: When an error occurred on the device side
|
125
|
+
"""
|
126
|
+
response: Response = super()._execute_command('ccd_getExposureTime', {'index': self._id})
|
127
|
+
exposure = ureg.Quantity(response.results['time'], 'ms')
|
128
|
+
return exposure
|
129
|
+
|
130
|
+
def set_exposure_time(self, exposure_time_ms: int) -> None:
|
131
|
+
"""Sets the exposure time in ms
|
132
|
+
|
133
|
+
Args:
|
134
|
+
exposure_time_ms (int): Exposure time in ms
|
135
|
+
Raises:
|
136
|
+
Exception: When an error occurred on the device side
|
137
|
+
"""
|
138
|
+
|
139
|
+
super()._execute_command('ccd_setExposureTime', {'index': self._id, 'time': exposure_time_ms})
|
140
|
+
|
141
|
+
def get_acquisition_ready(self) -> bool:
|
142
|
+
"""Returns true if the CCD is ready to acquire
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
bool: True if the CCD is ready to acquire
|
146
|
+
Raises:
|
147
|
+
Exception: When an error occurred on the device side
|
148
|
+
"""
|
149
|
+
response: Response = super()._execute_command('ccd_getAcquisitionReady', {'index': self._id})
|
150
|
+
return bool(response.results['ready'])
|
151
|
+
|
152
|
+
def set_acquisition_start(self, open_shutter: bool) -> None:
|
153
|
+
"""Starts the acquisition of the CCD
|
154
|
+
|
155
|
+
Args:
|
156
|
+
open_shutter (bool): Whether the shutter of the camera should be open
|
157
|
+
Raises:
|
158
|
+
Exception: When an error occurred on the device side
|
159
|
+
"""
|
160
|
+
super()._execute_command('ccd_setAcquisitionStart', {'index': self._id, 'openShutter': open_shutter})
|
161
|
+
|
162
|
+
def set_region_of_interest(
|
163
|
+
self,
|
164
|
+
roi_index: int = 1,
|
165
|
+
x_origin: int = 0,
|
166
|
+
y_origin: int = 0,
|
167
|
+
x_size: int = 1024,
|
168
|
+
y_size: int = 256,
|
169
|
+
x_bin: int = 1,
|
170
|
+
y_bin: int = 256,
|
171
|
+
) -> None:
|
172
|
+
"""Sets the region of interest of the CCD
|
173
|
+
an example json command looks like this:
|
174
|
+
|
175
|
+
Args:
|
176
|
+
roi_index (int, optional): Index of the region of interest. Defaults to 1.
|
177
|
+
x_origin (int, optional): X origin of the region of interest. Defaults to 0.
|
178
|
+
y_origin (int, optional): Y origin of the region of interest. Defaults to 0.
|
179
|
+
x_size (int, optional): X size of the region of interest. Defaults to 1024.
|
180
|
+
y_size (int, optional): Y size of the region of interest. Defaults to 256.
|
181
|
+
x_bin (int, optional): X bin of the region of interest. Defaults to 1.
|
182
|
+
y_bin (int, optional): Y bin of the region of interest. Defaults to 256.
|
183
|
+
|
184
|
+
Raises:
|
185
|
+
Exception: When an error occurred on the device side
|
186
|
+
"""
|
187
|
+
super()._execute_command(
|
188
|
+
'ccd_setRoi',
|
189
|
+
{
|
190
|
+
'index': self._id,
|
191
|
+
'roiIndex': roi_index,
|
192
|
+
'xOrigin': x_origin,
|
193
|
+
'yOrigin': y_origin,
|
194
|
+
'xSize': x_size,
|
195
|
+
'ySize': y_size,
|
196
|
+
'xBin': x_bin,
|
197
|
+
'yBin': y_bin,
|
198
|
+
},
|
199
|
+
)
|
200
|
+
|
201
|
+
def get_acquisition_data(self) -> dict[Any, Any]:
|
202
|
+
"""Returns the acquisition data of the CCD
|
203
|
+
nina: atm this returns data still formatted for telnet communication, not formatted as json"""
|
204
|
+
response: Response = super()._execute_command('ccd_getAcquisitionData', {'index': self._id})
|
205
|
+
return response.results
|
206
|
+
|
207
|
+
def get_acquisition_busy(self) -> bool:
|
208
|
+
"""Returns true if the CCD is busy with the acquisition"""
|
209
|
+
response: Response = super()._execute_command('ccd_getAcquisitionBusy', {'index': self._id})
|
210
|
+
return bool(response.results['isBusy'])
|
211
|
+
|
212
|
+
def set_x_axis_conversion_type(self, conversion_type: XAxisConversionType) -> None:
|
213
|
+
"""Sets the conversion type of the x axis"""
|
214
|
+
super()._execute_command('ccd_setXAxisConversionType', {'index': self._id, 'type': conversion_type.value})
|
215
|
+
|
216
|
+
def get_x_axis_conversion_type(self) -> XAxisConversionType:
|
217
|
+
"""Gets the conversion type of the x axis"""
|
218
|
+
response: Response = super()._execute_command('ccd_getXAxisConversionType', {'index': self._id})
|
219
|
+
return self.XAxisConversionType(response.results['type'])
|
@@ -0,0 +1,150 @@
|
|
1
|
+
from types import TracebackType
|
2
|
+
from typing import Optional, final
|
3
|
+
|
4
|
+
from loguru import logger
|
5
|
+
from numericalunits import nm
|
6
|
+
from overrides import override
|
7
|
+
|
8
|
+
from horiba_sdk.communication import Response
|
9
|
+
from horiba_sdk.icl_error import AbstractErrorDB
|
10
|
+
from horiba_sdk.sync.communication.abstract_communicator import AbstractCommunicator
|
11
|
+
from horiba_sdk.sync.devices.single_devices.abstract_device import AbstractDevice
|
12
|
+
|
13
|
+
|
14
|
+
@final
|
15
|
+
class Monochromator(AbstractDevice):
|
16
|
+
"""Monochromator device
|
17
|
+
|
18
|
+
This class should not be instanced by the end user. Instead, the :class:`horiba_sdk.sync.devices.DeviceManager`
|
19
|
+
should be used to access the detected Monochromators on the system.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, device_id: int, communicator: AbstractCommunicator, error_db: AbstractErrorDB) -> None:
|
23
|
+
super().__init__(device_id, communicator, error_db)
|
24
|
+
|
25
|
+
def __enter__(self) -> 'Monochromator':
|
26
|
+
self.open()
|
27
|
+
return self
|
28
|
+
|
29
|
+
def __exit__(
|
30
|
+
self,
|
31
|
+
exc_type: Optional[type[BaseException]],
|
32
|
+
exc_value: Optional[BaseException],
|
33
|
+
traceback: Optional[TracebackType],
|
34
|
+
) -> None:
|
35
|
+
if not self.is_open():
|
36
|
+
logger.debug('Monochromator is already closed')
|
37
|
+
return
|
38
|
+
|
39
|
+
self.close()
|
40
|
+
|
41
|
+
@override
|
42
|
+
def open(self) -> None:
|
43
|
+
"""Opens the connection to the Monochromator
|
44
|
+
|
45
|
+
Raises:
|
46
|
+
Exception: When an error occured on the device side
|
47
|
+
"""
|
48
|
+
super().open()
|
49
|
+
super()._execute_command('mono_open', {'index': self._id}, 0.5)
|
50
|
+
|
51
|
+
@override
|
52
|
+
def close(self) -> None:
|
53
|
+
"""Closes the connection to the Monochromator
|
54
|
+
|
55
|
+
Raises:
|
56
|
+
Exception: When an error occured on the device side
|
57
|
+
"""
|
58
|
+
super()._execute_command('mono_close', {'index': self._id})
|
59
|
+
|
60
|
+
def is_open(self) -> bool:
|
61
|
+
"""Checks if the connection to the monochromator is open.
|
62
|
+
|
63
|
+
Raises:
|
64
|
+
Exception: When an error occured on the device side
|
65
|
+
"""
|
66
|
+
response: Response = super()._execute_command('mono_isOpen', {'index': self._id})
|
67
|
+
return bool(response.results['open'])
|
68
|
+
|
69
|
+
@property
|
70
|
+
def is_busy(self) -> bool:
|
71
|
+
"""Checks if the monochromator is busy.
|
72
|
+
|
73
|
+
Raises:
|
74
|
+
Exception: When an error occured on the device side
|
75
|
+
"""
|
76
|
+
response: Response = super()._execute_command('mono_isBusy', {'index': self._id})
|
77
|
+
return bool(response.results['busy'])
|
78
|
+
|
79
|
+
def home(self) -> None:
|
80
|
+
"""Starts the monochromator initialization process called "homing".
|
81
|
+
|
82
|
+
Use :func:`Monochromator.is_busy()` to know if the operation is still taking place.
|
83
|
+
|
84
|
+
Raises:
|
85
|
+
Exception: When an error occured on the device side
|
86
|
+
"""
|
87
|
+
super()._execute_command('mono_init', {'index': self._id})
|
88
|
+
|
89
|
+
@property
|
90
|
+
def wavelength(self) -> nm:
|
91
|
+
"""Current wavelength of the monochromator's position in nm.
|
92
|
+
|
93
|
+
Raises:
|
94
|
+
Exception: When an error occured on the device side
|
95
|
+
"""
|
96
|
+
response = super()._execute_command('mono_getPosition', {'index': self._id})
|
97
|
+
return float(response.results['wavelength']) * nm
|
98
|
+
|
99
|
+
def set_current_wavelength(self, wavelength: int) -> None:
|
100
|
+
"""This command sets the wavelength value of the current grating position of the monochromator.
|
101
|
+
|
102
|
+
.. warning:: This could potentially uncalibrate the monochromator and report an incorrect wavelength compared to
|
103
|
+
the actual output wavelength.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
wavelength (nm): wavelength
|
107
|
+
|
108
|
+
Raises:
|
109
|
+
Exception: When an error occured on the device side
|
110
|
+
"""
|
111
|
+
super()._execute_command('mono_setPosition', {'index': self._id, 'wavelength': wavelength})
|
112
|
+
|
113
|
+
def move_to_wavelength(self, wavelength: nm) -> None:
|
114
|
+
"""Orders the monochromator to move to the requested wavelength.
|
115
|
+
|
116
|
+
Use :func:`Monochromator.is_busy()` to know if the operation is still taking place.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
wavelength (nm): wavelength
|
120
|
+
|
121
|
+
Raises:
|
122
|
+
Exception: When an error occured on the device side
|
123
|
+
"""
|
124
|
+
super()._execute_command('mono_moveToPosition', {'index': self._id, 'wavelength': wavelength / nm})
|
125
|
+
|
126
|
+
@property
|
127
|
+
def turret_grating_position(self) -> int:
|
128
|
+
"""Grating turret position.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
int: current grating turret position
|
132
|
+
|
133
|
+
Raises:
|
134
|
+
Exception: When an error occured on the device side
|
135
|
+
"""
|
136
|
+
response: Response = super()._execute_command('mono_getGratingPosition', {'index': self._id})
|
137
|
+
return int(response.results['position'])
|
138
|
+
|
139
|
+
def move_turret_to_grating(self, position: int) -> None:
|
140
|
+
"""Move turret to grating position
|
141
|
+
|
142
|
+
.. todo:: Get more information about how it works and clarify veracity of returned data
|
143
|
+
|
144
|
+
Args:
|
145
|
+
position (int): new grating position
|
146
|
+
|
147
|
+
Raises:
|
148
|
+
Exception: When an error occured on the device side
|
149
|
+
"""
|
150
|
+
super()._execute_command('mono_getPosition', {'index': self._id, 'position': position})
|
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2023 ZühlkeEngineering
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
20
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|