Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ad9c941

Browse files
Implement, on Ximea cameras automated settings definitions.
See issue #262
1 parent a9aab83 commit ad9c941

File tree

1 file changed

+208
-27
lines changed

1 file changed

+208
-27
lines changed

microscope/cameras/ximea.py

Lines changed: 208 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,17 @@
5454
PyPI. See Ximea's website for `install instructions
5555
<https://www.ximea.com/support/wiki/apis/Python>`__.
5656
57+
If installing under Linux be sure to follow the Linux installation tutorial
58+
<https://www.ximea.com/support/wiki/apis/XIMEA_Linux_Software_Package>__.
5759
"""
5860

5961
import contextlib
6062
import enum
6163
import logging
62-
from typing import Optional, Tuple
64+
from typing import Optional, Union, Tuple
6365

6466
import numpy as np
65-
from ximea import xiapi
67+
from ximea import xiapi, xidefs
6668

6769
import microscope
6870
import microscope.abc
@@ -77,9 +79,34 @@
7779
_XI_TIMEOUT = 10
7880
_XI_NOT_SUPPORTED = 12
7981
_XI_NOT_IMPLEMENTED = 26
80-
_XI_ACQUISITION_STOPED = 45
82+
_XI_ACQUISITION_STOPPED = 45
8183
_XI_UNKNOWN_PARAM = 100
82-
84+
_XI_UNSUPPORTED_PARAM = 106
85+
_XI_UNSUPPORTED_INFO_PARAM = 107
86+
_XI_READ_ONLY_PARAM = 109
87+
88+
# Some more "advanced" features of the Ximea cameras are not supported,
89+
# at least for the moment. These features are implemented as settings that
90+
# we have to "blacklist" to avoid their loading.
91+
_UNSUPPORTED_SETTINGS = [
92+
# The device manifest provides XML data of the features supported by the camera
93+
"device_manifest",
94+
# Settings related to the FFS. Some ximea camera models provide access
95+
# to the Flash memory as a file system.
96+
"read_file_ffs",
97+
"write_file_ffs",
98+
"ffs_file_name",
99+
"ffs_file_id",
100+
"ffs_file_offset",
101+
"ffs_file_size",
102+
"free_ffs_size",
103+
"used_ffs_size",
104+
"ffs_access_key",
105+
# The context list is used to get a list of settings for off-line processing
106+
"xiapi_context_list",
107+
# The trigger source setting is not added automatically but rather through a custom function so we skip it
108+
"trigger_source",
109+
]
83110

84111
# During acquisition, we rely on catching timeout errors which then
85112
# get discarded. However, with debug level set to warning (XiApi
@@ -225,7 +252,7 @@ def _fetch_data(self) -> Optional[np.ndarray]:
225252
if getattr(err, "status", None) == _XI_TIMEOUT:
226253
return None
227254
elif (
228-
getattr(err, "status", None) == _XI_ACQUISITION_STOPED
255+
getattr(err, "status", None) == _XI_ACQUISITION_STOPPED
229256
and not self._acquiring
230257
):
231258
# We can end up here during disable if self._acquiring
@@ -253,6 +280,101 @@ def abort(self):
253280
self._acquiring = True
254281
raise
255282

283+
def _is_setting_readonly(self, name):
284+
# As far as I see, there is no other way to see if a setting is readonly apart from trying to change it
285+
# if a setter function is not implemented I assume it is a permanent readonly setting
286+
# Some cameras implement the "device_manifest" setting that returns a full description of the settings as a
287+
# XML file. As this is not a standard feature I prefer to stick with this "less proper" way of defining this
288+
if hasattr(self._handle, f"set_{name}"):
289+
return False
290+
else:
291+
return True
292+
293+
def _get_setting_values(self, setting_name: str) -> \
294+
Optional[Tuple[Union[int, float, None], Union[int, float, None]]]:
295+
if self._is_setting_readonly(setting_name):
296+
return None, None
297+
else:
298+
try:
299+
min_val = self._handle.get_param(f"{setting_name}:min")
300+
except xiapi.Xi_error as err:
301+
if err.status == _XI_UNKNOWN_PARAM:
302+
min_val = None
303+
else:
304+
raise err
305+
try:
306+
max_val = self._handle.get_param(f"{setting_name}:max")
307+
except xiapi.Xi_error as err:
308+
if err.status == _XI_UNKNOWN_PARAM:
309+
max_val = None
310+
else:
311+
raise err
312+
313+
return min_val, max_val
314+
315+
def _get_int_setting(self, setting_name: str) -> int:
316+
return self._handle.get_param(setting_name)
317+
318+
def _set_int_setting(self, setting_name: str, value: int) -> None:
319+
try:
320+
self._handle.set_param(setting_name, value)
321+
except xiapi.Xi_error as err:
322+
if err.status in [_XI_UNKNOWN_PARAM, _XI_READ_ONLY_PARAM]:
323+
_logger.debug(f"Failed setting {setting_name} Error {err.status}")
324+
325+
def _get_float_setting(self, setting_name: str) -> float:
326+
return self._handle.get_param(setting_name)
327+
328+
def _set_float_setting(self, setting_name: str, value: float) -> None:
329+
try:
330+
self._handle.set_param(setting_name, value)
331+
except xiapi.Xi_error as err:
332+
if err.status in [_XI_UNKNOWN_PARAM, _XI_READ_ONLY_PARAM]:
333+
_logger.debug(f"Failed setting {setting_name} Error {err.status}")
334+
335+
def _get_str_setting(self, setting_name: str) -> str:
336+
return self._handle.get_param(setting_name)
337+
338+
def _set_str_setting(self, setting_name: str, value: str) -> None:
339+
# Updating initializing all the settings sometimes tries to set a setting using an empty string.
340+
if len(value) == 0:
341+
return
342+
try:
343+
self._handle.set_param(setting_name, value)
344+
except xiapi.Xi_error as err:
345+
if err.status in [_XI_UNKNOWN_PARAM, _XI_READ_ONLY_PARAM]:
346+
_logger.debug(f"Failed setting {setting_name} Error {err.status}")
347+
348+
def _get_enum_setting(self, setting_name: str) -> int:
349+
try:
350+
values_to_idx = {val: idx.value for val, idx in xidefs.ASSOC_ENUM[setting_name].items()}
351+
except KeyError as err:
352+
_logger.error(f"The Ximea API does not define the enum values for the setting {setting_name}")
353+
raise err
354+
return values_to_idx[self._handle.get_param(setting_name)]
355+
356+
def _get_enum_values(self, setting_name: str) -> dict:
357+
try:
358+
values = {i.value: val for val, i in xidefs.ASSOC_ENUM[setting_name].items()}
359+
except KeyError as err:
360+
_logger.error(f"Failed getting values for {setting_name}")
361+
raise err
362+
return values
363+
364+
def _set_enum_setting(self, setting_name: str, value: enum) -> None:
365+
try:
366+
idx_to_values = {i.value: val for val, i in xidefs.ASSOC_ENUM[setting_name].items()}
367+
self._handle.set_param(setting_name, idx_to_values[value])
368+
except KeyError as err:
369+
_logger.error(f"Failed setting {setting_name}. Error {err.status}")
370+
raise err
371+
372+
def _get_bool_setting(self, setting_name: str) -> bool:
373+
return self._handle.get_param(setting_name)
374+
375+
def _set_bool_setting(self, setting_name: str, value: bool) -> None:
376+
self._handle.set_param(setting_name, value)
377+
256378
def initialize(self) -> None:
257379
"""Initialise the camera.
258380
@@ -295,20 +417,80 @@ def initialize(self) -> None:
295417
microscope.TriggerType.SOFTWARE, microscope.TriggerMode.ONCE
296418
)
297419

