horiba-sdk 0.3.3__py3-none-any.whl → 0.5.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.
Files changed (27) hide show
  1. horiba_sdk/__init__.py +5 -3
  2. horiba_sdk/communication/websocket_communicator.py +2 -2
  3. horiba_sdk/core/__init__.py +0 -0
  4. horiba_sdk/core/acquisition_format.py +20 -0
  5. horiba_sdk/core/clean_count_mode.py +13 -0
  6. horiba_sdk/core/timer_resolution.py +14 -0
  7. horiba_sdk/core/x_axis_conversion_type.py +18 -0
  8. horiba_sdk/devices/ccd_discovery.py +10 -12
  9. horiba_sdk/devices/device_manager.py +24 -10
  10. horiba_sdk/devices/fake_responses/ccd.json +261 -12
  11. horiba_sdk/devices/fake_responses/monochromator.json +38 -10
  12. horiba_sdk/devices/monochromator_discovery.py +16 -9
  13. horiba_sdk/devices/single_devices/abstract_device.py +46 -1
  14. horiba_sdk/devices/single_devices/ccd.py +388 -143
  15. horiba_sdk/devices/single_devices/monochromator.py +87 -71
  16. horiba_sdk/sync/communication/abstract_communicator.py +2 -3
  17. horiba_sdk/sync/communication/websocket_communicator.py +38 -18
  18. horiba_sdk/sync/devices/device_discovery.py +13 -37
  19. horiba_sdk/sync/devices/device_manager.py +14 -10
  20. horiba_sdk/sync/devices/fake_icl_server.py +9 -6
  21. horiba_sdk/sync/devices/single_devices/abstract_device.py +11 -7
  22. horiba_sdk/sync/devices/single_devices/ccd.py +517 -62
  23. horiba_sdk/sync/devices/single_devices/monochromator.py +288 -25
  24. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/METADATA +166 -92
  25. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/RECORD +27 -22
  26. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/LICENSE +0 -0
  27. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/WHEEL +0 -0
@@ -1,14 +1,15 @@
1
- from enum import Enum
2
1
  from types import TracebackType
3
2
  from typing import Any, Optional, final
4
3
 
5
- import pint
6
4
  from loguru import logger
7
5
  from overrides import override
8
6
 
9
- from horiba_sdk import ureg
10
7
  from horiba_sdk.communication import AbstractCommunicator, Response
8
+ from horiba_sdk.core.acquisition_format import AcquisitionFormat
9
+ from horiba_sdk.core.clean_count_mode import CleanCountMode
11
10
  from horiba_sdk.core.resolution import Resolution
11
+ from horiba_sdk.core.timer_resolution import TimerResolution
12
+ from horiba_sdk.core.x_axis_conversion_type import XAxisConversionType
12
13
  from horiba_sdk.icl_error import AbstractErrorDB
13
14
 
14
15
  from .abstract_device import AbstractDevice
@@ -22,40 +23,6 @@ class ChargeCoupledDevice(AbstractDevice):
22
23
  should be used to access the detected CCDs on the system.
23
24
  """
24
25
 
25
- @final
26
- class Gain(Enum):
27
- HIGH_LIGHT = 0
28
- BEST_DYNAMIC_RANGE = 1
29
- HIGH_SENSITIVITY = 2
30
-
31
- @final
32
- class Speed(Enum):
33
- SLOW_45_kHz = 0
34
- MEDIUM_1_MHz = 1
35
- FAST_1_MHz_Ultra = 2
36
-
37
- @final
38
- class AcquisitionFormat(Enum):
39
- SPECTRA = 0
40
- IMAGE = 1
41
- CROP = 2
42
- FAST_KINETICS = 3
43
-
44
- @final
45
- class CleanCountMode(Enum):
46
- Mode1 = 238
47
-
48
- @final
49
- class XAxisConversionType(Enum):
50
- """
51
- Enumeration of possible XAxisConversionTypes
52
- None = 0, CCD-Firmware = 1, ICL ini settings file = 2
53
- """
54
-
55
- NONE = 0
56
- FROM_CCD_FIRMWARE = 1
57
- FROM_ICL_SETTINGS_INI = 2
58
-
59
26
  def __init__(self, device_id: int, communicator: AbstractCommunicator, error_db: AbstractErrorDB) -> None:
60
27
  super().__init__(device_id, communicator, error_db)
61
28
 
@@ -82,6 +49,7 @@ class ChargeCoupledDevice(AbstractDevice):
82
49
  """
83
50
  await super().open()
84
51
  await super()._execute_command('ccd_open', {'index': self._id})
