Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pymodaq_plugins_opencv
.. the following must be adapted to your developped package, links to pypi, github description...

.. image:: https://img.shields.io/pypi/v/pymodaq_plugins_opencv.svg
:target: https://pypi.org/project/pymodaq_plugins_thorlabs/
:target: https://pypi.org/project/pymodaq_plugins_opencv/
:alt: Latest Version

.. image:: https://readthedocs.org/projects/pymodaq/badge/?version=latest
Expand Down Expand Up @@ -34,6 +34,22 @@ Viewer2D

* **opencv**: control of cameras using the opencv library (mainly webcams)

It is built on the camera base class of ``pymodaq_plugins_utils`` (``CameraBase``), with the driver
``hardware/opencv_camera.py`` that follows the camera driver interface of that package. So it has the same
settings as the other camera plugins: live and snap, averaging, the ROI drawn in the viewer (*Update ROI from
Viewer*, *Apply ROI*, *Clear ROI+Bin*), binning and FPS. A webcam has neither a hardware ROI nor binning: they are
applied on each image (binning is the mean of the pixels).

* *Camera index*: the camera to open (0 is usually the one built in the laptop).
* *Color Conversion*: OpenCV gives colour images, converted by default to gray (``RGB2GRAY``). With ``None``
the three channels (red, green, blue) are given.
* *Cam. Settings*: the properties that OpenCV can read on the camera (brightness, gain, exposure...), the unit
of some depends on the backend. Changing the frame width or height there resets the ROI. *Open Settings* shows
the settings window of the driver of the camera (DirectShow, Windows).

The exposure time and buffer settings of the camera base class are hidden: OpenCV does not set them in a portable
way, use *Cam. Settings*.

.. if needed use this field

Infos
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies = [
"pymodaq_utils>=5.3.1",
"pymodaq_gui>=5.3.0",
"pymodaq_data>=5.3.0",
"pymodaq_plugins_utils>=5.3.1",
'opencv-python',
]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,64 @@
from sys import platform
from copy import deepcopy

import numpy as np
import cv2

from pymodaq_utils.utils import ThreadCommand
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
from pymodaq_utils.logger import set_logger, get_module_name
from pymodaq_gui.parameter import Parameter

from pymodaq_plugins_opencv.hardware.opencv import OpenCVProp
from pymodaq.control_modules.viewer_utility_classes import comon_parameters, main

from pymodaq_plugins_utils.hardware.camera_base import CameraBase, cam_params

class DAQ_2DViewer_OpenCV(DAQ_Viewer_base):
"""
from pymodaq_plugins_opencv.hardware.opencv_camera import OpenCVCamera

logger = set_logger(get_module_name(__file__))


def webcam_params() -> list:
"""The settings of ``CameraBase`` that suit a webcam: colour image converted to gray by default, no exposure
time nor buffer (OpenCV sets neither, see ``Cam. Settings``)"""
params = deepcopy(cam_params)
for param in params:
if param['name'] == 'color_conversion':
param['value'] = 'RGB2GRAY'
elif param['name'] == 'buffer':
param['visible'] = False
elif param['name'] == 'timing_opts':
for child in param['children']:
if child['name'] == 'exposure_time':
child['visible'] = False
return params


class DAQ_2DViewer_OpenCV(CameraBase):
"""Cameras read with OpenCV (webcams, any device handled by ``cv2.VideoCapture``)

The acquisition, the live mode, the averaging, the ROI drawn in the viewer, the binning (both done on the image
since a webcam has no ROI) and the FPS are those of ``CameraBase``. The images are RGB: select a colour
conversion (default RGB2GRAY) or None to get the three channels.

