Skip to content

Commit

Permalink
Refactoring, added possibility to customize layout, dependencies and …
Browse files Browse the repository at this point in the history
…units; added datatype choice_text
  • Loading branch information
Kasape committed Sep 20, 2019
1 parent 060fb11 commit 888357c
Show file tree
Hide file tree
Showing 4 changed files with 600 additions and 273 deletions.
289 changes: 217 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ which takes on the input raw camera output image and display your own output.
pip install pypylon-opencv-viewer
```

## Usage
## Initialization

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

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

Now we can start working with our viewer. Basically we need 3 things: connected camera, features we want to work with
(you can find them in [official Basler documentation](https://docs.baslerweb.com/#t=en%2Ffeatures.htm&rhsearch=sdk), for
now this library supports only boolean and numeric features) and image processing function we want to apply on grabbing
images. Image processing function is not a requirement, you don't have to specify one, in this case you'll get raw
camera output.

#### List of features

Features - list of dicts.

Dict structure:
1. `name` - camera pylon feature name, example: "GainRaw" (required)
1. `type` - widget input type, allowed values `int`, `float`, `bool`, `int_text`, `float_text` (optional, default: "int")
1. `value` - widget input value (optional, default: current camera feature value)
1. `max` - maximum widget input value, only numeric widget types (optional, default: camera feature max value)
1. `min` - minimum widget input value, only numeric widget types (optional, default: camera feature min value)
1. `step` - step of allowed input value (optional, default: camera feature increment, if not exist =1)
```python
from pypylon_opencv_viewer import BaslerOpenCVViewer
viewer = BaslerOpenCVViewer(camera)
```

### Configuration
Next step is to configure created viewer using method `set_configuration`, where passed value is dictionary with the following items:

features : list of dicts (required)
List of widgets configuration stored in
dictionaries with items:
name : str (required)
Camera pylon feature name, example: "GainRaw"
type : str (required)
widget input type, allowed values are {"int", "float", "bool", "int_text", "float_text", "choice_text"}
value : number or bool (optional, default: current camera feature value)
widget input value
max : number (optional, default: camera feature max value)
maximum widget input value, only numeric widget types
min : number (optional, default: camera feature min value)
minimum widget input value, only numeric widget types
step : number (optional, default: camera feature increment)
step of allowed input value
options: list, mandatory for type "choice_text",
sets values in list as options for ToggleButtons
unit: str (optional, default empty)
string shown at the end of label in the form "Label [unit]:"
dependency: dict, (optional, default empty)
defines how other widgets must be set to be this widget enabled
layout : dict (optional, default: {"width": '100%', "height": '50px', "align_items": "center"})
values are passed to widget's layout
style: dict, (optional, default {'description_width': 'initial'})
values are passed to widget's style

Example:
"features": {
"name": "GainRaw",
"type": "int",
"value": 20,
"max": 63,
"min": 10,
"step": 1,
"layout": {"width":"99%", "height": "50px")
"style": {"button_width": "90px"}
}
features_layout: list of tuples (optional, default is one widget per row)
List of features' widgets' name for reordering. Each tuple represents one row
Example:
"* features_layout": [
("Height", "Width"),
("OffsetX", "CenterX"),
("ExposureAuto", "ExposureTimeAbs"),
("AcquisitionFrameCount", "AcquisitionLineRateAbs")
],
actions_layout: list of tuples (optional, default is one widget per row)
List of actions' widgets' name for reordering. Each tuple represents one row.
Available widgets are StatusLabel, SaveConfig, LoadConfig, ContinuousShot, SingleShot, "UserSet"
* Example:
"action_layout": [
("StatusLabel"),
("SaveConfig", "LoadConfig", "ContinuousShot", "SingleShot"),
("UserSet")
]
default_user_set: string (optional, default is None)
If value is None, widget for selecting UserSet is displayed.
Otherwise is set to given value in ["UserSet1", "UserSet2", "UserSet3"]
* Example:
"default_user_set": "UserSet3"

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).

Example configuration you can see below:

```python
# List of features to create wigets
features = [
{
"name": "GainRaw",
"type": "int"
},
{
"name": "Height",
"type": "int_text",
"max": 1000,
"min": 100,
"step": "5"
},
{
"name": "Width",
"type": "int_text",
"max": 1000,
"min": 100,
"step": "5"
},
{
"name": "AcquisitionFrameRateEnable",
"type": "bool"
},
{
"name": "AcquisitionFrameRateAbs",
"type": "int",
"max": 60,
"min": 10
}
]
# Example of configuration for basic RGB camera's features
VIEWER_CONFIG_RGB_MATRIX = {
"features": [
{
"name": "GainRaw",
"type": "int",
"step": 1,
},
{
"name": "Height",
"type": "int",
"value": 1080,
"unit": "px",
"step": 2,
},
{
"name": "Width",
"type": "int",
"value": 1920,
"unit": "px",
"step": 2,
},
{
"name": "CenterX",
"type": "bool",
},
{
"name": "CenterY",
"type": "bool",

},
{
"name": "OffsetX",
"type": "int",
"dependency": {"CenterX": False},
"unit": "px",
"step": 2,
},
{
"name": "OffsetY",
"type": "int",
"dependency": {"CenterY": False},
"unit": "px",
"step": 2,
},
{
"name": "AcquisitionFrameRateAbs",
"type": "int",
"unit": "fps",
"dependency": {"AcquisitionFrameRateEnable": True},
"max": 150,
"min": 1,
},
{
"name": "AcquisitionFrameRateEnable",
"type": "bool",
},
{
"name": "ExposureAuto",
"type": "choice_text",
"options": ["Off", "Once", "Continuous"],
"style": {"button_width": "90px"}
},
{
"name": "ExposureTimeAbs",
"type": "int",
"dependency": {"ExposureAuto": "Off"},
"unit": "μs",
"step": 100,
"max": 35000,
"min": 500,
},
{
"name": "BalanceWhiteAuto",
"type": "choice_text",
"options": ["Off", "Once", "Continuous"],
"style": {"button_width": "90px"}
},
],
"features_layout": [
("Height", "Width"),
("OffsetX", "CenterX"),
("OffsetY", "CenterY"),
("ExposureAuto", "ExposureTimeAbs"),
("AcquisitionFrameRateAbs", "AcquisitionFrameRateEnable"),
("BalanceWhiteAuto", "GainRaw")
],
"actions_layout": [
("StatusLabel"),
("SaveConfig", "LoadConfig", "ContinuousShot", "SingleShot"),
("UserSet")
],
"default_user_set": "UserSet3",
}
viewer.set_configuration(VIEWER_CONFIG_RGB_MATRIX)