52
+ self._config: dict[str, Any] = await self.get_configuration()
85
53
 
86
54
  @override
87
55
  async def close(self) -> None:
@@ -121,131 +89,156 @@ class ChargeCoupledDevice(AbstractDevice):
121
89
  Exception: When an error occurred on the device side
122
90
  """
123
91
  response: Response = await super()._execute_command('ccd_getConfig', {'index': self._id})
124
- return response.results
92
+ return response.results['configuration']
93
+
94
+ async def get_gain_token(self) -> int:
95
+ """Returns the current gain token.
125
96
 
126
- async def get_number_of_averages(self) -> int:
127
- """Returns the number of averages of the CCD
97
+ .. note:: The CCD can have different sensors installed, which can have different gain values. This is why only
98
+ the token to the gain is returned. You need to first check what gain values are available for the CCD using the
99
+ get_configuration function. Please see the according "Gain and Speed" documentation.
128
100
 
129
101
  Returns:
130
- int: Number of averages
102
+ int: Gain token of the ccd
131
103
 
132
104
  Raises:
133
105
  Exception: When an error occurred on the device side
134
106
  """
135
- response: Response = await super()._execute_command('ccd_getNumberOfAvgs', {'index': self._id})
136
- return int(response.results['count'])
107
+ response: Response = await super()._execute_command('ccd_getGain', {'index': self._id})
108
+ gain: int = int(response.results['token'])
109
+ return gain
110
+
111
+ async def set_gain(self, gain_token: int) -> None:
112
+ """Sets the gain of the CCD.
137
113
 
138
- async def set_number_of_averages(self, number_of_averages: int) -> None:
139
- """Sets the number of averages of the CCD
114
+ .. note:: The CCD can have different sensors installed, which can have different gain values. Therefore you need
115
+ to first check what gain values are available for the CCD using the get_configuration function. Please see the
116
+ according "Gain and Speed" documentation.
140
117
 
141
118
  Args:
142
- number_of_averages (int): Number of averages
119
+ gain_token (int): Token of the desired gain
143
120
 
144
121
  Raises:
145
122
  Exception: When an error occurred on the device side
146
123
  """
147
- await super()._execute_command('ccd_setNumberOfAvgs', {'index': self._id, 'count': number_of_averages})
124
+ await super()._execute_command('ccd_setGain', {'index': self._id, 'token': gain_token})
148
125
 
149
- async def get_gain(self) -> str:
150
- """Returns the gain of the CCD
126
+ async def get_speed_token(self) -> int:
127
+ """Returns the speed token.
128
+
129
+ .. note:: The CCD can have different sensors installed, which can have different speed values. This is why only
130
+ the token to the speed is returned. You need to first check what speed values are available for the CCD using
131
+ the get_configuration function. Please see the according "Gain and Speed" documentation.
151
132
 
152
133
  Returns:
153
- str: Gain
134
+ int: Speed token of the CCD.
154
135
 
155
136
  Raises:
156
137
  Exception: When an error occurred on the device side
157
138
  """
158
- response: Response = await super()._execute_command('ccd_getGain', {'index': self._id})
159
- return str(response.results['info'])
139
+ response: Response = await super()._execute_command('ccd_getSpeed', {'index': self._id})
140
+ speed_token: int = int(response.results['token'])
141
+ return speed_token
160
142
 
161
- async def set_gain(self, gain: Gain) -> None:
162
- """Sets the gain of the CCD
143
+ async def set_speed(self, speed_token: int) -> None:
144
+ """Sets the speed of the CCD
145
+
146
+ .. note:: The CCD can have different sensors installed, which can have different speed values. Therefore you
147
+ need to first check what speed values are available for the CCD using the get_configuration function. Please
148
+ see the according "Gain and Speed" documentation.
163
149
 
164
150
  Args:
165
- gain (Gain): Gain
151
+ speed_token (int): Token of the desired speed.
166
152
 
167
153
  Raises:
168
154
  Exception: When an error occurred on the device side
169
155
  """
170
- await super()._execute_command('ccd_setGain', {'index': self._id, 'token': gain.value})
156
+ await super()._execute_command('ccd_setSpeed', {'index': self._id, 'token': speed_token})
171
157
 
172
- async def get_speed(self) -> str:
173
- """Returns the speed of the CCD
158
+ async def get_parallel_speed(self) -> int:
159
+ """Gets the current parallel speed token
174
160
 
175
161
  Returns:
176
- str: Speed
162
+ int: current parallel speed token
177
163
 
178
164
  Raises:
179
165
  Exception: When an error occurred on the device side
180
166
  """
