Skip to content

Commit 88eeefe

Browse files
Merge pull request #42 from JQIamo/SpinnakerCamera
Added SpinnakerCamera
2 parents 55b1bd8 + 84eb889 commit 88eeefe

File tree

5 files changed

+325
-0
lines changed

5 files changed

+325
-0
lines changed

labscript_devices/SpinnakerCamera/__init__.py

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/SpinnakerCamera/blacs_tabs.py #
4+
# #
5+
# Copyright 2019, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
from labscript_devices.IMAQdxCamera.blacs_tabs import IMAQdxCameraTab
15+
16+
class SpinnakerCameraTab(IMAQdxCameraTab):
17+
18+
# override worker class
19+
worker_class = 'labscript_devices.SpinnakerCamera.blacs_workers.SpinnakerCameraWorker'
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/SpinnakerCamera/blacs_workers.py #
4+
# #
5+
# Copyright 2019, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
# Original imaqdx_camera server by dt, with modifications by rpanderson and cbillington.
15+
# Refactored as a BLACS worker by cbillington
16+
# PsSpin implementation by spe
17+
18+
import numpy as np
19+
from labscript_utils import dedent
20+
from enum import IntEnum
21+
import PySpin
22+
from time import sleep, perf_counter
23+
24+
from labscript_devices.IMAQdxCamera.blacs_workers import IMAQdxCameraWorker
25+
26+
class Spinnaker_Camera(object):
27+
def __init__(self, serial_number):
28+
"""Initialize Spinnaker API camera.
29+
30+
Serial number should be of string(?) type."""
31+
self.system = PySpin.System.GetInstance()
32+
33+
ver = self.system.GetLibraryVersion()
34+
min_ver = (1,23,0,27) # first release with python 3.6 support
35+
if (ver.major, ver.minor, ver.type, ver.build) < min_ver:
36+
raise RuntimeError(f"PySpin version {ver} must be >= {min_ver}")
37+
38+
camList = self.system.GetCameras()
39+
numCams = camList.GetSize()
40+
41+
if numCams==0:
42+
raise ValueError('No cameras found!')
43+
44+
if isinstance(serial_number, int):
45+
self.camera = camList.GetBySerial('%d' % serial_number)
46+
else:
47+
self.camera = camList.GetBySerial(serial_number)
48+
self.camera.Init()
49+
camList.Clear()
50+
51+
# Set the timeout to 5 s:
52+
self.timeout = 5000 # in ms
53+
54+
# Set the abort acquisition thingy:
55+
self._abort_acquisition = False
56+
57+
def get_attribute_names(self, visibility):
58+
names = []
59+
def get_node_names_in_category(node_category, prefix=''):
60+
for node_feature in node_category.GetFeatures():
61+
# Ensure node is available and readable
62+
if (not PySpin.IsAvailable(node_feature) or not
63+
PySpin.IsReadable(node_feature)):
64+
continue
65+
66+
# Get the feature name:
67+
feature_name = node_feature.GetName()
68+
69+
# Category nodes must be dealt with separately in order to retrieve subnodes recursively.
70+
if node_feature.GetPrincipalInterfaceType() == PySpin.intfICategory:
71+
get_node_names_in_category(PySpin.CCategoryPtr(node_feature),
72+
prefix=feature_name + '::')
73+
else:
74+
names.append(prefix + feature_name)
75+
76+
node = self.camera.GetNodeMap()
77+
get_node_names_in_category(PySpin.CCategoryPtr(node.GetNode('Root')))
78+
79+
return names
80+
81+
def get_attribute(self, name, stream_map=False):
82+
"""Return current values dictionary of attribute of the given name"""
83+
#print('Getting attribute %s.' % name)
84+
name = name.split('::')
85+
86+
if stream_map:
87+
nodemap = self.camera.GetTLStreamNodeMap()
88+
else:
89+
nodemap = self.camera.GetNodeMap()
90+
node = nodemap.GetNode(name[-1])
91+
92+
if PySpin.IsAvailable(node) and PySpin.IsReadable(node):
93+
if node.GetPrincipalInterfaceType() == PySpin.intfIInteger:
94+
return PySpin.CIntegerPtr(node).GetValue()
95+
elif node.GetPrincipalInterfaceType() == PySpin.intfIFloat:
96+
return PySpin.CFloatPtr(node).GetValue()
97+
elif node.GetPrincipalInterfaceType() == PySpin.intfIBoolean:
98+
return PySpin.CBooleanPtr(node).GetValue()
99+
else:
100+
return PySpin.CValuePtr(node).ToString()
101+
102+
def set_attributes(self, attr_dict):
103+
for k, v in attr_dict.items():
104+
self.set_attribute(k, v)
105+
106+
def set_stream_attribute(self, name, value):
107+
self.set_attribute(name, value, stream_map=True)
108+
109+
def set_attribute(self, name, value, stream_map=False):
110+
#print('Setting attribute %s.' % name)
111+
name = name.split('::')
112+
113+
if stream_map:
114+
nodemap = self.camera.GetTLStreamNodeMap()
115+
else:
116+
nodemap = self.camera.GetNodeMap()
117+
node = nodemap.GetNode(name[-1])
118+
119+
if PySpin.IsAvailable(node) and PySpin.IsWritable(node):
120+
if node.GetPrincipalInterfaceType() == PySpin.intfIInteger:
121+
PySpin.CIntegerPtr(node).SetValue(value)
122+
elif node.GetPrincipalInterfaceType() == PySpin.intfIFloat:
123+
PySpin.CFloatPtr(node).SetValue(value)
124+
elif node.GetPrincipalInterfaceType() == PySpin.intfIBoolean:
125+
PySpin.CBooleanPtr(node).SetValue(value)
126+
else:
127+
PySpin.CValuePtr(node).FromString(value)
128+
129+
sleep(0.05)
130+
# Sometimes this doesn't work, so let's check and print warnings if it
131+
# fails:
132+
name = '::'.join(name)
133+
return_value = self.get_attribute(name, stream_map=stream_map)
134+
if return_value != value:
135+
print('WARNING: setting attribute %s to %s failed. '%(name, str(value)) +
136+
'Returned value %s instead'%str(return_value))
137+
else:
138+
print('Successfully set %s to %s.'%(name, str(return_value)))
139+
else:
140+
print('WARNING: not capable of writing attribute %s.'%'::'.join(name))
141+
142+
143+
def snap(self):
144+
"""Acquire a single image and return it"""
145+
self.configure_acquisition(continuous=False, bufferCount=1)
146+
#self.trigger()
147+
image = self.grab()
148+
self.camera.EndAcquisition()
149+
return image
150+
151+
def grab(self):
152+
"""Grab and return single image during pre-configured acquisition."""
153+
#print('Grabbing...')
154+
image_result = self.camera.GetNextImage(self.timeout)
155+
img = self._decode_image_data(image_result.GetData())
156+
image_result.Release()
157+
return img
158+
159+
def grab_multiple(self, n_images, images):
160+
"""Grab n_images into images array during buffered acquistion."""
161+
print(f"Attempting to grab {n_images} images.")
162+
for i in range(n_images):
163+
if self._abort_acquisition:
164+
print("Abort during acquisition.")
165+
self._abort_acquisition = False
166+
return
167+
168+
images.append(self.grab())
169+
print(f"Got image {i+1} of {n_images}.")
170+
print(f"Got {len(images)} of {n_images} images.")
171+
172+
173+
def trigger(self):
174+
"""Execute software trigger"""
175+
nodemap = self.camera.GetNodeMap()
176+
trigger_cmd = PySpin.CCommandPtr(nodemap.GetNode('TriggerSoftware'))
177+
if not PySpin.IsAvailable(trigger_cmd) or not PySpin.IsWritable(trigger_cmd):
178+
print('WARNING: Unable to execute trigger. Aborting...')
179+
else:
180+
trigger_cmd.Execute()
181+
182+
183+
def configure_acquisition(self, continuous=True, bufferCount=10):
184+
self.pix_fmt = self.get_attribute('PixelFormat')
185+
self.height = self.get_attribute('Height')
186+
self.width = self.get_attribute('Width')
187+
188+
# Unless the camera settings are set properly, in cntinuous mode
189+
# the camera will generally move faster than BLACS, and so the buffer
190+
# will fill up. With a Flea3, I was unable to solve the prolem
191+
# easily. It really is quite annoying.
192+
if continuous:
193+
self.set_stream_attribute('StreamBufferCountMode', 'Manual')
194+
self.set_stream_attribute('StreamBufferCountManual', bufferCount)
195+
self.set_stream_attribute('StreamBufferHandlingMode', 'NewestFirst')
196+
self.set_attribute('AcquisitionMode', 'Continuous')
197+
elif bufferCount == 1:
198+
self.set_stream_attribute('StreamBufferCountMode', 'Auto')
199+
self.set_stream_attribute('StreamBufferHandlingMode', 'OldestFirst')
200+
self.set_attribute('AcquisitionMode', 'SingleFrame')
201+
else:
202+
self.set_stream_attribute('StreamBufferCountMode', 'Auto')
203+
self.set_stream_attribute('StreamBufferHandlingMode', 'OldestFirst')
204+
self.set_attribute('AcquisitionMode', 'MultiFrame')
205+
self.set_attribute('AcquisitionFrameCount', bufferCount)
206+
207+
self.camera.BeginAcquisition()
208+
209+
def _decode_image_data(self, img):
210+
"""Spinnaker image buffers require significant formatting.
211+
This returns what one would expect from a camera.
212+
configure_acquisition must be called first to set image format parameters."""
213+
if self.pix_fmt.startswith('Mono'):
214+
if self.pix_fmt.endswith('8'):
215+
dtype = 'uint8'
216+
else:
217+
dtype = 'uint16'
218+
image = np.frombuffer(img, dtype=dtype).reshape(self.height, self.width)
219+
else:
220+
msg = """Only MONO image types currently supported.
221+
To add other image types, add conversion logic from returned
222+
uint8 data to desired format in _decode_image_data() method."""
223+
raise ValueError(dedent(msg))
224+
return image.copy()
225+
226+
def stop_acquisition(self):
227+
print('Stopping acquisition...')
228+
self.camera.EndAcquisition()
229+
230+
# This is supposed to provide debugging info, but as with most things
231+
# in PySpin, it appears to be completely useless:.
232+
num_frames=self.get_attribute('StreamTotalBufferCount', stream_map=True)
233+
failed_frames=self.get_attribute('StreamFailedBufferCount', stream_map=True)
234+
underrun_frames=self.get_attribute('StreamBufferUnderrunCount', stream_map=True)
235+
print('Stream info: %d frames acquired, %d failed, %d underrun' %
236+
(num_frames, failed_frames, underrun_frames))
237+
238+
def abort_acquisition(self):
239+
print('Stopping acquisition...')
240+
self._abort_acquisition = True
241+
242+
def close(self):
243+
print('Closing down the camera...')
244+
self.camera.DeInit()
245+
self.camList.Clear()
246+
self.system.ReleaseInstance()
247+
248+
249+
class SpinnakerCameraWorker(IMAQdxCameraWorker):
250+
"""Spinnaker API Camera Worker.
251+
252+
Inherits from IMAQdxCameraWorker."""
253+
interface_class = Spinnaker_Camera
254+
255+
#def continuous_loop(self, dt):
256+
# """Acquire continuously in a loop, with minimum repetition interval dt"""
257+
# self.camera.trigger()
258+
# while True:
259+
# if dt is not None:
260+
# t = perf_counter()
261+
# image = self.camera.grab()
262+
# self.camera.trigger()
263+
# self._send_image_to_parent(image)
264+
# if dt is None:
265+
# timeout = 0
266+
# else:
267+
# timeout = t + dt - perf_counter()
268+
# if self.continuous_stop.wait(timeout):
269+
# self.continuous_stop.clear()
270+
# break
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/SpinnakerCamera/labscript_devices.py #
4+
# #
5+
# Copyright 2019, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
from labscript_devices.IMAQdxCamera.labscript_devices import IMAQdxCamera
15+
16+
class SpinnakerCamera(IMAQdxCamera):
17+
description = 'Spinnaker Camera'
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#####################################################################
2+
# #
3+
# /labscript_devices/SpinnakerCamera/register_classes.py #
4+
# #
5+
# Copyright 2019, Monash University and contributors #
6+
# #
7+
# This file is part of labscript_devices, in the labscript suite #
8+
# (see http://labscriptsuite.org), and is licensed under the #
9+
# Simplified BSD License. See the license.txt file in the root of #
10+
# the project for the full license. #
11+
# #
12+
#####################################################################
13+
from labscript_devices import register_classes
14+
15+
register_classes(
16+
'SpinnakerCamera',
17+
BLACS_tab='labscript_devices.SpinnakerCamera.blacs_tabs.SpinnakerCameraTab',
18+
runviewer_parser=None,
19+
)

0 commit comments

Comments
 (0)