CVBpy 14.1
cvb/QmlGenDCDisplay
1numpy==1.25.1 # this is just a recommendation
2Pillow==9.4.0 # this is just a recommendation
3PySide6==6.6.1 # this is just a recommendation
1import os
2import sys
3import time
4import numpy as np
5import cvb
6from PIL import Image, ImageDraw, ImageFont
7
8if sys.version_info >= (3, 11):
9 from PySide6.QtCore import Qt, QThread, Signal, Slot
10 from PySide6.QtGui import QAction, QImage, QPixmap, QIcon, QStandardItemModel, QStandardItem
11 from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QSizePolicy,
12 QVBoxLayout, QWidget, QLabel, QTableView, QHeaderView, QHBoxLayout)
13else:
14 from PySide2.QtCore import Qt, QThread, Signal, Slot
15 from PySide2.QtGui import QAction, QImage, QPixmap, QIcon, QStandardItemModel, QStandardItem
16 from PySide2.QtWidgets import (QApplication, QMainWindow, QPushButton, QSizePolicy,
17 QVBoxLayout, QWidget, QLabel, QTableView, QHeaderView, QHBoxLayout)
18
19# Set MOCK = True for testing with mock cameras
20# Set MOCK = False when using real GenDC cameras
21MOCK = True
22OUTPUT_SIZE = (600, 600)
23
24# Mapping of CompositePurpose values to human-readable names
25purpose_list = {
26 cvb.CompositePurpose.Custom: "Custom",
27 cvb.CompositePurpose.Image: "Image",
28 cvb.CompositePurpose.PointCloud: "PointCloud",
29 cvb.CompositePurpose.ImageList: "ImageList"
30}
31
32# Function to get purpose from an index
33def purpose_from_index(index):
34 if 0 <= index < len(purpose_list):
35 return purpose_list[index]
36 else:
37 return "Incompatible purpose"
38
39# Function to resize an image
40def resize_image(image, target_size):
41 pil_image = Image.fromarray(image)
42 pil_image = pil_image.resize(target_size, Image.LANCZOS)
43 return np.array(pil_image)
44
45# Function to format an image
46def format_image(image, mode="RGB"):
47 pil_image = Image.fromarray(image)
48 if pil_image.mode != mode:
49 pil_image = pil_image.convert(mode)
50 return np.array(pil_image)
51
52# Function to prepare the device
53def prepare_device(device):
54 nm = device.node_maps["Device"]
55 node_tlpl = nm.nodes["Std::TLParamsLocked"]
56 node_tlpl.value = 0
57 if MOCK:
58 node_count = nm.nodes["Cust::TestGenDCImageCount"]
59 node_count.value = 2
60 node_mode = nm.nodes["Cust::GenDCFlowMappingConfiguration"]
61 node_mode.value = "MultipleFlow"
62
63 node_streamingmode = nm.nodes["Std::GenDCStreamingMode"]
64 node_streamingmode.value = "On"
65
66 node_tlpl = nm.nodes["Std::TLParamsLocked"]
67 node_tlpl.value = 1
68
69# Function to create an image with text
70def create_image_with_text(
71 text, image_size=(
72 400, 200), bg_color=(
73 255, 255, 255), text_color=(0, 0, 0)):
74 image = Image.new("RGB", image_size, bg_color)
75 try:
76 font = ImageFont.load_default()
77 except OSError:
78 font = None
79
80 if font:
81 draw = ImageDraw.Draw(image)
82 text_width, text_height = draw.textbbox.textsize(text, font)
83 x = (image_size[0] - text_width) // 2
84 y = (image_size[1] - text_height) // 2
85 draw.text((x, y), text, fill=text_color, font=font)
86 image_array = np.array(image)
87 else:
88 image_array = None
89
90 return image_array
91
92# Function to evaluate a composite and extract images and information
93def evaluate_composite(composite, id):
94 images = []
95 table = []
96
97 table.append(f"Image ID: {id}")
98 table.append(f"Composite purpose: {purpose_from_index(composite.purpose)}")
99 table.append(f"Composite element count: {composite.item_count}")
100
101 for n in range(composite.item_count):
102 item = composite[n]
103 item_type = "unknown"
104 img = None
105
106 if isinstance(item, cvb.Image):
107 item_type = "image"
108 img = cvb.to_array(item)
109
110 elif isinstance(item, cvb.Plane):
111 item_type = "plane"
112 img = create_image_with_text("part is a plane")
113 elif isinstance(item, cvb.PlaneEnumerator):
114 item_type = "plane enumerator"
115 img = create_image_with_text("part is a plane enumerator")
116 elif isinstance(item, cvb.PFNCBuffer):
117 item_type = "pfnc buffer"
118 img = create_image_with_text("part is a PFNC buffer")
119 else:
120 img = create_image_with_text("unknown part type")
121
122 table.append(f"Composite item #{n}: {item_type}")
123
124 img = format_image(img)
125 images.append(img)
126
127 return images, table
128
129# Function to subdivide and arrange images in a grid
130def subdivide_images(images):
131 num_images = len(images)
132 side_length = int(num_images ** 0.5)
133 num_rows = num_cols = side_length
134
135 if num_rows * num_cols < num_images:
136 num_cols += 1
137 if num_rows * num_cols < num_images:
138 num_rows += 1
139
140 sub_image_width = OUTPUT_SIZE[1] // num_cols
141 sub_image_height = OUTPUT_SIZE[0] // num_rows
142
143 output_image = np.zeros(
144 (OUTPUT_SIZE[0], OUTPUT_SIZE[1], 3), dtype=np.uint8)
145
146 for idx, image in enumerate(images):
147 sub_image = resize_image(image, (sub_image_width, sub_image_height))
148 row = idx // num_cols
149 col = idx % num_cols
150 output_image[row * sub_image_height:(row + 1) * sub_image_height,
151 col * sub_image_width:(col + 1) * sub_image_width] = sub_image
152
153 return output_image
154
155# Class for the acquisition thread
156# It continuously captures images from the device and updates the UI with new frames.
157class AcquisitionThread(QThread):
158
159 updateFrame = Signal(np.ndarray)
160 updateTable = Signal(list)
161
162 def __init__(self, parent=None):
163 QThread.__init__(self, parent)
164 self.status = True
165
166 def run(self):
167 if MOCK:
169 cvb.DiscoverFlags.IgnoreVins | cvb.DiscoverFlags.IncludeMockTL, time_span=300)
170 else:
172 cvb.DiscoverFlags.IgnoreVins, time_span=300)
173
174 if devices:
175 device_token = next(iter([dev.access_token for dev in devices if
176 (MOCK and "MockTL" in dev.access_token) or
177 (not MOCK and "SD" in dev.access_token)]), None)
178
179 if not device_token:
180 raise RuntimeError("No suitable device found.")
181
182 with cvb.DeviceFactory.open(device_token, cvb.AcquisitionStack.GenTL) as device:
183 device: cvb.GenICamDevice = device
184 prepare_device(device)
185 stream = device.stream(cvb.CompositeStream)
186 stream.start()
187 image_count = 0
188
189 while self.status:
190 wait_result = stream.wait()
191 composite: cvb.Composite = wait_result[0]
192
193 images, table = evaluate_composite(composite, image_count)
194 if len(images) > 1:
195 output_image = subdivide_images(images)
196 else:
197 output_image = images[0]
198
199 output_image_copy = output_image.copy()
200 # Emitting the updateFrame signal to update the UI with the new image and table data
201 self.updateFrame.emit(output_image_copy)
202 self.updateTable.emit(table)
203 image_count += 1
204
205 stream.stop()
206
207 sys.exit(-1)
208
209# GUI Window Interface
210# Window: This class represents the main window of the application. It sets up the user interface,
211# including image display, control buttons, and data tables. It also manages the interaction
212# between the UI and the acquisition thread.
213class Window(QMainWindow):
214
215 def __init__(self):
216 super().__init__()
217 self.setWindowTitle("CVB GenDC Demo")
218 self.setGeometry(0, 0, 800, 700)
219 script_dir = os.path.dirname(os.path.realpath(__file__))
220 icon_path = os.path.join(script_dir, 'Tutorial-Python_32x32.png')
221 self.setWindowIcon(QIcon(icon_path))
222
223 menu = self.menuBar()
224 menu_file = menu.addMenu("File")
225 exit = QAction("Exit", self, triggered=qApp.quit)
226 menu_file.addAction(exit)
227
228 # Initialize and configure the UI components like labels, buttons, and table view.
229 self.label = QLabel(self)
230 self.label.setAlignment(Qt.AlignCenter)
231 self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
232
233 buttons_layout = QHBoxLayout()
234 self.button1 = QPushButton("Start")
235 self.button2 = QPushButton("Stop/Close")
236 self.button1.setMaximumWidth(100)
237 self.button2.setMaximumWidth(100)
238 self.button1.clicked.connect(self.start)
239 self.button2.clicked.connect(self.kill_thread)
240 self.button2.setEnabled(False)
241 buttons_layout.addWidget(self.button1)
242 buttons_layout.addWidget(self.button2)
243
244 self.table_model = QStandardItemModel(0, 1, self)
245 self.table_view = QTableView(self)
246 self.table_view.setModel(self.table_model)
247 self.table_view.verticalHeader().setVisible(False)
248 self.table_view.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
249
250 # Organizing the layout
251 table_widget = QWidget()
252 table_layout = QVBoxLayout()
253 table_layout.addWidget(self.table_view)
254 table_widget.setLayout(table_layout)
255 table_widget.setSizePolicy(
256 QSizePolicy.Expanding, QSizePolicy.Preferred)
257 image_widget = QWidget()
258 image_layout = QVBoxLayout()
259 image_layout.addWidget(self.label)
260 image_widget.setLayout(image_layout)
261 central_widget = QWidget(self)
262 central_layout = QVBoxLayout()
263 central_layout.addWidget(image_widget)
264 central_layout.addLayout(buttons_layout)
265 central_layout.addWidget(table_widget)
266 central_widget.setLayout(central_layout)
267 self.setCentralWidget(central_widget)
268
269 # Set up the acquisition thread for handling image capture and processing.
270 self.th = AcquisitionThread(self)
271 self.th.finished.connect(self.close)
272 self.th.updateFrame.connect(self.set_image)
273 self.th.updateTable.connect(self.update_table)
274
275 # Create a dummy numpy array for the initial image
276 dummy_array = np.zeros(
277 (OUTPUT_SIZE[0], OUTPUT_SIZE[1], 3), dtype=np.uint8)
278 self.set_image(dummy_array)
279
280 dummy_table_data = ["Press start to begin GenDC streaming"]
281 self.update_table(dummy_table_data)
282
283 @Slot(np.ndarray)
284 def set_image(self, np_image):
285 h, w, ch = np_image.shape
286 q_img = QImage(np_image.data, w, h, ch * w, QImage.Format_RGB888)
287 scaled_image = QPixmap.fromImage(q_img)
288 self.label.setPixmap(scaled_image)
289
290 @Slot()
291 def kill_thread(self):
292 print("Finishing...")
293 self.button2.setEnabled(False)
294 self.button1.setEnabled(True)
295 self.status = False
296 self.th.terminate()
297 time.sleep(1)
298
299 @Slot()
300 def start(self):
301 print("Starting...")
302 text = []
303 text.append("Starting...")
304 self.update_table(text)
305 self.button2.setEnabled(True)
306 self.button1.setEnabled(False)
307 self.th.start()
308
309 @Slot(list)
310 def update_table(self, data):
311 self.table_model.removeRows(0, self.table_model.rowCount())
312 self.table_model.setHorizontalHeaderLabels(["Infos"])
313
314 for item in data:
315 self.table_model.appendRow(QStandardItem(item))
316
317 self.table_view.horizontalHeader().setSectionResizeMode(
318 QHeaderView.ResizeToContents)
319
320
321if __name__ == "__main__":
322 app = QApplication(sys.argv)
323
324 if sys.platform == 'win32':
325 import ctypes
326 myappid = u'stemmerimaging.commonvisionblox.pystreamdisplay.0'
327 ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
328
329 w = Window()
330 w.show()
331 sys.exit(app.exec())
The Common Vision Blox composite.
Definition: __init__.py:745
The composite stream class.
Definition: __init__.py:782
Union[cvb.GenICamDevice, cvb.VinDevice, cvb.EmuDevice, cvb.VideoDevice, cvb.NonStreamingDevice] open(str provider, int acquisition_stack=cvb.AcquisitionStack.PreferVin)
Opens a device with the given provider and acquisition stack.
Definition: __init__.py:1570
List[cvb.DiscoveryInformation] discover_from_root(int flags=cvb.DiscoverFlags.FindAll, int time_span=300)
Discovers available devices / nodes depending on the given flags.
Definition: __init__.py:1550
A GenICam compliant device.
Definition: __init__.py:2022
The Common Vision Blox image.
Definition: __init__.py:2038
PFNC buffer class implementing the IPFNCBuffer interface.
Definition: __init__.py:3856
A collection of planes.
Definition: __init__.py:4208
Plane container.
Definition: __init__.py:4066
numpy.array to_array(Any buffer)
Copies buffer values of a cvb object to a newly created numpy array.
Definition: __init__.py:8527