181
- response: Response = await super()._execute_command('ccd_getSpeed', {'index': self._id})
182
- return str(response.results['info'])
167
+ response: Response = await super()._execute_command('ccd_getParallelSpeed', {'index': self._id})
168
+ parallel_speed_token: int = int(response.results['token'])
169
+ return parallel_speed_token
183
170
 
184
- async def set_speed(self, speed: Speed) -> None:
185
- """Sets the speed of the CCD
186
-
187
- Args:
188
- speed (Speed): Speed
171
+ async def set_parallel_speed(self, parallel_speed_token: int) -> None:
172
+ """Sets the desired parallel speed token
189
173
 
190
174
  Raises:
191
175
  Exception: When an error occurred on the device side
192
176
  """
193
- await super()._execute_command('ccd_setSpeed', {'index': self._id, 'token': speed.value})
177
+ await super()._execute_command('ccd_setParallelSpeed', {'index': self._id, 'token': parallel_speed_token})
194
178
 
195
- async def get_fit_params(self) -> str:
179
+ async def get_fit_parameters(self) -> list[int]:
196
180
  """Returns the fit parameters of the CCD
197
181
 
198
182
  Returns:
199
- str: Fit parameters
183
+ List[int]: Fit parameters
200
184
 
201
185
  Raises:
202
186
  Exception: When an error occurred on the device side
203
187
  """
204
188
  response: Response = await super()._execute_command('ccd_getFitParams', {'index': self._id})
205
- return str(response.results['params'])
189
+ fit_params: list[int] = response.results['fitParameters']
190
+ return fit_params
206
191
 
207
- async def set_fit_params(self, fit_params: str) -> None:
192
+ async def set_fit_parameters(self, fit_params: list[int]) -> None:
208
193
  """Sets the fit parameters of the CCD
209
194
 
210
195
  Args:
211
- fit_params (str): Fit parameters
196
+ fit_params (List[int]): Fit parameters
212
197
 
213
198
  Raises:
214
199
  Exception: When an error occurred on the device side
215
200
  """
216
- await super()._execute_command('ccd_setFitParams', {'index': self._id, 'params': fit_params})
201
+ fit_params_str: str = ','.join(map(str, fit_params))
202
+ await super()._execute_command('ccd_setFitParams', {'index': self._id, 'params': fit_params_str})
217
203
 
218
- async def get_timer_resolution(self) -> int:
219
- """Returns the timer resolution of the CCD
204
+ async def get_timer_resolution(self) -> TimerResolution:
205
+ """Returns the timer resolution of the CCD in microseconds [μs]
220
206
 
221
207
  Returns:
222
- int: Timer resolution
208
+ int: Timer resolution in microseconds [μs]
223
209
 
224
210
  Raises:
225
211
  Exception: When an error occurred on the device side
226
212
  """
227
213
  response: Response = await super()._execute_command('ccd_getTimerResolution', {'index': self._id})
228
- return int(response.results['resolution'])
214
+ timer_resolution: int = int(response.results['resolutionToken'])
215
+ return TimerResolution(timer_resolution)
229
216
 
230
- async def set_timer_resolution(self, timer_resolution: int) -> None:
217
+ async def set_timer_resolution(self, timer_resolution: TimerResolution) -> None:
231
218
  """Sets the timer resolution of the CCD
232
219
 
220
+ .. note:: The timer resolution value of 1 microsecond is not supported by all CCDs.
221
+
233
222
  Args:
234
223
  timer_resolution (int): Timer resolution
235
224
 
236
225
  Raises:
237
226
  Exception: When an error occurred on the device side
238
227
  """
239
- await super()._execute_command('ccd_setTimerResolution', {'index': self._id, 'resolution': timer_resolution})
228
+ await super()._execute_command(
229
+ 'ccd_setTimerResolution', {'index': self._id, 'resolutionToken': timer_resolution.value}
230
+ )
240
231
 
241
232
  async def set_acquisition_format(self, number_of_rois: int, acquisition_format: AcquisitionFormat) -> None:
242
- """Sets the acquisition format and the number of ROIs (Regions of Interest) or areas. After using this command
243
- to set the number of ROIs and format, the ccd_setRoi command should be used to define each ROI.
244
- Note: The Crop (2) and Fast Kinetics (3) acquisition formats are not supported by every CCD.
233
+ """Sets the acquisition format and the number of ROIs (Regions of Interest) or areas.
234
+
235
+ After using this command to set the number of ROIs and format, the set_region_of_interest function
236
+ should be used to define each ROI. Note: The Crop and Fast Kinetics acquisition formats are not
237
+ supported by every CCD.
245
238
 
246
239
  Args:
