From c43ed6fb0b8d5cc4dfbc77109eb3b1e4dba77e0b Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 15:05:46 +0000 Subject: [PATCH 01/19] Added new MMAL parameters NB I've not yet added the new datatypes for e.g. lens shading. However, I have wrapped analog and digital gain so that you can set them. --- picamera/camera.py | 35 +++++++++++++++++++++++++++++++++-- picamera/mmal.py | 12 +++++++++++- picamera/mmalobj.py | 10 ++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index 01ec65fd..397109c7 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2641,14 +2641,29 @@ def _get_analog_gain(self): self._check_camera_open() return mo.to_fraction( self._camera.control.params[mmal.MMAL_PARAMETER_CAMERA_SETTINGS].analog_gain) - analog_gain = property(_get_analog_gain, doc="""\ - Retrieves the current analog gain of the camera. + def _set_analog_gain(self): + self._check_camera_open() + self._camera.control.params[mmal.MMAL_PARAMETER_ANALOG_GAIN] = value + analog_gain = property(_get_analog_gain, _set_analog_gain, doc="""\ + Retrieves or sets the current analog gain of the camera. When queried, this property returns the analog gain currently being used by the camera. The value represents the analog gain of the sensor prior to digital conversion. The value is returned as a :class:`~fractions.Fraction` instance. + + When set, the property adjusts the analog gain of the camera, which + most obviously affects the brightness and noise of subsequently captured + images. Analog gain can be adjusted while previews or recordings are + running. + + .. note:: + Setting the analog gain requires up-to-date userland libraries and + firmware on your Raspberry Pi. Setting the analog gain will raise + a PiCameraMMALError if your userland libraries do not support setting + the analog gain. + .. versionadded:: 1.6 """) @@ -2656,6 +2671,9 @@ def _get_digital_gain(self): self._check_camera_open() return mo.to_fraction( self._camera.control.params[mmal.MMAL_PARAMETER_CAMERA_SETTINGS].digital_gain) + def _set_digital_gain(self): + self._check_camera_open() + self._camera.control.params[mmal.MMAL_PARAMETER_DIGITAL_GAIN] = value digital_gain = property(_get_digital_gain, doc="""\ Retrieves the current digital gain of the camera. @@ -2663,6 +2681,19 @@ def _get_digital_gain(self): used by the camera. The value represents the digital gain the camera applies after conversion of the sensor's analog output. The value is returned as a :class:`~fractions.Fraction` instance. + + When set, the property adjusts the digital gain of the camera, which + most obviously affects the brightness and noise of subsequently captured + images. Digital gain can be adjusted while previews or recordings are + running. + + .. note:: + + Setting the digital gain requires up-to-date userland libraries and + firmware on your Raspberry Pi. Setting the digital gain will raise + a PiCameraMMALError if your userland libraries do not support setting + the digital gain. + .. versionadded:: 1.6 """) diff --git a/picamera/mmal.py b/picamera/mmal.py index 013d1341..f68c6390 100644 --- a/picamera/mmal.py +++ b/picamera/mmal.py @@ -617,7 +617,17 @@ class MMAL_PARAMETER_LOGGING_T(ct.Structure): MMAL_PARAMETER_CAMERA_RX_TIMING, MMAL_PARAMETER_DPF_CONFIG, MMAL_PARAMETER_JPEG_RESTART_INTERVAL, -) = range(MMAL_PARAMETER_GROUP_CAMERA, MMAL_PARAMETER_GROUP_CAMERA + 81) + MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE, + MMAL_PARAMETER_LENS_SHADING_OVERRIDE, + MMAL_PARAMETER_BLACK_LEVEL, + MMAL_PARAMETER_RESIZE_PARAMS, + MMAL_PARAMETER_CROP, + MMAL_PARAMETER_OUTPUT_SHIFT, + MMAL_PARAMETER_CCM_SHIFT, + MMAL_PARAMETER_CUSTOM_CCM, + MMAL_PARAMETER_ANALOG_GAIN, + MMAL_PARAMETER_DIGITAL_GAIN, +) = range(MMAL_PARAMETER_GROUP_CAMERA, MMAL_PARAMETER_GROUP_CAMERA + 91) class MMAL_PARAMETER_THUMBNAIL_CONFIG_T(ct.Structure): _fields_ = [ diff --git a/picamera/mmalobj.py b/picamera/mmalobj.py index 223474e9..8b59fc9a 100644 --- a/picamera/mmalobj.py +++ b/picamera/mmalobj.py @@ -73,10 +73,12 @@ # MMALControlPort.params attribute. PARAM_TYPES = { mmal.MMAL_PARAMETER_ALGORITHM_CONTROL: mmal.MMAL_PARAMETER_ALGORITHM_CONTROL_T, + mmal.MMAL_PARAMETER_ANALOG_GAIN: mmal.MMAL_PARAMETER_RATIONAL_T, mmal.MMAL_PARAMETER_ANNOTATE: None, # adjusted by MMALCamera.annotate_rev mmal.MMAL_PARAMETER_ANTISHAKE: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_AUDIO_LATENCY_TARGET: mmal.MMAL_PARAMETER_AUDIO_LATENCY_TARGET_T, mmal.MMAL_PARAMETER_AWB_MODE: mmal.MMAL_PARAMETER_AWBMODE_T, + mmal.MMAL_PARAMETER_BLACK_LEVEL: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_BRIGHTNESS: mmal.MMAL_PARAMETER_RATIONAL_T, mmal.MMAL_PARAMETER_BUFFER_FLAG_FILTER: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_BUFFER_REQUIREMENTS: mmal.MMAL_PARAMETER_BUFFER_REQUIREMENTS_T, @@ -86,6 +88,7 @@ mmal.MMAL_PARAMETER_CAMERA_CUSTOM_SENSOR_CONFIG: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_CAMERA_INFO: None, # adjusted by MMALCameraInfo.info_rev mmal.MMAL_PARAMETER_CAMERA_INTERFACE: mmal.MMAL_PARAMETER_CAMERA_INTERFACE_T, + mmal.MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_CAMERA_MIN_ISO: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_CAMERA_NUM: mmal.MMAL_PARAMETER_INT32_T, mmal.MMAL_PARAMETER_CAMERA_RX_CONFIG: mmal.MMAL_PARAMETER_CAMERA_RX_CONFIG_T, @@ -97,6 +100,7 @@ mmal.MMAL_PARAMETER_CAPTURE_MODE: mmal.MMAL_PARAMETER_CAPTUREMODE_T, mmal.MMAL_PARAMETER_CAPTURE_STATS_PASS: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_CAPTURE_STATUS: mmal.MMAL_PARAMETER_CAPTURE_STATUS_T, + mmal.MMAL_PARAMETER_CCM_SHIFT: mmal.MMAL_PARAMETER_INT32_T, mmal.MMAL_PARAMETER_CHANGE_EVENT_REQUEST: mmal.MMAL_PARAMETER_CHANGE_EVENT_REQUEST_T, mmal.MMAL_PARAMETER_CLOCK_ACTIVE: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_CLOCK_DISCONT_THRESHOLD: mmal.MMAL_PARAMETER_CLOCK_DISCONT_THRESHOLD_T, @@ -110,7 +114,10 @@ mmal.MMAL_PARAMETER_COLOUR_EFFECT: mmal.MMAL_PARAMETER_COLOURFX_T, mmal.MMAL_PARAMETER_CONTRAST: mmal.MMAL_PARAMETER_RATIONAL_T, mmal.MMAL_PARAMETER_CORE_STATISTICS: mmal.MMAL_PARAMETER_CORE_STATISTICS_T, +# mmal.MMAL_PARAMETER_CROP: mmal.MMAL_PARAMETER_CROP_T, mmal.MMAL_PARAMETER_CUSTOM_AWB_GAINS: mmal.MMAL_PARAMETER_AWB_GAINS_T, +# mmal.MMAL_PARAMETER_CUSTOM_CCM: mmal.MMAL_PARAMETER_CUSTOM_CCM_T, + mmal.MMAL_PARAMETER_DIGITAL_GAIN: mmal.MMAL_PARAMETER_RATIONAL_T, mmal.MMAL_PARAMETER_DISPLAYREGION: mmal.MMAL_DISPLAYREGION_T, mmal.MMAL_PARAMETER_DPF_CONFIG: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_DYNAMIC_RANGE_COMPRESSION: mmal.MMAL_PARAMETER_DRC_T, @@ -139,6 +146,7 @@ mmal.MMAL_PARAMETER_JPEG_ATTACH_LOG: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_JPEG_Q_FACTOR: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_JPEG_RESTART_INTERVAL: mmal.MMAL_PARAMETER_UINT32_T, +# mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE: mmal.MMAL_PARAMETER_LENS_SHADING_T, mmal.MMAL_PARAMETER_LOCKSTEP_ENABLE: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_LOGGING: mmal.MMAL_PARAMETER_LOGGING_T, mmal.MMAL_PARAMETER_MB_ROWS_PER_SLICE: mmal.MMAL_PARAMETER_UINT32_T, @@ -147,11 +155,13 @@ mmal.MMAL_PARAMETER_MIRROR: mmal.MMAL_PARAMETER_UINT32_T, # actually mmal.MMAL_PARAMETER_MIRROR_T but this just contains a uint32 mmal.MMAL_PARAMETER_NALUNITFORMAT: mmal.MMAL_PARAMETER_VIDEO_NALUNITFORMAT_T, mmal.MMAL_PARAMETER_NO_IMAGE_PADDING: mmal.MMAL_PARAMETER_BOOLEAN_T, + mmal.MMAL_PARAMETER_OUTPUT_SHIFT: mmal.MMAL_PARAMETER_INT32_T, mmal.MMAL_PARAMETER_POWERMON_ENABLE: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_PRIVACY_INDICATOR: mmal.MMAL_PARAMETER_PRIVACY_INDICATOR_T, mmal.MMAL_PARAMETER_PROFILE: mmal.MMAL_PARAMETER_VIDEO_PROFILE_T, mmal.MMAL_PARAMETER_RATECONTROL: mmal.MMAL_PARAMETER_VIDEO_RATECONTROL_T, mmal.MMAL_PARAMETER_REDEYE: mmal.MMAL_PARAMETER_REDEYE_T, +# mmal.MMAL_PARAMETER_RESIZE_PARAMS: mmal.MMAL_PARAMETER_RESIZE_T, mmal.MMAL_PARAMETER_ROTATION: mmal.MMAL_PARAMETER_INT32_T, mmal.MMAL_PARAMETER_SATURATION: mmal.MMAL_PARAMETER_RATIONAL_T, mmal.MMAL_PARAMETER_SEEK: mmal.MMAL_PARAMETER_SEEK_T, From ca7dd23e6b623e3cd0b2407558ef31d354ed3c0f Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 15:18:41 +0000 Subject: [PATCH 02/19] Fixed arguments to _set_XXX_gain --- picamera/camera.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index 397109c7..5ab1bc6d 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2641,7 +2641,7 @@ def _get_analog_gain(self): self._check_camera_open() return mo.to_fraction( self._camera.control.params[mmal.MMAL_PARAMETER_CAMERA_SETTINGS].analog_gain) - def _set_analog_gain(self): + def _set_analog_gain(self, value): self._check_camera_open() self._camera.control.params[mmal.MMAL_PARAMETER_ANALOG_GAIN] = value analog_gain = property(_get_analog_gain, _set_analog_gain, doc="""\ @@ -2671,7 +2671,7 @@ def _get_digital_gain(self): self._check_camera_open() return mo.to_fraction( self._camera.control.params[mmal.MMAL_PARAMETER_CAMERA_SETTINGS].digital_gain) - def _set_digital_gain(self): + def _set_digital_gain(self, value): self._check_camera_open() self._camera.control.params[mmal.MMAL_PARAMETER_DIGITAL_GAIN] = value digital_gain = property(_get_digital_gain, doc="""\ From 9a58055176afeeb896879ae487c0f23dcf3ac7c6 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 15:28:00 +0000 Subject: [PATCH 03/19] Added datatype for lens shading override --- picamera/mmal.py | 12 ++++++++++++ picamera/mmalobj.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/picamera/mmal.py b/picamera/mmal.py index f68c6390..0540275c 100644 --- a/picamera/mmal.py +++ b/picamera/mmal.py @@ -1336,6 +1336,18 @@ class MMAL_PARAMETER_CAMERA_RX_TIMING_T(ct.Structure): ('cpi_timing2', ct.c_uint32), ] +class MMAL_PARAMETER_LENS_SHADING_T(ct.Structure): + _fields_ = [ + ('hdr', MMAL_PARAMETER_HEADER_T), + ('enabled', MMAL_BOOL_T), + ('grid_cell_size', ct.c_uint32), + ('grid_width', ct.c_uint32), + ('grid_stride', ct.c_uint32), + ('grid_height', ct.c_uint32), + ('mem_handle_table', ct.c_uint32), + ('ref_transform', ct.c_uint32), + ] + # mmal_parameters_video.h #################################################### ( diff --git a/picamera/mmalobj.py b/picamera/mmalobj.py index 8b59fc9a..d8b70e01 100644 --- a/picamera/mmalobj.py +++ b/picamera/mmalobj.py @@ -146,7 +146,7 @@ mmal.MMAL_PARAMETER_JPEG_ATTACH_LOG: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_JPEG_Q_FACTOR: mmal.MMAL_PARAMETER_UINT32_T, mmal.MMAL_PARAMETER_JPEG_RESTART_INTERVAL: mmal.MMAL_PARAMETER_UINT32_T, -# mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE: mmal.MMAL_PARAMETER_LENS_SHADING_T, + mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE: mmal.MMAL_PARAMETER_LENS_SHADING_T, mmal.MMAL_PARAMETER_LOCKSTEP_ENABLE: mmal.MMAL_PARAMETER_BOOLEAN_T, mmal.MMAL_PARAMETER_LOGGING: mmal.MMAL_PARAMETER_LOGGING_T, mmal.MMAL_PARAMETER_MB_ROWS_PER_SLICE: mmal.MMAL_PARAMETER_UINT32_T, From 5697339b6ac18c58c21ac48f1ade0a6cb06b9ce1 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 16:47:38 +0000 Subject: [PATCH 04/19] Wrapped user-vcsm.h Wrapped the videocore shared memory functions needed for lens shading (not the whole file. Is there a script that does this??) --- picamera/user-vcsm.py | 111 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 picamera/user-vcsm.py diff --git a/picamera/user-vcsm.py b/picamera/user-vcsm.py new file mode 100644 index 00000000..8ae1cabd --- /dev/null +++ b/picamera/user-vcsm.py @@ -0,0 +1,111 @@ +# vim: set et sw=4 sts=4 fileencoding=utf-8: +# +# Python header conversion +# Copyright (c) 2013-2017 Richard Bowman +# +# Original headers +# Copyright (c) 2012, Broadcom Europe Ltd +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import ( + unicode_literals, + print_function, + division, + absolute_import, + ) + +# Make Py2's str equivalent to Py3's +str = type('') + +import ctypes as ct +import warnings + +from .bcm_host import VCOS_UNSIGNED + +_lib = ct.CDLL('libvcsm.so') + +VCSM_STATUS_T = ct.c_uint32 # enum +( + VCSM_STATUS_VC_WALK_ALLOC, + VCSM_STATUS_HOST_WALK_MAP, + VCSM_STATUS_HOST_WALK_PID_MAP, + VCSM_STATUS_HOST_WALK_PID_ALLOC, + VCSM_STATUS_VC_MAP_ALL, + VCSM_STATUS_NONE, +) = range(6) + +VCSM_CACHE_TYPE_T = ct.c_uint32 # enum +( + VCSM_CACHE_TYPE_NONE, + VCSM_CACHE_TYPE_HOST, + VCSM_CACHE_TYPE_VC, + VCSM_CACHE_TYPE_HOST_AND_VC, +) = range(4) + +vcsm_init = _lib.vcsm_init +vcsm_init.argtypes = [] +vcsm_init.restype = ct.c_int + +vcsm_exit = _lib.vcsm_exit +vcsm_exit.argtypes = [] +vcsm_exit.restype = None + +vcsm_status = _lib.vcsm_status +vcsm_status.argtypes = [VCSM_STATUS_T, ct.c_int] +vcsm_status.restype = None + +vcsm_malloc = _lib.vcsm_malloc +vcsm_malloc.argtypes = [ct.c_uint, ct.c_char_p] +vcsm_malloc.restype = ct.c_uint + +vcsm_malloc_share = _lib.vcsm_malloc_share +vcsm_malloc_share.argtypes = [ct.c_uint] +vcsm_malloc_share.restype = ct.c_uint + +vcsm_free = _lib.vcsm_free +vcsm_free.argtypes = [ct.c_uint] +vcsm_free.restype = None + +vcsm_vc_hdl_from_ptr = _lib.vcsm_vc_hdl_from_ptr +vcsm_vc_hdl_from_ptr.argtypes = [ct.c_void_p] +vcsm_vc_hdl_from_ptr.restype = ct.c_uint + +vcsm_vc_hdl_from_hdl = _lib.vcsm_vc_hdl_from_hdl +vcsm_vc_hdl_from_hdl.argtypes = [ct.c_uint] +vcsm_vc_hdl_from_hdl.restype = ct.c_uint + +vscm_lock = _lib.vscm_lock +vscm_lock.argtypes = [ct.c_uint] +vscm_lock.restype = ct.c_void_p + +vcsm_unlock_ptr = _lib.vcsm_unlock_ptr +vcsm_unlock_ptr.argtypes = [ct.c_void_p] +vcsm_unlock_ptr.restype = ct.c_int + +vcsm_unlock_hdl = _lib.vcsm_unlock_hdl +vcsm_unlock_hdl.argtypes = [ct.c_uint] +vcsm_unlock_hdl.restype = ct.c_int From 18831f65456437f8ff97f27693aac74e379a3c2f Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 19:14:31 +0000 Subject: [PATCH 05/19] Fixed property for digital gain --- picamera/camera.py | 2 +- picamera/{user-vcsm.py => user_vcsm.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename picamera/{user-vcsm.py => user_vcsm.py} (100%) diff --git a/picamera/camera.py b/picamera/camera.py index 5ab1bc6d..bed1ae52 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2674,7 +2674,7 @@ def _get_digital_gain(self): def _set_digital_gain(self, value): self._check_camera_open() self._camera.control.params[mmal.MMAL_PARAMETER_DIGITAL_GAIN] = value - digital_gain = property(_get_digital_gain, doc="""\ + digital_gain = property(_get_digital_gain, _set_digital_gain, doc="""\ Retrieves the current digital gain of the camera. When queried, this property returns the digital gain currently being diff --git a/picamera/user-vcsm.py b/picamera/user_vcsm.py similarity index 100% rename from picamera/user-vcsm.py rename to picamera/user_vcsm.py From 02e38cdb83c83ff84a45e0d0c34d65e0f0387879 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 25 Jan 2018 19:15:45 +0000 Subject: [PATCH 06/19] worked on vcsm wrapper --- picamera/user_vcsm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/picamera/user_vcsm.py b/picamera/user_vcsm.py index 8ae1cabd..34298a89 100644 --- a/picamera/user_vcsm.py +++ b/picamera/user_vcsm.py @@ -98,9 +98,9 @@ vcsm_vc_hdl_from_hdl.argtypes = [ct.c_uint] vcsm_vc_hdl_from_hdl.restype = ct.c_uint -vscm_lock = _lib.vscm_lock -vscm_lock.argtypes = [ct.c_uint] -vscm_lock.restype = ct.c_void_p +vcsm_lock = _lib.vcsm_lock +vcsm_lock.argtypes = [ct.c_uint] +vcsm_lock.restype = ct.c_void_p vcsm_unlock_ptr = _lib.vcsm_unlock_ptr vcsm_unlock_ptr.argtypes = [ct.c_void_p] From 22b45e92e1dbb98cb2bda4d4575d786bc41361bf Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Mon, 29 Jan 2018 10:21:08 +0000 Subject: [PATCH 07/19] Added a docstring mmal.h is not documented, so probably this needn't be either. However, I thought it was worth at least adding a link to the C header I wrapped (which has extensive comments). --- picamera/user_vcsm.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/picamera/user_vcsm.py b/picamera/user_vcsm.py index 34298a89..2b6a5188 100644 --- a/picamera/user_vcsm.py +++ b/picamera/user_vcsm.py @@ -1,7 +1,9 @@ # vim: set et sw=4 sts=4 fileencoding=utf-8: # # Python header conversion -# Copyright (c) 2013-2017 Richard Bowman +# Copyright (c) 2017 Richard Bowman +# Following the style of mmal.py which is +# Copyright (c) 2013-2017 Dave Jones # # Original headers # Copyright (c) 2012, Broadcom Europe Ltd @@ -31,6 +33,16 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +""" +Wraps the VideoCore Shared Memory library in Python. + +This Python module wraps the necessary functions from the Raspberry Pi +``userland`` module to allow shared memory use in ``picamera``. Currently +this is only used to load a custom lens shading table. Please see the +comments in [user_vcsm.h](https://github.com/raspberrypi/userland/ +blob/master/host_applications/linux/libs/sm/user-vcsm.h). +""" + from __future__ import ( unicode_literals, print_function, From 1d145d90f8701c3ba27bfe7694eff33773c02a5c Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Fri, 2 Feb 2018 17:12:24 +0000 Subject: [PATCH 08/19] OO wrapper for VCSM --- picamera/user_vcsm.py | 4 +- picamera/vcsmobj.py | 193 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 picamera/vcsmobj.py diff --git a/picamera/user_vcsm.py b/picamera/user_vcsm.py index 2b6a5188..b58f12be 100644 --- a/picamera/user_vcsm.py +++ b/picamera/user_vcsm.py @@ -1,9 +1,9 @@ # vim: set et sw=4 sts=4 fileencoding=utf-8: # # Python header conversion -# Copyright (c) 2017 Richard Bowman -# Following the style of mmal.py which is # Copyright (c) 2013-2017 Dave Jones +# Please blame this particular file on +# Richard Bowman # # Original headers # Copyright (c) 2012, Broadcom Europe Ltd diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py new file mode 100644 index 00000000..463f4ec6 --- /dev/null +++ b/picamera/vcsmobj.py @@ -0,0 +1,193 @@ +# vim: set et sw=4 sts=4 fileencoding=utf-8: +# +# Python header conversion +# Copyright (c) 2013-2017 Dave Jones +# Please blame this particular file on +# Richard Bowman +# +# Original headers +# Copyright (c) 2012, Broadcom Europe Ltd +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +An object-oriented wrapper for the VideoCore Shared Memory library. + +This module is intended as a friendly Python wrapper for the VideoCore shared +memory API exposed by [user_vcsm.h](https://github.com/raspberrypi/userland/ +blob/master/host_applications/linux/libs/sm/user-vcsm.h) in the Raspberry Pi +userland code. +""" + + +from __future__ import ( + unicode_literals, + print_function, + division, + absolute_import, + ) + +# Make Py2's str equivalent to Py3's +str = type('') + +import user_vcsm as vcsm +import warnings +import contextlib +import ctypes + +# All the next bit of code does is attempt to open/close the library. +# I am sure there ought to be a nice way to hook into the module unloading +# or the interpreter closing, but I have not yet found it. + +class VideoCoreSharedMemoryServiceManager(): + """Class to manage initialising/closing the VCSM service.""" + def __init__(self): + ret = vcsm.vcsm_init() + if ret == 0: + self._initialised = True + else: + raise Error("Error initialising VideoCore Shared Memory " + "interface. Code {}".format(ret)) + + def exit(self): + """Shut down the VCSM service. Should be called only once.""" + assert self._initialised, "The VCSM service is not running." + vcsm.vcsm_exit() + self._initialised = False + + def __del__(self): + #TODO: find a reliable way to call this before Python shuts down! + #TODO: get rid of the print statement as it's confusing/annoying. + print("Closing VideoCore Shared Memory service on garbage collection.") + if self._initialised: + self.exit() + +_vcsm_manager = None +def ensure_vcsm_init(): + """Initialise the shared memory interface if required. + + The VideoCore shared memory service must be initialised in order to + use any of the other functions. When this module is unloaded or + Python closes, it should release the library. + """ + #TODO: find a better way to cleanly close the library. + global _vcsm_manager + if _vcsm_manager is None: + _vcsm_manager = VideoCoreSharedMemoryServiceManager() + + def vcsm_exit(): + """Close the VideoCore shared memory service down. + + It is not clear whether multiple init/close cycles are allowed in + one run of Python. This method should only be called once. It is + also probably not required - the library should be shut down cleanly + when the garbage collector cleans up after the module. + """ + if _vcsm_manager is not None: + _vcsm_manager.exit() + _vcsm_manager = None + else: + warnings.warn("The VCSM service can't be closed - it's not open.") + +class VideoCoreSharedMemory(): + """This class manages a chunk of VideoCore shared memory.""" + def __init__(self, size, name): + """Create a chunk of shared memory. + + Arguments: + size: unsigned integer + The size of the block of memory, in bytes + name: string + A name for the block of shared memory. + + On creation, this object will create some VC shared memory by + calling vcsm_malloc. It will call vcsm_free to free the memory + when the object is deleted. + """ + self._handle = vcsm.vcsm_malloc(size, name) + self._size = size + if self._handle == 0: + raise Error("Could not allocate VC shared memory block " + "'{}' with size {} bytes".format(name, size)) + + def __del__(self): + vcsm.vcsm_free(self._handle) + + @contextlib.contextmanager + def lock(self): + """Lock the shared memory and return a pointer to it. + + Usage: + ``` + sm = VideoCoreSharedMemory(128, "test") + with sm.lock() as pointer: + #copy stuff into the block + """ + pointer = vcsm.vcsm_lock(self._handle) + try: + yield pointer + finally: + vcsm.vcsm_unlock_hdl(self._handle) + + def copy_from_buffer(self, source, size=None, warn_if_small=True): + """Copy data from a buffer to shared memory. + + Arguments: + buffer: ctypes.c_void_p + A pointer to the location of the memory you want to copy in. + size: integer (optional) + If specified, copy this much memory. It will not copy more + than the size of the shared memory, and will raise an exception + if you try to do so. + warn_if_small: boolean (optional) + By default, a warning will be raised if you copy in a buffer + that is smaller than the allocated memory. Set this to False + to suppress the warning. + """ + if size is None: + size = self._size + if size > self._size: + raise ValueError("Attempted to copy in more bytes than the buffer holds.") + if size < self._size: + warnings.warn("The allocated memory won't be filled by the array passed in.") + with self.lock() as destination: + ctypes.memmove(destination, source, size) + + def copy_from_array(self, source): + """Copy the contents of a numpy array into the buffer. + + Arguments: + source: numpy.ndarray + The data to copy into the buffer. Must be np.uint8 datatype. + + NB the array must be contiguous. This will be checked but, in order to avoid + a hard dependency on numpy, it will not be made contiguous automatically. + """ + if not source.flags['C_CONTIGUOUS']: + raise ValueError("Only contiguous arrays can be copied to shared memory.") + if source.itemsize != 1: + raise TypeError("You may only copy 8-bit integers.") + self.copy_from_buffer(source.ctypes.data_as(ctypes.c_void_p), source.size) \ No newline at end of file From 25b517e03ab8693c01885c5cb64ad2432a229fd7 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Fri, 2 Feb 2018 17:12:44 +0000 Subject: [PATCH 09/19] Added lens_shading_table to picamera I've not tested this yet!!! --- picamera/camera.py | 147 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 12 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index bed1ae52..d3d3b537 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -46,7 +46,7 @@ from operator import itemgetter from collections import namedtuple -from . import bcm_host, mmal, mmalobj as mo +from . import bcm_host, mmal, mmalobj as mo, vcsmobj from .exc import ( PiCameraError, PiCameraValueError, @@ -115,9 +115,10 @@ class PiCamera(object): will represent. Only the Raspberry Pi compute module currently supports more than one camera. - The *sensor_mode*, *resolution*, *framerate*, *framerate_range*, and - *clock_mode* parameters provide initial values for the :attr:`sensor_mode`, - :attr:`resolution`, :attr:`framerate`, :attr:`framerate_range`, and + The *sensor_mode*, *resolution*, *framerate*, *framerate_range*, + *lens_shading_table*, and *clock_mode* parameters provide initial values + for the :attr:`sensor_mode`, :attr:`resolution`, :attr:`framerate`, + :attr:`framerate_range`, :attr:`lens_shading_table` and :attr:`clock_mode` attributes of the class (these attributes are all relatively expensive to set individually, hence setting them all upon construction is a speed optimization). Please refer to the attribute @@ -182,6 +183,9 @@ class PiCamera(object): .. versionchanged:: 1.13 Added *framerate_range* parameter. + + .. versionchanged:: 1.13 + Made *analog_gain* and *digital_gain* writeable and added *lens_shading_table* argument. .. _Compute Module: https://www.raspberrypi.org/documentation/hardware/computemodule/cmio-camera.md """ @@ -328,7 +332,7 @@ class PiCamera(object): def __init__( self, camera_num=0, stereo_mode='none', stereo_decimate=False, resolution=None, framerate=None, sensor_mode=0, led_pin=None, - clock_mode='reset', framerate_range=None): + clock_mode='reset', lens_shading_table=None, framerate_range=None): bcm_host.bcm_host_init() mimetypes.add_type('application/h264', '.h264', False) mimetypes.add_type('application/mjpeg', '.mjpg', False) @@ -364,6 +368,7 @@ def __init__( self._overlays = [] self._raw_format = 'yuv' self._image_effect_params = None + self._lens_shading_table = None with mo.MMALCameraInfo() as camera_info: info = camera_info.control.params[mmal.MMAL_PARAMETER_CAMERA_INFO] self._revision = 'ov5647' @@ -429,7 +434,7 @@ def __init__( raise PiCameraValueError('Invalid clock mode: %s' % clock_mode) try: self._init_camera(camera_num, stereo_mode, stereo_decimate) - self._configure_camera(sensor_mode, framerate, resolution, clock_mode) + self._configure_camera(sensor_mode, framerate, resolution, clock_mode, lens_shading_table) self._init_preview() self._init_splitter() self._camera.enable() @@ -2004,7 +2009,7 @@ def _control_callback(self, port, buf): def _configure_camera( self, sensor_mode, framerate, resolution, clock_mode, - old_sensor_mode=0): + lens_shading_table=None, old_sensor_mode=0): """ An internal method for setting a new camera mode, framerate, resolution, and/or clock_mode. @@ -2021,6 +2026,7 @@ def _configure_camera( (port.framesize, port.framerate, port.params[mmal.MMAL_PARAMETER_FPS_RANGE]) for port in self._camera.outputs ] + self._upload_lens_shading_table(lens_shading_table) if old_sensor_mode != 0 or sensor_mode != 0: self._camera.control.params[mmal.MMAL_PARAMETER_CAMERA_CUSTOM_SENSOR_CONFIG] = sensor_mode if not self._camera.control.enabled: @@ -2107,10 +2113,11 @@ def _set_framerate(self, value): sensor_mode = self.sensor_mode clock_mode = self.CLOCK_MODES[self.clock_mode] resolution = self.resolution + lens_shading_table = self.lens_shading_table self._disable_camera() self._configure_camera( sensor_mode=sensor_mode, framerate=value, resolution=resolution, - clock_mode=clock_mode) + clock_mode=clock_mode, lens_shading_table=lens_shading_table) self._configure_splitter() self._enable_camera() framerate = property(_get_framerate, _set_framerate, doc="""\ @@ -2182,13 +2189,14 @@ def _set_sensor_mode(self, value): clock_mode = self.CLOCK_MODES[self.clock_mode] resolution = self.resolution framerate = Fraction(self.framerate) + lens_shading_table = self.lens_shading_table if framerate == 0: framerate = self.framerate_range self._disable_camera() self._configure_camera( old_sensor_mode=sensor_mode, sensor_mode=value, framerate=framerate, resolution=resolution, - clock_mode=clock_mode) + clock_mode=clock_mode, lens_shading_table=lens_shading_table) self._configure_splitter() self._enable_camera() sensor_mode = property(_get_sensor_mode, _set_sensor_mode, doc="""\ @@ -2216,6 +2224,115 @@ def _set_sensor_mode(self, value): .. versionadded:: 1.9 """) + + def _lens_shading_table_shape(self, sensor_mode=None) + """Calculate the correct shape for a lens shading table. + + The lens shading table is not the full resolution of the camera - it + is defined with one point per 64x64 pixel block. This means the table + should be 1/64 times the size of the sensor, rounding **up** to the + nearest integer. + """ + if sensor_mode is None: + sensor_mode = self.sensor_mode + #TODO: make sure the resolution is appropriate to the camera mode! + full_resolution = [self.MAX_RESOLUTION.width, self.MAX_RESOLUTION.height] + return (4,) + tuple([(r // 64) + 1 for r in full_resolution[::-1]]) + + def _validate_lens_shading_table(self, lens_shading_table, sensor_mode): + """Check a lens shading table is valid and raise an exception if not.""" + table_shape = self._lens_shading_table_shape(sensor_mode) + if lens_shading_table.shape != table_shape: + raise PiCameraValueError("The lens shading table should have shape {} " + "for mode {}".format(table_shape, sensor_mode)) + if lens_shading_table.dtype != np.uint8: + raise PiCameraValueError("Lens shading tables must be uint8") + + def _upload_lens_shading_table(self, lens_shading_table, sensor_mode): + """Actually commit the lens shading table to the camera.""" + if lens_shading_table is None: + self._lens_shading_table = None + # Given that we reset the camera each time anyway, hopefully we revert + # to built-in lens shading correction by simply doing nothing here! + return + + self._validate_lens_shading_table(lens_shading_table, sensor_mode) + + # This sets the lens shading table based on the example code by 6by9 + # https://github.com/6by9/lens_shading/ + vcsmobj.ensure_vcsm_init() # make sure the shared memory service is initialised + shared_memory = vcsmobj.VideoCoreSharedMemory(grid_width*grid_height*4, "ls_grid") # allocate shared memory on the GPU + + lens_shading_parameters = mmal.MMAL_PARAMETER_LENS_SHADING_T( + hdr = mmal.MMAL_PARAMETER_HEADER_T( + mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE, + ct.sizeof(mmal.MMAL_PARAMETER_LENS_SHADING_T), + ), + enabled = mmal.MMAL_TRUE, + grid_cell_size = 64, + grid_width = grid_width, + grid_stride = grid_width, + grid_height = grid_height, + mem_handle_table = shared_memory.handle, + ref_transform = 3,# TODO: figure out what this should be properly!!! + ) + + contiguous_lens_shading_table = np.ascontiguousarray(lens_shading_table) # make sure the array is contiguous in memory + shared_memory.copy_from_array(contiguous_lens_shading_table) # copy in the array + self._camera.control.params[mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE] = lens_shading_parameters + + def _get_lens_shading_table(self): + self._check_camera_open() + return self._lens_shading_table + def _set_lens_shading_table(self, value): + self._check_camera_open() + self._check_recording_stopped() + #TODO: validate the table here? + sensor_mode = self.sensor_mode + clock_mode = self.CLOCK_MODES[self.clock_mode] + resolution = self.resolution + framerate = Fraction(self.framerate) + if framerate == 0: + framerate = self.framerate_range + self._disable_camera() + self._configure_camera( + old_sensor_mode=sensor_mode, sensor_mode=sensor_mode, + framerate=framerate, resolution=resolution, + clock_mode=clock_mode, lens_shading_table=value) + self._configure_splitter() + self._enable_camera() + lens_shading_table = property(_get_lens_shading_table, + _set_lens_shading_table, doc="""\ + Retrieves or sets the lens shading correction table. + + This is an advanced property which can be used to control the camera's + lens shading correction. By default, images from the camera are + corrected for vignetting by applying different amounts of gain to each + pixel. The lens shading table sets this gain, so if you are using a + lens other than the one supplied with the camera, this property should + allow you to remove vignetting artefacts from your images. NB this + correction is not applied to the raw Bayer data captured with the + option ``bayer=True``. + + + .. note:: + + By default, this property is None, and the camera's built-in lens + shading is used, which is correct for the lens supplied with the + camera module. It is not currently possible to read the lens + shading table back from the GPU, so this property will only have a + useful value if you have previously set it manually. + + Also, using this property with binned or cropped modes of the + camera may produce unpredictable results. + + The initial value of this property can be specified with the + *lens_shading_table* parameter in the :class:`PiCamera` constructor. + As it is an expensive parameter to set otherwise, it is best to use + the constructor rather than to change the property afterwards. + + .. versionadded:: 1.14 + """) def _get_clock_mode(self): self._check_camera_open() @@ -2228,6 +2345,7 @@ def _set_clock_mode(self, value): except KeyError: raise PiCameraValueError("Invalid clock mode %s" % value) sensor_mode = self.sensor_mode + lens_shading_table = self.lens_shading_table framerate = Fraction(self.framerate) if framerate == 0: framerate = self.framerate_range @@ -2235,7 +2353,8 @@ def _set_clock_mode(self, value): self._disable_camera() self._configure_camera( sensor_mode=sensor_mode, framerate=framerate, - resolution=resolution, clock_mode=clock_mode) + resolution=resolution, clock_mode=clock_mode, + lens_shading_table=lens_shading_table) self._configure_splitter() self._enable_camera() clock_mode = property(_get_clock_mode, _set_clock_mode, doc="""\ @@ -2272,12 +2391,14 @@ def _set_resolution(self, value): sensor_mode = self.sensor_mode clock_mode = self.CLOCK_MODES[self.clock_mode] framerate = Fraction(self.framerate) + lens_shading_table = self.lens_shading_table if framerate == 0: framerate = self.framerate_range self._disable_camera() self._configure_camera( sensor_mode=sensor_mode, framerate=framerate, - resolution=value, clock_mode=clock_mode) + resolution=value, clock_mode=clock_mode, + lens_shading_table=lens_shading_table) self._configure_splitter() self._enable_camera() resolution = property(_get_resolution, _set_resolution, doc=""" @@ -2351,10 +2472,12 @@ def _set_framerate_range(self, value): sensor_mode = self.sensor_mode clock_mode = self.CLOCK_MODES[self.clock_mode] resolution = self.resolution + lens_shading_table = self.lens_shading_table self._disable_camera() self._configure_camera( sensor_mode=sensor_mode, framerate=(low, high), - resolution=resolution, clock_mode=clock_mode) + resolution=resolution, clock_mode=clock_mode, + lens_shading_table=lens_shading_table) self._configure_splitter() self._enable_camera() framerate_range = property(_get_framerate_range, _set_framerate_range, doc="""\ From f3661f6492d9a721191dfb5f70b456631b494cff Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 6 Feb 2018 13:59:19 +0000 Subject: [PATCH 10/19] Added properties for the VCSM handles Should now be complete... --- picamera/camera.py | 4 ++-- picamera/vcsmobj.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index d3d3b537..75c8648d 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2257,7 +2257,7 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode): return self._validate_lens_shading_table(lens_shading_table, sensor_mode) - + nchannels, grid_height, grid_width = lens_shading_table.shape # This sets the lens shading table based on the example code by 6by9 # https://github.com/6by9/lens_shading/ vcsmobj.ensure_vcsm_init() # make sure the shared memory service is initialised @@ -2273,7 +2273,7 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode): grid_width = grid_width, grid_stride = grid_width, grid_height = grid_height, - mem_handle_table = shared_memory.handle, + mem_handle_table = shared_memory.videocore_handle, ref_transform = 3,# TODO: figure out what this should be properly!!! ) diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index 463f4ec6..6a21d470 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -63,7 +63,10 @@ # or the interpreter closing, but I have not yet found it. class VideoCoreSharedMemoryServiceManager(): - """Class to manage initialising/closing the VCSM service.""" + """Class to manage initialising/closing the VCSM service. + + .. versionadded:: 1.14 + """ def __init__(self): ret = vcsm.vcsm_init() if ret == 0: @@ -92,6 +95,8 @@ def ensure_vcsm_init(): The VideoCore shared memory service must be initialised in order to use any of the other functions. When this module is unloaded or Python closes, it should release the library. + + .. versionadded:: 1.14 """ #TODO: find a better way to cleanly close the library. global _vcsm_manager @@ -105,6 +110,8 @@ def vcsm_exit(): one run of Python. This method should only be called once. It is also probably not required - the library should be shut down cleanly when the garbage collector cleans up after the module. + + .. versionadded:: 1.14 """ if _vcsm_manager is not None: _vcsm_manager.exit() @@ -126,6 +133,8 @@ def __init__(self, size, name): On creation, this object will create some VC shared memory by calling vcsm_malloc. It will call vcsm_free to free the memory when the object is deleted. + + .. versionadded:: 1.14 """ self._handle = vcsm.vcsm_malloc(size, name) self._size = size @@ -136,6 +145,29 @@ def __init__(self, size, name): def __del__(self): vcsm.vcsm_free(self._handle) + def _get_handle(self): + return self._handle + handle = property(_get_handle, doc="""\ + The handle of the underlying VideoCore shared memory + + The handle identifies the block of shared memory, and is used by + the various functions wrapped in ``user_vcsm.py``. + + .. versionadded:: 1.14 + """) + + def _get_videocore_handle(self): + return self._handle + videocore_handle = property(_get_videocore_handle, doc="""\ + A handle to access the shared memory from the GPU + + The handle identifies the block of shared memory to the GPU. It + cannot, for safety reasons, be used to read or write memory from + the CPU, so it is only useful when passing memory to the GPU. + + .. versionadded:: 1.14 + """) + @contextlib.contextmanager def lock(self): """Lock the shared memory and return a pointer to it. @@ -145,6 +177,9 @@ def lock(self): sm = VideoCoreSharedMemory(128, "test") with sm.lock() as pointer: #copy stuff into the block + ``` + + .. versionadded:: 1.14 """ pointer = vcsm.vcsm_lock(self._handle) try: @@ -166,6 +201,8 @@ def copy_from_buffer(self, source, size=None, warn_if_small=True): By default, a warning will be raised if you copy in a buffer that is smaller than the allocated memory. Set this to False to suppress the warning. + + .. versionadded:: 1.14 """ if size is None: size = self._size @@ -185,6 +222,8 @@ def copy_from_array(self, source): NB the array must be contiguous. This will be checked but, in order to avoid a hard dependency on numpy, it will not be made contiguous automatically. + + .. versionadded:: 1.14 """ if not source.flags['C_CONTIGUOUS']: raise ValueError("Only contiguous arrays can be copied to shared memory.") From 89e70eec73616736be37bf23c45f9e7e7f1d55a4 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 6 Feb 2018 15:20:04 +0000 Subject: [PATCH 11/19] Tidied up some typos but not working --- picamera/camera.py | 16 ++++++++++------ picamera/vcsmobj.py | 6 +++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index 75c8648d..6eab8937 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -184,7 +184,7 @@ class PiCamera(object): .. versionchanged:: 1.13 Added *framerate_range* parameter. - .. versionchanged:: 1.13 + .. versionchanged:: 1.14 Made *analog_gain* and *digital_gain* writeable and added *lens_shading_table* argument. .. _Compute Module: https://www.raspberrypi.org/documentation/hardware/computemodule/cmio-camera.md @@ -327,6 +327,7 @@ class PiCamera(object): '_raw_format', '_image_effect_params', '_exif_tags', + '_lens_shading_table', ) def __init__( @@ -2225,7 +2226,7 @@ def _set_sensor_mode(self, value): .. versionadded:: 1.9 """) - def _lens_shading_table_shape(self, sensor_mode=None) + def _lens_shading_table_shape(self, sensor_mode=None): """Calculate the correct shape for a lens shading table. The lens shading table is not the full resolution of the camera - it @@ -2245,10 +2246,12 @@ def _validate_lens_shading_table(self, lens_shading_table, sensor_mode): if lens_shading_table.shape != table_shape: raise PiCameraValueError("The lens shading table should have shape {} " "for mode {}".format(table_shape, sensor_mode)) - if lens_shading_table.dtype != np.uint8: + # Ensure the array is uint8. NB the slightly odd string comparison + # avoids a hard dependency on numpy. + if lens_shading_table.dtype.name != "uint8": raise PiCameraValueError("Lens shading tables must be uint8") - def _upload_lens_shading_table(self, lens_shading_table, sensor_mode): + def _upload_lens_shading_table(self, lens_shading_table, sensor_mode=None): """Actually commit the lens shading table to the camera.""" if lens_shading_table is None: self._lens_shading_table = None @@ -2277,8 +2280,9 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode): ref_transform = 3,# TODO: figure out what this should be properly!!! ) - contiguous_lens_shading_table = np.ascontiguousarray(lens_shading_table) # make sure the array is contiguous in memory - shared_memory.copy_from_array(contiguous_lens_shading_table) # copy in the array + if not lens_shading_table.flags['C_CONTIGUOUS']: + raise ValueError("The lens shading table must be a C-contiguous numpy array") # make sure the array is contiguous in memory + shared_memory.copy_from_array(lens_shading_table) # copy in the array self._camera.control.params[mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE] = lens_shading_parameters def _get_lens_shading_table(self): diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index 6a21d470..e0826d52 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -53,7 +53,7 @@ # Make Py2's str equivalent to Py3's str = type('') -import user_vcsm as vcsm +from . import user_vcsm as vcsm import warnings import contextlib import ctypes @@ -103,7 +103,7 @@ def ensure_vcsm_init(): if _vcsm_manager is None: _vcsm_manager = VideoCoreSharedMemoryServiceManager() - def vcsm_exit(): +def vcsm_exit(): """Close the VideoCore shared memory service down. It is not clear whether multiple init/close cycles are allowed in @@ -229,4 +229,4 @@ def copy_from_array(self, source): raise ValueError("Only contiguous arrays can be copied to shared memory.") if source.itemsize != 1: raise TypeError("You may only copy 8-bit integers.") - self.copy_from_buffer(source.ctypes.data_as(ctypes.c_void_p), source.size) \ No newline at end of file + self.copy_from_buffer(source.ctypes.data_as(ctypes.c_void_p), source.size) From 62bc17fce54bf1feb6bf8ee8eaef4ca815e4d2b3 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 6 Feb 2018 16:15:48 +0000 Subject: [PATCH 12/19] Fixed the vcsm handle bug - now works! --- picamera/camera.py | 5 ++--- picamera/vcsmobj.py | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/picamera/camera.py b/picamera/camera.py index 6eab8937..1e677e34 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2250,6 +2250,8 @@ def _validate_lens_shading_table(self, lens_shading_table, sensor_mode): # avoids a hard dependency on numpy. if lens_shading_table.dtype.name != "uint8": raise PiCameraValueError("Lens shading tables must be uint8") + if not lens_shading_table.flags['C_CONTIGUOUS']: + raise ValueError("The lens shading table must be a C-contiguous numpy array") # make sure the array is contiguous in memory def _upload_lens_shading_table(self, lens_shading_table, sensor_mode=None): """Actually commit the lens shading table to the camera.""" @@ -2263,7 +2265,6 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode=None): nchannels, grid_height, grid_width = lens_shading_table.shape # This sets the lens shading table based on the example code by 6by9 # https://github.com/6by9/lens_shading/ - vcsmobj.ensure_vcsm_init() # make sure the shared memory service is initialised shared_memory = vcsmobj.VideoCoreSharedMemory(grid_width*grid_height*4, "ls_grid") # allocate shared memory on the GPU lens_shading_parameters = mmal.MMAL_PARAMETER_LENS_SHADING_T( @@ -2280,8 +2281,6 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode=None): ref_transform = 3,# TODO: figure out what this should be properly!!! ) - if not lens_shading_table.flags['C_CONTIGUOUS']: - raise ValueError("The lens shading table must be a C-contiguous numpy array") # make sure the array is contiguous in memory shared_memory.copy_from_array(lens_shading_table) # copy in the array self._camera.control.params[mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE] = lens_shading_parameters diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index e0826d52..fbf76339 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -136,6 +136,7 @@ def __init__(self, size, name): .. versionadded:: 1.14 """ + ensure_vcsm_init() self._handle = vcsm.vcsm_malloc(size, name) self._size = size if self._handle == 0: @@ -157,13 +158,14 @@ def _get_handle(self): """) def _get_videocore_handle(self): - return self._handle + return vcsm.vcsm_vc_hdl_from_hdl(self._handle) + videocore_handle = property(_get_videocore_handle, doc="""\ A handle to access the shared memory from the GPU The handle identifies the block of shared memory to the GPU. It cannot, for safety reasons, be used to read or write memory from - the CPU, so it is only useful when passing memory to the GPU. + the CPU, so it is only useful when passing data to the GPU. .. versionadded:: 1.14 """) @@ -227,6 +229,4 @@ def copy_from_array(self, source): """ if not source.flags['C_CONTIGUOUS']: raise ValueError("Only contiguous arrays can be copied to shared memory.") - if source.itemsize != 1: - raise TypeError("You may only copy 8-bit integers.") self.copy_from_buffer(source.ctypes.data_as(ctypes.c_void_p), source.size) From 12f11b1bcde0cdedc749edce0869a24e8edd8361 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Fri, 9 Feb 2018 14:27:39 +0000 Subject: [PATCH 13/19] Saved the lens shading array when it's set --- picamera/camera.py | 1 + 1 file changed, 1 insertion(+) diff --git a/picamera/camera.py b/picamera/camera.py index 1e677e34..25221656 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2283,6 +2283,7 @@ def _upload_lens_shading_table(self, lens_shading_table, sensor_mode=None): shared_memory.copy_from_array(lens_shading_table) # copy in the array self._camera.control.params[mmal.MMAL_PARAMETER_LENS_SHADING_OVERRIDE] = lens_shading_parameters + self._lens_shading_table = lens_shading_table def _get_lens_shading_table(self): self._check_camera_open() From ed53543c74e112f520b09eb26a13956c7861c629 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Sat, 10 Feb 2018 14:51:21 +0000 Subject: [PATCH 14/19] Removed a debug print statement --- picamera/vcsmobj.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index fbf76339..65fec0ad 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -83,8 +83,7 @@ def exit(self): def __del__(self): #TODO: find a reliable way to call this before Python shuts down! - #TODO: get rid of the print statement as it's confusing/annoying. - print("Closing VideoCore Shared Memory service on garbage collection.") + #print("Closing VideoCore Shared Memory service on garbage collection.") if self._initialised: self.exit() From e1c31491f4e5b9472994044646362d41b11ce1a8 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 13 Mar 2018 16:55:17 +0000 Subject: [PATCH 15/19] modified the version to avoid confusion --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d3d4bf25..f7b8fcc5 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ pass __project__ = 'picamera' -__version__ = '1.13' +__version__ = '1.13-lens_shading.0' __author__ = 'Dave Jones' __author_email__ = 'dave@waveform.org.uk' __url__ = 'http://picamera.readthedocs.io/' From 719b385f80f9038d13e5b73dc0558ba2055e8c10 Mon Sep 17 00:00:00 2001 From: Filip Ayazi Date: Mon, 2 Jul 2018 12:33:18 +0100 Subject: [PATCH 16/19] python3 compatibility several python3 related fixes: ctype char * now requries bytes (was string) some calculations now seem to need int() --- picamera/bcm_host.py | 4 ++-- picamera/mmalobj.py | 4 ++-- picamera/vcsmobj.py | 46 ++++++++++++++++++++++---------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/picamera/bcm_host.py b/picamera/bcm_host.py index 7ecd362b..3253f218 100644 --- a/picamera/bcm_host.py +++ b/picamera/bcm_host.py @@ -91,11 +91,11 @@ def VCOS_ALIGN_UP(value, round_to): # Note: this function assumes round_to is some power of 2. - return (value + (round_to - 1)) & ~(round_to - 1) + return int(value + (round_to - 1)) & ~(round_to - 1) def VCOS_ALIGN_DOWN(value, round_to): # Note: this function assumes round_to is some power of 2. - return value & ~(round_to - 1) + return int(value) & ~(round_to - 1) # vc_image_types.h ########################################################### diff --git a/picamera/mmalobj.py b/picamera/mmalobj.py index d8b70e01..f87efad1 100644 --- a/picamera/mmalobj.py +++ b/picamera/mmalobj.py @@ -1346,8 +1346,8 @@ def _set_framesize(self, value): video = self._port[0].format[0].es[0].video video.width = bcm_host.VCOS_ALIGN_UP(value.width, 32) video.height = bcm_host.VCOS_ALIGN_UP(value.height, 16) - video.crop.width = value.width - video.crop.height = value.height + video.crop.width = int(value.width) + video.crop.height = int(value.height) framesize = property(_get_framesize, _set_framesize, doc="""\ Retrieves or sets the size of the port's video frames as a (width, height) tuple. This attribute implicitly handles scaling the given diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index 65fec0ad..aa480147 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -38,7 +38,7 @@ This module is intended as a friendly Python wrapper for the VideoCore shared memory API exposed by [user_vcsm.h](https://github.com/raspberrypi/userland/ -blob/master/host_applications/linux/libs/sm/user-vcsm.h) in the Raspberry Pi +blob/master/host_applications/linux/libs/sm/user-vcsm.h) in the Raspberry Pi userland code. """ @@ -74,13 +74,13 @@ def __init__(self): else: raise Error("Error initialising VideoCore Shared Memory " "interface. Code {}".format(ret)) - + def exit(self): """Shut down the VCSM service. Should be called only once.""" assert self._initialised, "The VCSM service is not running." vcsm.vcsm_exit() self._initialised = False - + def __del__(self): #TODO: find a reliable way to call this before Python shuts down! #print("Closing VideoCore Shared Memory service on garbage collection.") @@ -90,9 +90,9 @@ def __del__(self): _vcsm_manager = None def ensure_vcsm_init(): """Initialise the shared memory interface if required. - + The VideoCore shared memory service must be initialised in order to - use any of the other functions. When this module is unloaded or + use any of the other functions. When this module is unloaded or Python closes, it should release the library. .. versionadded:: 1.14 @@ -101,11 +101,11 @@ def ensure_vcsm_init(): global _vcsm_manager if _vcsm_manager is None: _vcsm_manager = VideoCoreSharedMemoryServiceManager() - + def vcsm_exit(): """Close the VideoCore shared memory service down. - - It is not clear whether multiple init/close cycles are allowed in + + It is not clear whether multiple init/close cycles are allowed in one run of Python. This method should only be called once. It is also probably not required - the library should be shut down cleanly when the garbage collector cleans up after the module. @@ -117,18 +117,18 @@ def vcsm_exit(): _vcsm_manager = None else: warnings.warn("The VCSM service can't be closed - it's not open.") - + class VideoCoreSharedMemory(): """This class manages a chunk of VideoCore shared memory.""" def __init__(self, size, name): """Create a chunk of shared memory. - + Arguments: size: unsigned integer The size of the block of memory, in bytes name: string A name for the block of shared memory. - + On creation, this object will create some VC shared memory by calling vcsm_malloc. It will call vcsm_free to free the memory when the object is deleted. @@ -136,15 +136,15 @@ def __init__(self, size, name): .. versionadded:: 1.14 """ ensure_vcsm_init() - self._handle = vcsm.vcsm_malloc(size, name) + self._handle = vcsm.vcsm_malloc(size, name.encode()) self._size = size if self._handle == 0: raise Error("Could not allocate VC shared memory block " "'{}' with size {} bytes".format(name, size)) - + def __del__(self): vcsm.vcsm_free(self._handle) - + def _get_handle(self): return self._handle handle = property(_get_handle, doc="""\ @@ -154,8 +154,8 @@ def _get_handle(self): the various functions wrapped in ``user_vcsm.py``. .. versionadded:: 1.14 - """) - + """) + def _get_videocore_handle(self): return vcsm.vcsm_vc_hdl_from_hdl(self._handle) @@ -168,11 +168,11 @@ def _get_videocore_handle(self): .. versionadded:: 1.14 """) - + @contextlib.contextmanager def lock(self): """Lock the shared memory and return a pointer to it. - + Usage: ``` sm = VideoCoreSharedMemory(128, "test") @@ -187,10 +187,10 @@ def lock(self): yield pointer finally: vcsm.vcsm_unlock_hdl(self._handle) - + def copy_from_buffer(self, source, size=None, warn_if_small=True): """Copy data from a buffer to shared memory. - + Arguments: buffer: ctypes.c_void_p A pointer to the location of the memory you want to copy in. @@ -213,14 +213,14 @@ def copy_from_buffer(self, source, size=None, warn_if_small=True): warnings.warn("The allocated memory won't be filled by the array passed in.") with self.lock() as destination: ctypes.memmove(destination, source, size) - + def copy_from_array(self, source): """Copy the contents of a numpy array into the buffer. - + Arguments: source: numpy.ndarray The data to copy into the buffer. Must be np.uint8 datatype. - + NB the array must be contiguous. This will be checked but, in order to avoid a hard dependency on numpy, it will not be made contiguous automatically. From 00704a782e21a34041bafe08644d66e89b7428dc Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Fri, 5 Oct 2018 09:59:13 +0100 Subject: [PATCH 17/19] Added docs for VCSM interface --- docs/api_vcsmobj.rst | 105 +++++++++++++++++++++++++++++++++++++++++++ picamera/vcsmobj.py | 3 ++ 2 files changed, 108 insertions(+) create mode 100644 docs/api_vcsmobj.rst diff --git a/docs/api_vcsmobj.rst b/docs/api_vcsmobj.rst new file mode 100644 index 00000000..ae3715fc --- /dev/null +++ b/docs/api_vcsmobj.rst @@ -0,0 +1,105 @@ +.. _api_mmalobj: + +============= +API - vcsmobj +============= + +.. module:: picamera.vcsmobj + +.. currentmodule:: picamera.vcsmobj + +This module provides an object-oriented interface to the VideoCore shared +memory API, to make it simpler to use from Python. + +.. warning:: + + This part of the API is still experimental and subject to change in future + versions. Backwards compatibility is not (yet) guaranteed. + + +The Shared Memory Interface +=========================== +Some MMAL functions (see :mod:`~picamera.mmalobj`) need to pass larger amounts +of information to the GPU, which we can achieve using shared memory. The API +doesn't provide a persistent way to "hold on" to a block of shared memory, which +means this transfer is effectively one way, i.e. it allows us to transfer data +onto the GPU, but not back off again. + +The process of getting some data onto the GPU usually consists of: + +#. Allocate a block of shared memory on the GPU. +#. Get a "handle" that identifies the block of memory. +#. "Lock" the block of memory - stop the GPU from accessing it, and make it + available to the CPU. +#. Copy the data into the block of memory. +#. Unlock the memory. +#. Unallocate the block of memory so it can be re-used later. + +It's important that all of these steps are done in sequence; interrupting the +process can leave blocks of memory allocated (or worse, locked) when they are +no longer needed, which eventually causes crashes. That's the purpose of this +module; it wraps up the above process in higher-level functions, making it much +harder to accidentally crash the GPU. This module also makes sure the VCSM +interface is initialised and shut down cleanly. + +A Simple Example +---------------- +The :class:`VideoCoreSharedMemory` class represents a block of memory. This +block is initialised when the object is created, and freed when the object +is destroyed. It also provides functions that will copy data in from either a +`ctypes` buffer object or a `numpy` array. Our example starts by importing +the shared memory class (usually the only part of the module that is required) +and creating an array to send to the GPU. This uses ``numpy`` to create a +10x10 array of bytes: + +.. code-block:: pycon + + >>> from picamera.vcsmobj import VideoCoreSharedMemory + >>> import numpy as np + >>> w = 10 + >>> h = 10 + >>> data_to_send = np.ones((w, h), dtype=np.uint8) + +Next, we allocate some shared memory and copy our ``numpy`` array into it: + +.. code-block:: pycon + + >>> shared_mem = VideoCoreSharedMemory(w*h, "test_data") + >>> shared_mem.copy_from_array(data_to_send) + +You can then use this block of shared memory in a function (usually in the +MMAL library) by referring to ``shared_mem.videocore_handle`` which returns +a handle that identifies the block of memory to functions that run on the +GPU. The block of memory is freed when the object is garbage-collected by +Python, so there is no need to explicitly free it again. The first time +you allocate shared memory, the interface is initialised, and the module +will take care of closing down the shared memory interface when Python exits. + + +Classes +======= + +The VCSM wrapper can be used through one class, :class:`VideoCoreSharedMemory`. +This handles allocating and freeing the memory, as well as locking it and +copying in the data. There is a further class defined, used only to manage +initialising and closing the low-level VCSM interface +(:class:`VideoCoreSharedMemoryServiceManager`). + +.. autoclass:: VideoCoreSharedMemory + +.. autoclass:: VideoCoreSharedMemoryServiceManager + + +Functions +========= + +It is possible and recommended to use this module only through +:class:`VideoCoreSharedMemory`. However, there are a couple of functions +defined to explicitly initialise and shut down the shared memory interface. +These don't need to be called unless you are worried about the (very small) +overhead of starting the interface the first time you allocate memory. + +.. autofunction:: ensure_vcsm_init + +.. autofunction:: vcsm_exit + diff --git a/picamera/vcsmobj.py b/picamera/vcsmobj.py index aa480147..d1eee160 100644 --- a/picamera/vcsmobj.py +++ b/picamera/vcsmobj.py @@ -109,6 +109,9 @@ def vcsm_exit(): one run of Python. This method should only be called once. It is also probably not required - the library should be shut down cleanly when the garbage collector cleans up after the module. + + You probably do not want to call this function manually, but it is + here for completeness. .. versionadded:: 1.14 """ From 525c444603e85e61120aa825fe5bb2ef7a07950a Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 29 Jan 2019 13:23:49 +0000 Subject: [PATCH 18/19] updated version number for new beta --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f7b8fcc5..3156a927 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ pass __project__ = 'picamera' -__version__ = '1.13-lens_shading.0' +__version__ = '1.13.1b0' __author__ = 'Dave Jones' __author_email__ = 'dave@waveform.org.uk' __url__ = 'http://picamera.readthedocs.io/' From b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 5 Feb 2019 18:48:52 +0000 Subject: [PATCH 19/19] Mentioned setting ISO=0 --- picamera/camera.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/picamera/camera.py b/picamera/camera.py index 25221656..13ae4717 100644 --- a/picamera/camera.py +++ b/picamera/camera.py @@ -2791,6 +2791,10 @@ def _set_analog_gain(self, value): a PiCameraMMALError if your userland libraries do not support setting the analog gain. + Also, it may be necessary to set the camera's ``iso`` property to 0 + in order for setting the analog gain to work properly, due to the way + it is implemented in the GPU firmware. + .. versionadded:: 1.6 """)