Skip to content

Commit b0219f5

Browse files
authored
Merge pull request #3 from Kasape/master
Refactoring, added possibility to customize layout, dependencies and units; added datatype choice_text
2 parents 060fb11 + 888357c commit b0219f5

File tree

4 files changed

+600
-273
lines changed

4 files changed

+600
-273
lines changed

README.md

Lines changed: 217 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ which takes on the input raw camera output image and display your own output.
1515
pip install pypylon-opencv-viewer
1616
```
1717

18-
## Usage
18+
## Initialization
1919

20-
To start working, launch Jupyter notebook and connect to Basler camera. Here is an example how you can do it:
20+
To start working, launch Jupyter notebook and connect to Basler camera. Here is an example how we can do it:
2121
```python
2222
from pypylon import pylon
2323

@@ -36,94 +36,239 @@ if info is not None:
3636
camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateDevice(info))
3737
camera.Open()
3838
```
39+
When our camera is connected and open, we can initialize our viewer with it:
3940

40-
Now we can start working with our viewer. Basically we need 3 things: connected camera, features we want to work with
41-
(you can find them in [official Basler documentation](https://docs.baslerweb.com/#t=en%2Ffeatures.htm&rhsearch=sdk), for
42-
now this library supports only boolean and numeric features) and image processing function we want to apply on grabbing
43-
images. Image processing function is not a requirement, you don't have to specify one, in this case you'll get raw
44-
camera output.
45-
46-
#### List of features
47-
48-
Features - list of dicts.
49-
50-
Dict structure:
51-
1. `name` - camera pylon feature name, example: "GainRaw" (required)
52-
1. `type` - widget input type, allowed values `int`, `float`, `bool`, `int_text`, `float_text` (optional, default: "int")
53-
1. `value` - widget input value (optional, default: current camera feature value)
54-
1. `max` - maximum widget input value, only numeric widget types (optional, default: camera feature max value)
55-
1. `min` - minimum widget input value, only numeric widget types (optional, default: camera feature min value)
56-
1. `step` - step of allowed input value (optional, default: camera feature increment, if not exist =1)
41+
```python
42+
from pypylon_opencv_viewer import BaslerOpenCVViewer
43+
viewer = BaslerOpenCVViewer(camera)
44+
```
45+
46+
### Configuration
47+
Next step is to configure created viewer using method `set_configuration`, where passed value is dictionary with the following items:
48+
49+
features : list of dicts (required)
50+
List of widgets configuration stored in
51+
dictionaries with items:
52+
name : str (required)
53+
Camera pylon feature name, example: "GainRaw"
54+
type : str (required)
55+
widget input type, allowed values are {"int", "float", "bool", "int_text", "float_text", "choice_text"}
56+
value : number or bool (optional, default: current camera feature value)
57+
widget input value
58+
max : number (optional, default: camera feature max value)
59+
maximum widget input value, only numeric widget types
60+
min : number (optional, default: camera feature min value)
61+
minimum widget input value, only numeric widget types
62+
step : number (optional, default: camera feature increment)
63+
step of allowed input value
64+
options: list, mandatory for type "choice_text",
65+
sets values in list as options for ToggleButtons
66+
unit: str (optional, default empty)
67+
string shown at the end of label in the form "Label [unit]:"
68+
dependency: dict, (optional, default empty)
69+
defines how other widgets must be set to be this widget enabled
70+
layout : dict (optional, default: {"width": '100%', "height": '50px', "align_items": "center"})
71+
values are passed to widget's layout
72+
style: dict, (optional, default {'description_width': 'initial'})
73+
values are passed to widget's style
74+
75+
Example:
76+
"features": {
77+
"name": "GainRaw",
78+
"type": "int",
79+
"value": 20,
80+
"max": 63,
81+
"min": 10,
82+
"step": 1,
83+
"layout": {"width":"99%", "height": "50px")
84+
"style": {"button_width": "90px"}
85+
}
86+
features_layout: list of tuples (optional, default is one widget per row)
87+
List of features' widgets' name for reordering. Each tuple represents one row
88+
Example:
89+
"* features_layout": [
90+
("Height", "Width"),
91+
("OffsetX", "CenterX"),
92+
("ExposureAuto", "ExposureTimeAbs"),
93+
("AcquisitionFrameCount", "AcquisitionLineRateAbs")
94+
],
95+
actions_layout: list of tuples (optional, default is one widget per row)
96+
List of actions' widgets' name for reordering. Each tuple represents one row.
97+
Available widgets are StatusLabel, SaveConfig, LoadConfig, ContinuousShot, SingleShot, "UserSet"
98+
* Example:
99+
"action_layout": [
100+
("StatusLabel"),
101+
("SaveConfig", "LoadConfig", "ContinuousShot", "SingleShot"),
102+
("UserSet")
103+
]
104+
default_user_set: string (optional, default is None)
105+
If value is None, widget for selecting UserSet is displayed.
106+
Otherwise is set to given value in ["UserSet1", "UserSet2", "UserSet3"]
107+
* Example:
108+
"default_user_set": "UserSet3"
109+
110+
The only required and also most important item in the dictionary above is a list of features you want to control. Their names can be found in [official Basler documentation](https://docs.baslerweb.com/#t=en%2Ffeatures.htm&rhsearch=sdk).
57111

58112
Example configuration you can see below:
59113

60114
```python
61-
# List of features to create wigets
62-
features = [
63-
{
64-
"name": "GainRaw",
65-
"type": "int"
66-
},
67-
{
68-
"name": "Height",
69-
"type": "int_text",
70-
"max": 1000,
71-
"min": 100,
72-
"step": "5"
73-
},
74-
{
75-
"name": "Width",
76-
"type": "int_text",
77-
"max": 1000,
78-
"min": 100,
79-
"step": "5"
80-
},
81-
{
82-
"name": "AcquisitionFrameRateEnable",
83-
"type": "bool"
84-
},
85-
{
86-
"name": "AcquisitionFrameRateAbs",
87-
"type": "int",
88-
"max": 60,
89-
"min": 10
90-
}
91-
]
115+
# Example of configuration for basic RGB camera's features
116+
VIEWER_CONFIG_RGB_MATRIX = {
117+
"features": [
118+
{
119+
"name": "GainRaw",
120+
"type": "int",
121+
"step": 1,
122+
},
123+
{
124+
"name": "Height",
125+
"type": "int",
126+
"value": 1080,
127+
"unit": "px",
128+
"step": 2,
129+
},
130+
{
131+
"name": "Width",
132+
"type": "int",
133+
"value": 1920,
134+
"unit": "px",
135+
"step": 2,
136+
},
137+
{
138+
"name": "CenterX",
139+
"type": "bool",
140+
},
141+
{
142+
"name": "CenterY",
143+
"type": "bool",
144+
145+
},
146+
{
147+
"name": "OffsetX",
148+
"type": "int",
149+
"dependency": {"CenterX": False},
150+
"unit": "px",
151+
"step": 2,
152+
},
153+
{
154+
"name": "OffsetY",
155+
"type": "int",
156+
"dependency": {"CenterY": False},
157+
"unit": "px",
158+
"step": 2,
159+
},
160+
{
161+
"name": "AcquisitionFrameRateAbs",
162+
"type": "int",
163+
"unit": "fps",
164+
"dependency": {"AcquisitionFrameRateEnable": True},
165+
"max": 150,
166+
"min": 1,
167+
},
168+
{
169+
"name": "AcquisitionFrameRateEnable",
170+
"type": "bool",
171+
},
172+
{
173+
"name": "ExposureAuto",
174+
"type": "choice_text",
175+
"options": ["Off", "Once", "Continuous"],
176+
"style": {"button_width": "90px"}
177+
},
178+
{
179+
"name": "ExposureTimeAbs",
180+
"type": "int",
181+
"dependency": {"ExposureAuto": "Off"},
182+
"unit": "μs",
183+
"step": 100,
184+
"max": 35000,
185+
"min": 500,
186+
},
187+
{
188+
"name": "BalanceWhiteAuto",
189+
"type": "choice_text",
190+
"options": ["Off", "Once", "Continuous"],
191+
"style": {"button_width": "90px"}
192+
},
193+
],
194+
"features_layout": [
195+
("Height", "Width"),
196+
("OffsetX", "CenterX"),
197+
("OffsetY", "CenterY"),
198+
("ExposureAuto", "ExposureTimeAbs"),
199+
("AcquisitionFrameRateAbs", "AcquisitionFrameRateEnable"),
200+
("BalanceWhiteAuto", "GainRaw")
201+
],
202+
"actions_layout": [
203+
("StatusLabel"),
204+
("SaveConfig", "LoadConfig", "ContinuousShot", "SingleShot"),
205+
("UserSet")
206+
],
207+
"default_user_set": "UserSet3",
208+
}
209+
viewer.set_configuration(VIEWER_CONFIG_RGB_MATRIX)
210+
92211
```
93212

94-
#### Example image processing function
95-
Just example image processing function, which negatives the image. Image has to be the only argument in it.
96-
If you want some image to be shown, you have to do it yourself inside the function. DON'T DESTROY
97-
ALL OpenCV windows or wait for key pressed in it.
98213

99-
```python
100-
import numpy as np
101-
import cv2
214+
#### Image processing function
215+
We can also define image processing function that we want to apply on grabbed images using method `set_impro_function`. If we don't specify one, we will get raw camera output.
102216