247
- acquisition_format (AcquisitionFormat): Acquisition format
248
240
  number_of_rois (int): Number of regions of interest
241
+ acquisition_format (AcquisitionFormat): Acquisition format
249
242
 
250
243
  Raises:
251
244
  Exception: When an error occurred on the device side
@@ -254,6 +247,45 @@ class ChargeCoupledDevice(AbstractDevice):
254
247
  'ccd_setAcqFormat', {'index': self._id, 'format': acquisition_format.value, 'numberOfRois': number_of_rois}
255
248
  )
256
249
 
250
+ async def set_region_of_interest(
251
+ self,
252
+ roi_index: int = 1,
253
+ x_origin: int = 0,
254
+ y_origin: int = 0,
255
+ x_size: int = 1024,
256
+ y_size: int = 256,
257
+ x_bin: int = 1,
258
+ y_bin: int = 256,
259
+ ) -> None:
260
+ """Sets the region of interest of the CCD
261
+ an example json command looks like this:
262
+
263
+ Args:
264
+ roi_index (int, optional): One based index of the region of interest. Defaults to 1.
265
+ x_origin (int, optional): X origin of the region of interest. Defaults to 0.
266
+ y_origin (int, optional): Y origin of the region of interest. Defaults to 0.
267
+ x_size (int, optional): X size of the region of interest. Defaults to 1024.
268
+ y_size (int, optional): Y size of the region of interest. Defaults to 256.
269
+ x_bin (int, optional): X bin of the region of interest. Defaults to 1.
270
+ y_bin (int, optional): Y bin of the region of interest. Defaults to 256.
271
+
272
+ Raises:
273
+ Exception: When an error occurred on the device side
274
+ """
275
+ await super()._execute_command(
276
+ 'ccd_setRoi',
277
+ {
278
+ 'index': self._id,
279
+ 'roiIndex': roi_index,
280
+ 'xOrigin': x_origin,
281
+ 'yOrigin': y_origin,
282
+ 'xSize': x_size,
283
+ 'ySize': y_size,
284
+ 'xBin': x_bin,
285
+ 'yBin': y_bin,
286
+ },
287
+ )
288
+
257
289
  async def set_x_axis_conversion_type(self, conversion_type: XAxisConversionType) -> None:
258
290
  """Sets the X-axis pixel conversion type to be used when retrieving the acquisition data with the
259
291
  ccd_getAcquisitionData command.
@@ -274,13 +306,15 @@ class ChargeCoupledDevice(AbstractDevice):
274
306
  2 = Mono Wavelength parameters contained in the icl_settings.ini file
275
307
  """
276
308
  response: Response = await super()._execute_command('ccd_getXAxisConversionType', {'index': self._id})
277
- return self.XAxisConversionType(self.XAxisConversionType(response.results['type']))
309
+ return XAxisConversionType(response.results['type'])
278
310
 
279
311
  async def set_acquisition_count(self, count: int) -> None:
280
- """Sets the number of acquisitions to be performed. The acquisition count is used to perform multiple
281
- acquisitions in a row.
312
+ """Sets the number of acquisition measurements to be performed sequentially by the hardware.
313
+
314
+ A count > 1 is commonly referred to as "MultiAcq".
315
+
282
316
  Args:
283
- count (int): The number of acquisitions to be performed.
317
+ count (int): The number of acquisition measurements.
284
318
  """
285
319
  await super()._execute_command('ccd_setAcqCount', {'index': self._id, 'count': count})
286
320
 
@@ -291,11 +325,18 @@ class ChargeCoupledDevice(AbstractDevice):
291
325
  response: Response = await super()._execute_command('ccd_getAcqCount', {'index': self._id})
292
326
  return int(response.results['count'])
293
327
 
294
- async def get_clean_count(self) -> str:
295
- """Gets the clean count mode of the CCD and the according mode"""
328
+ async def get_clean_count(self) -> tuple[int, CleanCountMode]:
329
+ """Gets the number of cleans to be performed prior to measurement.
330
+
331
+ Returns:
332
+ Tuple[int, CleanCountMode]:
333
+ count: Number of cleans,
334
+ mode: Specifies how the cleans will be performed.
335
+ """
296
336
  response: Response = await super()._execute_command('ccd_getCleanCount', {'index': self._id})
297
- answer: str = 'count: ' + str(response.results['count']) + ' ' + 'mode: ' + str(response.results['mode'])
298
- return answer
337
+ count: int = int(response.results['count'])
338
+ mode: CleanCountMode = CleanCountMode(response.results['mode'])
339
+ return count, mode
299
340
 
300
341
  async def set_clean_count(self, count: int, mode: CleanCountMode) -> None:
301
342
  """Sets the clean count mode of the CCD and the according mode