298-
# Add settings for the different temperature sensors.
299-
for temp_param_name in [
300-
"chip_temp",
301-
"hous_temp",
302-
"hous_back_side_temp",
303-
"sensor_board_temp",
304-
]:
305-
get_temp_method = getattr(self._handle, "get_" + temp_param_name)
306-
# Not all cameras have temperature sensors in all
307-
# locations. We can't query if the sensor is there, we
308-
# can only try to read the temperature and skip that
309-
# temperature sensor if we get an exception.
420+
# Add settings
421+
def _add_int_setting(name):
422+
self.add_setting(
423+
name=name,
424+
dtype="int",
425+
get_func=lambda name=name: self._get_int_setting(name),
426+
set_func=lambda v, name=name: self._set_int_setting(name, v),
427+
values=lambda name=name: self._get_setting_values(name),
428+
readonly=lambda name=name: self._is_setting_readonly(name)
429+
)
430+
431+
def _add_float_setting(name):
432+
self.add_setting(
433+
name=name,
434+
dtype="float",
435+
get_func=lambda name=name: self._get_float_setting(name),
436+
set_func=lambda v, name=name: self._set_float_setting(name, v),
437+
values=lambda name=name: self._get_setting_values(name),
438+
readonly=lambda name=name: self._is_setting_readonly(name)
439+
)
440+
441+
def _add_str_setting(name):
442+
self.add_setting(
443+
name=name,
444+
dtype="str",
445+
get_func=lambda name=name: self._get_str_setting(name),
446+
set_func=lambda v, name=name: self._set_str_setting(name, v),
447+
# The value of the string size is extracted from the default buffer size of xiapi.Camera.get_param
448+
# This is definitely not enough for many settings. The Ximea API fails to provide proper string size
449+
# and a reference has to be found in the C library.
450+
values=256,
451+
readonly=lambda name=name: self._is_setting_readonly(name)
452+
)
453+
454+
def _add_enum_setting(name):
455+
self.add_setting(
456+
name=name,
457+
dtype="enum",
458+
get_func=lambda name=name: self._get_enum_setting(name),
459+
set_func=lambda v, name=name: self._set_enum_setting(name, v),
460+
values=lambda name=name: self._get_enum_values(name),
461+
readonly=lambda name=name: self._is_setting_readonly(name)
462+
)
463+
464+
def _add_bool_setting(name):
465+
self.add_setting(
466+
name=name,
467+
dtype="bool",
468+
get_func=lambda name=name: self._get_bool_setting(name),
469+
set_func=lambda v, name=name: self._set_bool_setting(name, v),
470+
values=None,
471+
readonly=lambda name=name: self._is_setting_readonly(name)
472+
)
473+
474+
def _add_cmd_setting(name):
475+
pass
476+
477+
prm_type_to_add_method = {
478+
"xiTypeInteger": _add_int_setting,
479+
"xiTypeFloat": _add_float_setting,
480+
"xiTypeString": _add_str_setting,
481+
"xiTypeEnum": _add_enum_setting,
482+
"xiTypeBoolean": _add_bool_setting,
483+
"xiTypeCommand": _add_cmd_setting,
484+
"xiTypeInteger64": _add_int_setting,
485+
}
486+
487+
for setting_name, setting_type in xidefs.VAL_TYPE.items():
488+
# TODO: Do we have to remove here the settings that are implemented in another way?
489+
# ROI, exposure,...
490+
if setting_name in _UNSUPPORTED_SETTINGS:
491+
continue
310492
try:
311-
get_temp_method()
493+
self._handle.get_param(setting_name)
312494
except xiapi.Xi_error as err:
313495
# Depending on XiAPI version, camera model, and
314496
# selected sensor, we might get any of these errors as
@@ -318,16 +500,15 @@ def initialize(self) -> None:
318500
_XI_NOT_SUPPORTED,
319501
_XI_NOT_IMPLEMENTED,
320502
_XI_UNKNOWN_PARAM,
503+
_XI_UNSUPPORTED_PARAM,
504+
_XI_UNSUPPORTED_INFO_PARAM
321505
]:
322-
raise
323-
else:
324-
self.add_setting(
325-
temp_param_name,
326-
"float",
327-
get_temp_method,
328-
None,
329-
values=tuple(),
330-
)
506+
_logger.debug(f"The setting {setting_name} failed to be added")
507+
raise err
508+
else:
509+
continue
510+
511+
prm_type_to_add_method[setting_type](setting_name)
331512

332513
def _do_disable(self):
333514
self.abort()

0 commit comments

Comments
 (0)