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