@@ -305,8 +346,8 @@ class ChargeCoupledDevice(AbstractDevice):
305
346
  """
306
347
  await super()._execute_command('ccd_setCleanCount', {'index': self._id, 'count': count, 'mode': mode.value})
307
348
 
308
- async def get_data_size(self) -> int:
309
- """Returns the size of the data of the CCD
349
+ async def get_acquisition_data_size(self) -> int:
350
+ """Returns the size of the acquisition data of the CCD
310
351
 
311
352
  Returns:
312
353
  int: Size of the data
@@ -317,17 +358,17 @@ class ChargeCoupledDevice(AbstractDevice):
317
358
  response: Response = await super()._execute_command('ccd_getDataSize', {'index': self._id})
318
359
  return int(response.results['size'])
319
360
 
320
- async def get_temperature(self) -> pint.Quantity:
361
+ async def get_chip_temperature(self) -> float:
321
362
  """Chip temperature of the CCD.
322
363
 
323
364
  Returns:
324
- pint.Quantity: chip's temperature in degree Celsius
365
+ float: chip's temperature in degree Celsius
325
366
 
326
367
  Raises:
327
368
  Exception: When an error occurred on the device side
328
369
  """
329
370
  response: Response = await super()._execute_command('ccd_getChipTemperature', {'index': self._id})
330
- return ureg.Quantity(response.results['temperature'], ureg.degC) # type: ignore
371
+ return float(response.results['temperature'])
331
372
 
332
373
  async def get_chip_size(self) -> Resolution:
333
374
  """Chip resolution of the CCD.
@@ -356,16 +397,176 @@ class ChargeCoupledDevice(AbstractDevice):
356
397
  exposure = int(response.results['time'])
357
398
  return exposure
358
399
 
359
- async def set_exposure_time(self, exposure_time_ms: int) -> None:
360
- """Sets the exposure time in ms
400
+ async def set_exposure_time(self, exposure_time: int) -> None:
401
+ """Sets the exposure time in timer resolution units (1us or 1000us)
402
+
403
+ Examples:
404
+ - If exposure_time is set to 50, and the timer resolution value is 1000, the CCD exposure time
405
+ (integration time) = 50 milliseconds.
406
+ - If exposure_time is set to 50, and the timer resolution value is 1, the CCD exposure time
407
+ (integration time) = 50 microseconds.
361
408
 
362
409
  Args:
363
- exposure_time_ms (int): Exposure time in ms
410
+ exposure_time (int): Exposure time in timer resolution units (1us or 1000us)
411
+ Raises:
412
+ Exception: When an error occurred on the device side
413
+ """
414
+
415
+ await super()._execute_command('ccd_setExposureTime', {'index': self._id, 'time': exposure_time})
416
+
417
+ async def get_trigger_input(self) -> tuple[bool, int, int, int]:
418
+ """This command is used to get the current setting of the input trigger.
419
+
420
+ The address, event, and signalType parameters are used to define the input trigger based on the
421
+ supported options of that particular CCD.
422
+
423
+ The supported trigger options are retrieved using the get_configuration function, and begin with the
424
+ “Triggers” string contained in the configuration.
425
+
426
+ Returns:
427
+ Tuple[bool, int, int, int]:
428
+ enabled: Specifies if the signal is enabled (e.g. False = Disabled),
429
+ address: used to specify where the trigger is located. (e.g. 0 = Trigger Input).
430
+ Note: Value of -1 indicates that the input trigger is disabled,
431
+ event: used to specify when the trigger event should occur. (e.g. 0 = Once - Start All)
432
+ Note: Value of -1 indicates that the input trigger is disabled,
433
+ signal type: used to specify how the signal will cause the input trigger. (e.g. 0 = TTL Falling Edge)
434
+ Note: Value of -1 indicates that the input trigger is disabled,
435
+
364
436
  Raises:
365
437
  Exception: When an error occurred on the device side