``Cam. Settings`` lists the properties of the camera that OpenCV can read (brightness, gain, exposure...), the
unit of some of them depends on the backend of OpenCV. Changing the width or the height there resets the ROI.
"""
params = comon_parameters + \
[{'title': 'Camera index:', 'name': 'camera_index', 'type': 'int', 'value': 0, 'default': 0, 'min': 0},
{'title': 'Colors:', 'name': 'colors', 'type': 'list', 'value': 'gray', 'limits': ['gray', 'RGB']},
{'title': 'Open Settings:', 'name': 'open_settings', 'type': 'bool', 'value': False},
{'title': 'Cam. Settings:', 'name': 'cam_settings', 'type': 'group', 'children': [
# {'title': 'Autoexposure:', 'name': 'autoexposure', 'type': 'bool', 'value': False},
# {'title': 'Exposure:', 'name': 'exposure', 'type': 'int', 'value': 0},
]},
]

def ini_attributes(self):
# TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy
# autocompletion
self.controller: cv2.VideoCapture = None

# TODO declare here attributes you want/need to init with a default value

self.x_axis = None
self.y_axis = None

def get_active_properties(self):
props = OpenCVProp.names()
self.additional_params = []
for prop in props:
try:
ret = int(self.controller.get(OpenCVProp[prop].value))
if ret != -1:
try:
ret_set = self.controller.set(OpenCVProp[prop].value, ret)
except:
ret_set = False
self.additional_params.append(
{'title': prop[7:], 'name': prop[7:], 'type': 'float', 'value': ret, 'readonly': not ret_set})
except:
pass
self.settings.child('cam_settings').addChildren(self.additional_params)
pass
{'title': 'Cam. Settings:', 'name': 'cam_settings', 'type': 'group', 'children': []},
] + webcam_params()

def ini_detector_custom(self, controller=None):
"""Open the camera (master) or take the one of the master (slave)"""
if self.is_master:
self.controller = OpenCVCamera(self.settings['camera_index'])
else:
self.controller = controller
self.add_camera_properties()

def add_camera_properties(self):
"""Make a setting of each property that this camera supports"""
group = self.settings.child('cam_settings')
group.clearChildren()
group.addChildren([
{'title': name, 'name': name, 'type': 'float', 'value': value, 'readonly': not writable}
for name, (value, writable) in self.controller.get_properties().items()])

def commit_settings(self, param: Parameter):
"""Apply the consequences of a change of value in the detector settings
Expand All @@ -62,116 +70,24 @@ def commit_settings(self, param: Parameter):
"""
if param.name() == 'open_settings':
if param.value():
self.controller.set(OpenCVProp['CV_CAP_PROP_SETTINGS'].value, 1)
# param.setValue(False)
elif param.name() == 'colors':
pass
self.controller.show_settings()
elif param.parent() is not None and param.parent().name() == 'cam_settings':
self.set_camera_property(param)
else:
self.controller.set(OpenCVProp['CV_CAP_' + param.name()].value, param.value())
val = self.controller.get(OpenCVProp['CV_CAP_' + param.name()].value)
param.setValue(val)

def ini_detector(self, controller=None):
"""Detector communication initialization

Parameters
----------
controller: (object)
custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller
(Master case)

Returns
-------
info: str
initialized: bool
False if initialization failed otherwise True
"""
if self.is_master:
if 'win' in platform:
self.controller = cv2.VideoCapture(self.settings['camera_index'], cv2.CAP_DSHOW)
else:
self.controller = cv2.VideoCapture(self.settings['camera_index'])
# to add settable settings to the param list (but driver builtin settings window is prefered (OpenCVProp['CV_CAP_PROP_SETTINGS'])
self.get_active_properties()

else:
self.controller = controller


self.x_axis = self.get_xaxis()
self.y_axis = self.get_yaxis()

self.dte_signal_temp.emit(DataToExport('myplugin',
data=[DataFromPlugins(name='Mock1',
data=[np.zeros((len(self.y_axis), len(self.x_axis)))],
dim='Data2D', labels=['opencv'],
axes=[self.x_axis, self.y_axis]), ]))

info = "Whatever info you want to log"
initialized = True
return info, initialized

def close(self):
"""Terminate the communication protocol"""
self.controller.release()

def get_xaxis(self) -> Axis:
""" Get the current x_axis from the controller

Returns
-------
Axis
"""
Nx = int(self.controller.get(cv2.CAP_PROP_FRAME_WIDTH)) # property index corresponding to width
return Axis('xaxis', data=np.linspace(0, Nx - 1, Nx), index=1)

def get_yaxis(self) -> Axis:
""" Get the current y_axis from the controller

Returns
-------
Axis
"""
Ny = int(self.controller.get(cv2.CAP_PROP_FRAME_HEIGHT)) # property index corresponding to width
return Axis('yaxis', data=np.linspace(0, Ny - 1, Ny), index=0)

def grab_data(self, Naverage=1, **kwargs):
"""Start a grab from the detector

Parameters
----------
Naverage: int
Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to
True in class preamble and you should code this implementation)
kwargs: dict
others optionals arguments
"""
if not self.controller.isOpened():
self.controller.open(self.settings['camera_index'])

ret, frame = self.controller.read()

if ret:
if self.settings['colors'] == 'gray':
data_cam = [cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)]
data_cam[0] = data_cam[0].astype(np.float32)
else:
if len(frame.shape) == 3:
data_cam = [frame[:, :, ind] for ind in range(frame.shape[2])]
else:
data_cam = [cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)]

else:
data_cam = [np.zeros((len(self.y_axis), len(self.x_axis)))]
self.emit_status(ThreadCommand('Update_Status', ['no return from the controller', 'log']))

self.dte_signal.emit(DataToExport('OpenCV data',
data=[
DataFromPlugins(name='OpenCV', data=data_cam, dim='Data2D')]))

def stop(self):
"""Stop the current grab hardware wise if necessary"""
pass
super().commit_settings(param)

def set_camera_property(self, param: Parameter):
"""Set a property of the camera and show the value that it took"""
resize = param.name() in self.controller.size_properties
was_live = self.is_live
if resize: # the images get another shape
self.stop()
param.setValue(self.controller.set_property(param.name(), param.value()))
if resize:
self.get_set_main_parameters()
self._prepare_view()
if was_live:
self.grab_data(self.Naverage, live=True)


if __name__ == '__main__':
Expand Down
Loading
Loading