5454PyPI. 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
5961import contextlib
6062import enum
6163import logging
62- from typing import Optional , Tuple
64+ from typing import Optional , Union , Tuple
6365
6466import numpy as np
65- from ximea import xiapi
67+ from ximea import xiapi , xidefs
6668
6769import microscope
6870import microscope .abc
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