366
438
  """
439
+ response: Response = await super()._execute_command('ccd_getTriggerIn', {'index': self._id})
440
+ address = int(response.results['address'])
441
+ event = int(response.results['event'])
442
+ signal_type = int(response.results['signalType'])
443
+ enabled = address > -1 and event > -1 and signal_type > -1
444
+ return enabled, address, event, signal_type
367
445
 
368
- await super()._execute_command('ccd_setExposureTime', {'index': self._id, 'time': exposure_time_ms})
446
+ async def set_trigger_input(self, enabled: bool, address: int, event: int, signal_type: int) -> None:
447
+ """This command is used to enable or disable the trigger input.
448
+
449
+ When enabling the trigger input, the address, event, and signalType parameters are used to define
450
+ the input trigger based on the supported options of that particular CCD.
451
+
452
+ The supported trigger options are retrieved using the get_configuration function, and begin with the
453
+ “Triggers” string contained in the configuration.
454
+
455
+ Args:
456
+ enabled (bool): Enable or disable the trigger input. Note: When disabling the input trigger,
457
+ the address, event, and signalType parameters are ignored.
458
+ address (int): Used to specify where the trigger is located. (e.g. 0 = Trigger Input)
459
+ event (int): Used to specify when the trigger event should occur. (e.g. 0 = Once - Start All)
460
+ signal_type (int): Used to specify how the signal will cause the input trigger. (e.g. 0 = TTL Falling Edge)
461
+
462
+ Raises:
463
+ Exception: When an error occurred on the device side
464
+ """
465
+ if not enabled:
466
+ address = -1
467
+ event = -1
468
+ signal_type = -1
469
+
470
+ await super()._execute_command(
471
+ 'ccd_setTriggerIn',
472
+ {'index': self._id, 'enable': enabled, 'address': address, 'event': event, 'signalType': signal_type},
473
+ )
474
+ return
475
+
476
+ found_triggers = [trigger for trigger in self._config['triggers'] if trigger['token'] == address]
477
+ if not found_triggers:
478
+ raise Exception(f'Trigger address {address} not found in the configuration')
479
+
480
+ found_events = [
481
+ trigger_event for trigger_event in found_triggers[0]['events'] if trigger_event['token'] == event
482
+ ]
483
+ if not found_events:
484
+ raise Exception(f'Trigger event {event} not found in the configuration')
485
+
486
+ found_signal_types = [signal for signal in found_events[0]['types'] if signal['token'] == signal_type]
487
+ if not found_signal_types:
488
+ raise Exception(f'Trigger signal type {signal_type} not found in the configuration')
489
+
490
+ await super()._execute_command(
491
+ 'ccd_setTriggerIn',
492
+ {'index': self._id, 'enable': enabled, 'address': address, 'event': event, 'signalType': signal_type},
493
+ )
494
+
495
+ async def get_signal_output(self) -> tuple[bool, int, int, int]:
496
+ """This command is used to get the current setting of the signal output.
497
+
498
+ The address, event, and signalType parameters are used to define the signal based on the supported
499
+ options of that particular CCD.
500
+
501
+ The supported signal options are retrieved using the get_configuration command, and begin with the
502
+ “Signals” string contained in the configuration.
503
+
504
+ Returns:
505
+ Tuple[bool, int, int, int]:
506
+ enabled: Specifies if the signal is enabled (e.g. False = Disabled),
507
+ address: Used to specify where the signal is located (e.g. 0 = Signal Output),
508
+ Note: Value of -1 indicates that the signal output is disabled,
509
+ event: Used to specify when the signal event should occur. (e.g. 3 = Shutter Open)
510
+ Note: Value of -1 indicates that the signal output is disabled,
511
+ signal type: how the signal will cause the event. (e.g. 0 = TTL Active High)
512
+ Note: Value of -1 indicates that the signal output is disabled,
513
+
514
+ Raises:
515
+ Exception: When an error occurred on the device side
516
+ """
517
+ response: Response = await super()._execute_command('ccd_getSignalOut', {'index': self._id})
518
+ address = int(response.results['address'])
519
+ event = int(response.results['event'])
520
+ signal_type = int(response.results['signalType'])
521
+ enabled = address > -1 and event > -1 and signal_type > -1
522
+ return enabled, address, event, signal_type
523
+
524
+ async def set_signal_output(self, enabled: bool, address: int, event: int, signal_type: int) -> None:
525
+ """This command is used to enable or disable the signal output.
526
+
527
+ When enabling the signal output, the address, event, and signalType parameters are used to
528
+ define the signal based on the supported options of that particular CCD.
529
+
530
+ The supported signal options are retrieved using the ccd_getConfig command, and begin with the
531
+ “Signals” string contained in the configuration.
532
+
533
+ Args:
534
+ enabled (bool): Enable or disable the signal output. Note: When disabling the signal output,
535
+ the address, event, and signal_type parameters are ignored.
536
+ address (int): Used to specify where the signal is located (e.g. 0 = Signal Output)
537
+ event (int): Used to specify when the signal event should occur. (e.g. 3 = Shutter Open)
538
+ signal_type (int): How the signal will cause the event. (e.g. 0 = TTL Active High)
539
+
540
+ """
541
+ if not enabled:
542
+ address = -1
543
+ event = -1
544
+ signal_type = -1
545
+
546
+ await super()._execute_command(
547
+ 'ccd_setSignalOut',
548
+ {'index': self._id, 'enable': enabled, 'address': address, 'event': event, 'signalType': signal_type},
549
+ )
550
+ return
551
+
552
+ found_triggers = [trigger for trigger in self._config['signals'] if trigger['token'] == address]
553
+ if not found_triggers:
554
+ raise Exception(f'Signal address {address} not found in the configuration')
555
+
556
+ found_events = [
557
+ trigger_event for trigger_event in found_triggers[0]['events'] if trigger_event['token'] == event
558
+ ]
559
+ if not found_events:
560
+ raise Exception(f'Signal event {event} not found in the configuration')
561
+
562
+ found_signal_types = [signal for signal in found_events[0]['types'] if signal['token'] == signal_type]
563
+ if not found_signal_types:
564
+ raise Exception(f'Signal type {signal_type} not found in the configuration')
565
+
566
+ await super()._execute_command(
567
+ 'ccd_setSignalOut',
568
+ {'index': self._id, 'enable': enabled, 'address': address, 'event': event, 'signalType': signal_type},
569
+ )
369
570
 
370
571
  async def get_acquisition_ready(self) -> bool:
371
572
  """Returns true if the CCD is ready to acquire