217+
The given function must either return processed image:
218+
```python
219+
def impro(img):
220+
return np.hstack([img, (255-img)])
221+
viewer.set_impro_function(impro)
222+
```
223+
or display it using cv2.namedWindow. In this case we must specify `own_window=True` to disable showing of default window.
224+
```python
103225
def impro(img):
104226
cv2.namedWindow('1', cv2.WINDOW_NORMAL | cv2.WINDOW_GUI_NORMAL)
105227
cv2.resizeWindow('1', 1080, 720)
106228
cv2.imshow("1", np.hstack([img, (255-img)]))
229+
viewer.set_impro_function(impro, own_window=True)
107230
```
231+
In both cases, DON'T DESTROY ALL OpenCV windows or wait for key pressed in it!
108232

109233
#### Viewer
110-
We have prepared all required parts. Now we just set them to the viewer object and launch image grabbing:
111-
`run_interaction_continuous_shot` for continuous or `run_interaction_single_shot` for single shot.
112-
Also you can press 'S' button to save raw camera image or impro function return value to `image_folder`.
113-
```python
114-
from pypylon_opencv_viewer import BaslerOpenCVViewer
115-
116-
viewer = BaslerOpenCVViewer(camera)
117-
viewer.set_features(features)
118-
viewer.set_impro_function(impro)
119-
viewer.run_interaction_continuous_shot(image_folder='~/Documents/images')
120-
```
234+
We have already created our viewer and set its configuration. Now we can display defined widgets using method `show_interactive_panel`
235+
with parameters `image_folder` and `window_size`.
236+
The panel contains 4 buttons:
237+
1. Save configuration - save current values of features to camera's inner memory (UserSet)
238+
1. Load configuration - load values of features from camera's inner memory (UserSet) to the widgets
239+
1. Continuous shot - start streaming frames from the camera
240+
1. Single shot - grab a one frame
121241

