Skip to content

Commit 7cfbf5f

Browse files
committed
ximea: merge changes from diverged development in DeepSIM (issue #128)
2 parents 7ffac10 + 9e0fa6e commit 7cfbf5f

File tree

2 files changed

+109
-65
lines changed

2 files changed

+109
-65
lines changed

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ Version 0.4.0 (upcoming)
2929
* Concrete classes must implement the `n_actuators` public
3030
property instead of the private _n_actuators`.
3131

32+
* Device specific changes:
33+
34+
* Ximea Camera:
35+
36+
* Support for the ximea cameras was completely rewritten to
37+
support hardware triggers, as well as fix multiple minor issues.
38+
3239
* The device server and clients no longer force pickle protocol
3340
version 2. If the client and server are running different Python
3441
versions it may be necessary to specify a version number. This

microscope/cameras/ximea.py

Lines changed: 102 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -21,74 +21,78 @@
2121

2222
import logging
2323

24-
import numpy as np
24+
from ximea import xiapi
2525

2626
from microscope import devices
27-
from microscope.devices import keep_acquiring, ROI
28-
29-
#import ximea python module.
30-
from ximea import xiapi
3127

3228

3329
_logger = logging.getLogger(__name__)
3430

3531

36-
# Trigger mode to type.
37-
TRIGGER_MODES = {
38-
'internal': None,
39-
'external': devices.TRIGGER_BEFORE,
40-
'external start': None,
41-
'external exposure': devices.TRIGGER_DURATION,
42-
'software': devices.TRIGGER_SOFT,
43-
}
44-
45-
#trig types from define file....
46-
# XI_TRG_SOURCE = {
47-
# "XI_TRG_OFF": c_uint(0), #Camera works in free run mode.
48-
# "XI_TRG_EDGE_RISING": c_uint(1), #External trigger (rising edge).
49-
# "XI_TRG_EDGE_FALLING": c_uint(2), #External trigger (falling edge).
50-
# "XI_TRG_SOFTWARE": c_uint(3), #Software(manual) trigger.
51-
# "XI_TRG_LEVEL_HIGH": c_uint(4), #Specifies that the trigger is considered valid as long as the level of the source signal is high.
52-
# "XI_TRG_LEVEL_LOW": c_uint(5), #Specifies that the trigger is considered valid as long as the level of the source signal is low.
53-
# }
54-
55-
5632
class XimeaCamera(devices.CameraDevice):
57-
def __init__(self, **kwargs):
33+
def __init__(self, dev_id=0, **kwargs):
5834
super().__init__(**kwargs)
5935
self._acquiring = False
6036
self._exposure_time = 0.1
6137
self._triggered = False
62-
self.Roi=ROI(None,None,None,None)
38+
self._handle = None
39+
self._dev_id = dev_id
40+
self._roi = devices.ROI(None,None,None,None)
6341

6442
def _fetch_data(self):
65-
if self._acquiring and self._triggered:
66-
self.handle.get_image(self.img)
43+
if not self._acquiring:
44+
return
45+
46+
trigger_type = self._handle.get_trigger_source()
47+
if trigger_type == 'XI_TRG_SOFTWARE' and not self._triggered:
48+
return
49+
elif trigger_type != 'XI_TRG_EDGE_RISING':
50+
raise Exception('unhandled trigger type %s' % trigger_type)
51+
52+
try:
53+
self._handle.get_image(self.img)
54+
data = self.img.get_image_data_numpy()
55+
_logger.info("Fetched imaged with dims %s and size %s.",
56+
data.shape, data.size)
6757
_logger.info('Sending image')
68-
self._triggered = False
69-
data = np.array(list(self.img.get_image_data_raw()),dtype=np.uint16).reshape(self.img.width,self.img.height)
70-
return data
58+
if trigger_type == 'XI_TRG_SOFTWARE':
59+
self._triggered = False
60+
return self.img.get_image_data_numpy()
61+
except Exception as err:
62+
if getattr(err, 'status', None) == 10:
63+
# This is a Timeout error
64+
return
65+
else:
66+
raise err
7167

7268
def abort(self):
7369
_logger.info('Disabling acquisition.')
7470
if self._acquiring:
7571
self._acquiring = False
76-
self.handle.stop_acquisition()
72+
self._handle.stop_acquisition()
7773

7874
def initialize(self):
7975
"""Initialise the camera.
8076
8177
Open the connection, connect properties and populate settings dict.
8278
"""
8379
try:
84-
self.handle = xiapi.Camera()
85-
self.handle.open_device()
80+
self._handle = xiapi.Camera(self._dev_id)
81+
self._handle.open_device()
8682
except:
8783
raise Exception("Problem opening camera.")
88-
if self.handle == None:
84+
if self._handle == None:
8985
raise Exception("No camera opened.")
86+
9087
_logger.info('Initializing.')
91-
self.img=xiapi.Image()
88+
# Try set camera into rising-edge hardware trigger mode. If
89+
# that can't be done set it to software trigger mode.
90+
try:
91+
self._handle.set_trigger_source('XI_TRG_EDGE_RISING')
92+
except:
93+
self._handle.set_trigger_source('XI_TRG_SOFTWARE')
94+
# create img buffer to hold images.
95+
self.img = xiapi.Image()
9296

9397
def make_safe(self):
9498
if self._acquiring:
@@ -102,63 +106,96 @@ def _on_enable(self):
102106
if self._acquiring:
103107
self.abort()
104108
self._acquiring = True
105-
#actually start camera
106-
self.handle.start_acquisition()
109+
# actually start camera
110+
self._handle.start_acquisition()
107111
_logger.info("Acquisition enabled.")
108112
return True
109113

110114
def set_exposure_time(self, value):
111-
#exposure times are set in us.
112-
self.handle.set_exposure(int(value*1.0E6))
115+
# exposure times are set in us.
116+
try:
117+
self._handle.set_exposure(int(value * 1000000))
118+
except Exception as err:
119+
_logger.debug("set_exposure_time exception: %s", err)
113120

114121
def get_exposure_time(self):
115-
#exposure times are in us, so multiple by 1E-6 to get seconds.
116-
return (self.handle.get_exposure()*1.0E-6)
122+
# exposure times are in us, so multiple by 1E-6 to get seconds.
123+
return (self._handle.get_exposure() * 1.0E-6)
117124

118125
def get_cycle_time(self):
119-
return (1.0/self.handle.get_framerate())
126+
return (1.0/self._handle.get_framerate())
120127

121128
def _get_sensor_shape(self):
122-
return (self.img.width,self.img.height)
129+
return (self._handle.get_width(), self._handle.get_height())
123130

131+
def get_trigger_source(self):
132+
return (self._handle.get_trigger_source())
124133

125134
def get_trigger_type(self):
126-
trig=self.handle.get_trigger_source()
127-
if trig==XI_TRG_SOFTWARE:
135+
trig = self._handle.get_trigger_source()
136+
_logger.info("called get trigger type %s", trig)
137+
if trig == 'XI_TRG_SOFTWARE':
128138
return devices.TRIGGER_SOFT
129-
elif trig==XI_TRG_EDGE_RISING:
139+
elif trig == 'XI_TRG_EDGE_RISING':
130140
return devices.TRIGGER_BEFORE
131141

132-
def set_trigger_type(self, trigger):
133-
if (trigger == devices.TRIGGER_SOFT):
134-
self.handle.set_triger_source(XI_TG_SOURCE['Xi_TRG_SOFTWARE'])
135-
elif (trigger == devices.TRIGGER_BEFORE):
136-
self.handle.set_triger_source(XI_TG_SOURCE['Xi_TRG_EDGE_RISING'])
137-
#define digial input mode of trigger
138-
self.handle.set_gpi_selector(1)
139-
self.handle.set_gpi_mode(XI_GPI_TRIGGER)
142+
def set_trigger_source(self, trig):
143+
_logger.info("Set trigger source %s", trig)
144+
reenable = False
145+
if self._acquiring:
146+
self.abort()
147+
reenable = True
148+
result = self._handle.set_trigger_source(trig)
149+
_logger.info("Set trigger source result %s", result)
150+
if reenable:
151+
self._on_enable()
152+
return
153+
154+
def set_trigger_type(self, trig):
155+
_logger.info("Set trigger type %s", trig)
156+
self.abort()
157+
158+
if trig is 0:
159+
self._handle.set_trigger_source('XI_TRG_SOFTWARE')
160+
elif trig is 1:
161+
self._handle.set_trigger_source('XI_TRG_EDGE_RISING')
162+
# define digial input mode of trigger
163+
self._handle.set_gpi_selector('XI_GPI_PORT1')
164+
self._handle.set_gpi_mode('XI_GPI_TRIGGER')
165+
self._handle.set_gpo_selector('XI_GPO_PORT1')
166+
self._handle.set_gpo_mode('XI_GPO_EXPOSURE_ACTIVE')
167+
168+
self._on_enable()
169+
170+
result = self._handle.get_trigger_source()
171+
_logger.info("Trigger type %s", result)
172+
_logger.info("GPI Selector %s", self._handle.get_gpi_selector())
173+
_logger.info("GPI Mode %s", self._handle.get_gpi_mode())
174+
175+
return
140176

141177
def soft_trigger(self):
142-
_logger.info('Trigger received; self._acquiring is %s.'
143-
% self._acquiring)
178+
_logger.info('Soft trigger received; self._acquiring is %s.',
179+
self._acquiring)
144180
if self._acquiring:
181+
self._handle.set_trigger_software(True)
145182
self._triggered = True
146183

147184
def _get_binning(self):
148-
return (1,1)
185+
return (1, 1)
149186

150-
@keep_acquiring
187+
@devices.keep_acquiring
151188
def _set_binning(self, h, v):
152189
return False
153190

154191
def _get_roi(self):
155-
return self.Roi
192+
return self._roi
156193

157-
@keep_acquiring
194+
@devices.keep_acquiring
158195
def _set_roi(self, x, y, width, height):
159-
self.Roi = ROI(x, y, width, height)
196+
self._roi = devices.ROI(x, y, width, height)
160197

161198
def _on_shutdown(self):
162199
if self._acquiring:
163-
self.handle.stop_acquisition()
164-
self.handle.close_device()
200+
self._handle.stop_acquisition()
201+
self._handle.close_device()

0 commit comments

Comments
 (0)