@@ -378,66 +579,110 @@ class ChargeCoupledDevice(AbstractDevice):
378
579
  response: Response = await super()._execute_command('ccd_getAcquisitionReady', {'index': self._id})
379
580
  return bool(response.results['ready'])
380
581
 
381
- async def set_acquisition_start(self, open_shutter: bool) -> None:
382
- """Starts the acquisition of the CCD
582
+ async def acquisition_start(self, open_shutter: bool) -> None:
583
+ """Starts an acquisition that has been set up according to the previously defined acquisition parameters.
584
+
585
+ Note: To specify the acquisiton parameters please see set_region_of_interest, set_x_axis_conversion_type.
586
+ If there are no acquisition parameters set at the time of acquisition it may result in no data being generated.
383
587
 
384
588
  Args:
385
- open_shutter (bool): Whether the shutter of the camera should be open
589
+ open_shutter (bool): Whether the shutter of the camera should be open during the acquisition.
386
590
  Raises:
387
591
  Exception: When an error occurred on the device side
388
592
  """
389
- await super()._execute_command('ccd_setAcquisitionStart', {'index': self._id, 'openShutter': open_shutter})
593
+ await super()._execute_command('ccd_acquisitionStart', {'index': self._id, 'openShutter': open_shutter})
390
594
 
391
- async def set_region_of_interest(
392
- self,
393
- roi_index: int = 1,
394
- x_origin: int = 0,
395
- y_origin: int = 0,
396
- x_size: int = 1024,
397
- y_size: int = 256,
398
- x_bin: int = 1,
399
- y_bin: int = 256,
400
- ) -> None:
401
- """Sets the region of interest of the CCD
402
- an example json command looks like this:
595
+ async def get_acquisition_busy(self) -> bool:
596
+ """Returns true if the CCD is busy with the acquisition"""
597
+ response: Response = await super()._execute_command('ccd_getAcquisitionBusy', {'index': self._id})
598
+ return bool(response.results['isBusy'])
599
+
600
+ async def acquisition_abort(self) -> None:
601
+ """Stops the acquisition of the CCD"""
602
+ await super()._execute_command('ccd_acquisitionAbort', {'index': self._id})
603
+
604
+ async def get_acquisition_data(self) -> dict[Any, Any]:
605
+ """Retrieves data from the last acquisition.
606
+
607
+ The acquisition description string consists of the following information:
608
+ - acqIndex: Acquisition number
609
+ - roiIndex: Region of Interest number
610
+ - xOrigin: ROI’s X Origin
611
+ - yOrigin: ROI’s Y Origin
612
+ - xSize: ROI’s X Size
613
+ - ySize: ROI’s Y Size
614
+ - xBinning: ROI’s X Bin
615
+ - yBinning: ROI’s Y Bin
616
+ - Timestamp: This is a timestamp that relates to the time when the all the programmed acquisitions have
617
+ completed. The data from all programmed acquisitions are retrieve from the CCD after all
618
+ acquisitions have completed, therefore the same timestamp is used for all acquisitions.
619
+ """
620
+ response: Response = await super()._execute_command('ccd_getAcquisitionData', {'index': self._id})
621
+ return response.results['acquisition']
622
+
623
+ async def set_center_wavelength(self, mono_index: float, center_wavelength: float) -> None:
624
+ """Sets the center wavelength value to be used in the grating equation.
625
+
626
+ Used when X axis conversion is XAxisConversionType.FROM_ICL_SETTINGS_INI
403
627
 
404
628
  Args:
405
- roi_index (int, optional): Index of the region of interest. Defaults to 1.
406
- x_origin (int, optional): X origin of the region of interest. Defaults to 0.
407
- y_origin (int, optional): Y origin of the region of interest. Defaults to 0.
408
- x_size (int, optional): X size of the region of interest. Defaults to 1024.
409
- y_size (int, optional): Y size of the region of interest. Defaults to 256.
410
- x_bin (int, optional): X bin of the region of interest. Defaults to 1.
411
- y_bin (int, optional): Y bin of the region of interest. Defaults to 256.
629
+ mono_index (float): Index for which mono to pull info from icl_settings.ini for
630
+ center_wavelength (float): Center wavelength
412
631
 
413
632
  Raises:
414
633
  Exception: When an error occurred on the device side
415
634
  """