122-
Now we see some similar image, we can setup camera features values. Push `Run interaction` to let it go.
123-
To close OpenCV windows just push 'Q' on your keyboard. You don't have to launch this cell once more to try the same
242+
Also we can press 's' key to save raw camera image or impro function return value (but only when own_window=False) to `image_folder`.
243+
To close OpenCV windows just push 'q' on the keyboard. We don't have to launch this cell once more to try the same
124244
procedure with the image, just change wanted values and push the button. That's it!
125-
![Basler OpenCV viewer](https://raw.githubusercontent.com/mbalatsko/pypylon-opencv-viewer/master/images/wiget.PNG)
126-
![Basler OpenCV viewer](https://raw.githubusercontent.com/mbalatsko/pypylon-opencv-viewer/master/images/opened.PNG)
245+
246+
For configuration above we should see this interactive panel:
247+
![Basler OpenCV viewer](images/widget.png)
248+
249+
#### Example
250+
We can use our viewer along with more complex image processing function for detection of numbers:
251+
```python
252+
def impro(img):
253+
img_rgb = img.copy()
254+
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
255+
_, img_gray = cv2.threshold(img_gray, 170, 255, cv2.THRESH_BINARY)
256+
img_gray = 255 - img_gray
257+
_, contours, _ = cv2.findContours(img_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
258+
selected_contours = []
259+
for c in contours:
260+
contour_area = cv2.contourArea(c)
261+
x,y,w,h = cv2.boundingRect(c)
262+
bounding_rect_area = w*h
263+
if(contour_area > 80 and contour_area/bounding_rect_area < 0.75):
264+
selected_contours.append(c)
265+
266+
cv2.drawContours(img_rgb, selected_contours, -1, (0,0,255), thickness=cv2.FILLED)
267+
img = cv2.putText(img, "Original", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,0,0), 8)
268+
img_rgb = cv2.putText(img_rgb, "Found numbers", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,0,0), 8)
269+
return np.hstack([img, img_rgb])
270+
```
271+
![Number detection](images/impro-function-example.png)
127272

128273
#### Save or get image from camera
129274

images/impro-function-example.png

945 KB
Loading

images/widget.png

65.1 KB
Loading

0 commit comments

Comments
 (0)