```

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

```python
import numpy as np
import cv2
#### Image processing function
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.

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

#### Viewer
We have prepared all required parts. Now we just set them to the viewer object and launch image grabbing:
`run_interaction_continuous_shot` for continuous or `run_interaction_single_shot` for single shot.
Also you can press 'S' button to save raw camera image or impro function return value to `image_folder`.
```python
from pypylon_opencv_viewer import BaslerOpenCVViewer

viewer = BaslerOpenCVViewer(camera)
viewer.set_features(features)
viewer.set_impro_function(impro)
viewer.run_interaction_continuous_shot(image_folder='~/Documents/images')
```
We have already created our viewer and set its configuration. Now we can display defined widgets using method `show_interactive_panel`
with parameters `image_folder` and `window_size`.
The panel contains 4 buttons:
1. Save configuration - save current values of features to camera's inner memory (UserSet)
1. Load configuration - load values of features from camera's inner memory (UserSet) to the widgets
1. Continuous shot - start streaming frames from the camera
1. Single shot - grab a one frame

Now we see some similar image, we can setup camera features values. Push `Run interaction` to let it go.
To close OpenCV windows just push 'Q' on your keyboard. You don't have to launch this cell once more to try the same
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`.
To close OpenCV windows just push 'q' on the keyboard. We don't have to launch this cell once more to try the same
procedure with the image, just change wanted values and push the button. That's it!
![Basler OpenCV viewer](https://raw.githubusercontent.com/mbalatsko/pypylon-opencv-viewer/master/images/wiget.PNG)
![Basler OpenCV viewer](https://raw.githubusercontent.com/mbalatsko/pypylon-opencv-viewer/master/images/opened.PNG)

For configuration above we should see this interactive panel:
![Basler OpenCV viewer](images/widget.png)

#### Example
We can use our viewer along with more complex image processing function for detection of numbers:
```python
def impro(img):
img_rgb = img.copy()
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
_, img_gray = cv2.threshold(img_gray, 170, 255, cv2.THRESH_BINARY)
img_gray = 255 - img_gray
_, contours, _ = cv2.findContours(img_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
selected_contours = []
for c in contours:
contour_area = cv2.contourArea(c)
x,y,w,h = cv2.boundingRect(c)
bounding_rect_area = w*h
if(contour_area > 80 and contour_area/bounding_rect_area < 0.75):
selected_contours.append(c)

cv2.drawContours(img_rgb, selected_contours, -1, (0,0,255), thickness=cv2.FILLED)
img = cv2.putText(img, "Original", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,0,0), 8)
img_rgb = cv2.putText(img_rgb, "Found numbers", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,0,0), 8)
return np.hstack([img, img_rgb])
```
![Number detection](images/impro-function-example.png)

#### Save or get image from camera

Expand Down
Binary file added images/impro-function-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 888357c

Please sign in to comment.