416
635
  await super()._execute_command(
417
- 'ccd_setRoi',
636
+ 'ccd_setCenterWavelength',
637
+ {'index': self._id, 'monoIndex': mono_index, 'wavelength': center_wavelength},
638
+ )
639
+
640
+ async def range_mode_center_wavelengths(
641
+ self, monochromator_index: int, start_wavelength: float, end_wavelength: float, pixel_overlap: int
642
+ ) -> list[float]:
643
+ """Finds the center wavelength positions based on the input range and pixel overlap.
644
+
645
+ The following commands are prerequisites and should be called prior to using this command:
646
+ - :func:`ChargeCoupledDevice.set_x`,
647
+ - :func:`ChargeCoupledDevice.ccd_setAcqFormat`,
648
+ - :func:`ChargeCoupledDevice.ccd_setRoi`
649
+
650
+ Args:
651
+ monochromator_index (int): Index of the monochromator that is connected to the setup
652
+ start_wavelength (float): Start wavelength
653
+ end_wavelength (float): End wavelength
654
+ pixel_overlap (int): Overlap size in pixels between the scans.
655
+
656
+ Returns:
657
+ List[float]: List of center wavelength positions to cover the desired range.
658
+ Raises:
659
+ Exception: When an error occurred on the device side
660
+ """
661
+ response: Response = await super()._execute_command(
662
+ 'ccd_calculateRangeModePositions',
418
663
  {
419
664
  'index': self._id,
420
- 'roiIndex': roi_index,
421
- 'xOrigin': x_origin,
422
- 'yOrigin': y_origin,
423
- 'xSize': x_size,
424
- 'ySize': y_size,
425
- 'xBin': x_bin,
426
- 'yBin': y_bin,
665
+ 'monoIndex': monochromator_index,
666
+ 'start': start_wavelength,
667
+ 'end': end_wavelength,
668
+ 'overlap': pixel_overlap,
427
669
  },
428
670
  )
671
+ return response.results['centerWavelengths']
429
672
 
430
- async def get_acquisition_data(self) -> dict[Any, Any]:
431
- """Returns the acquisition data of the CCD
432
- nina: atm this returns data still formatted for telnet communication, not formatted as json"""
433
- response: Response = await super()._execute_command('ccd_getAcquisitionData', {'index': self._id})
434
- return response.results
673
+ @staticmethod
674
+ async def raman_convert(spectrum: list[float], excitation_wavelength: float) -> list[float]:
675
+ """Calculates the raman shift for every wavelength in the list relative to the excitation wavelength.
435
676
 
436
- async def get_acquisition_busy(self) -> bool:
437
- """Returns true if the CCD is busy with the acquisition"""
438
- response: Response = await super()._execute_command('ccd_getAcquisitionBusy', {'index': self._id})
439
- return bool(response.results['isBusy'])
677
+ Args:
678
+ spectrum (list[float]): Wavelengths
679
+ excitation_wavelength: Excitation wavelength
440
680
 
441
- async def set_acquisition_abort(self) -> None:
442
- """Stops the acquisition of the CCD"""
443
- await super()._execute_command('ccd_setAcquisitionAbort', {'index': self._id})
681
+ Returns:
682
+ list[float]: Wavelengths converted to raman shifts
683
+ """
684
+ raman_values = []
685
+ for wave_length in spectrum:
686
+ raman_shift = ((1 / excitation_wavelength) - (1 / wave_length)) * (10**7)
687
+ raman_values.append(raman_shift)
688
+ return raman_values