Repository: pibooth/pibooth (language: python)

Issue:
Add support for libcamera/picamera2 (PiOS bullseye and Arducam)
hello
Could someone add the support for the arducam pivariety camera please
this camera use libcamera to work. 
she is compatible with openCV
my imx298 modele : https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/16mp-imx298-pivariety-motorized-focus-camera-module/
installation of librairie : https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/how-to-access-and-program-the-pivariety-camera-using-command-line-python-opencv-and-gstreamer
thanks for the help


Add support for libcamera/picamera2 (PiOS bullseye and Arducam)
hello
Could someone add the support for the arducam pivariety camera please
this camera use libcamera to work. 
she is compatible with openCV
my imx298 modele : https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/16mp-imx298-pivariety-motorized-focus-camera-module/
installation of librairie : https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/how-to-access-and-program-the-pivariety-camera-using-command-line-python-opencv-and-gstreamer
thanks for the help




Discussion / hints:
hello
could someone tell what i could do to help this issue.
i can't do it alone
i check the source but i can't identify when the probblem come

If the Arducam works with OpenCV, you could create a plugin which gives priority to OpenCV camera (and ignore Raspberrypi camera).
For instance, save this code in a file and setup it in the configuration file:

```python
"""Plugin to use OpenCV and gPhoto2 camera."""

import time
import pibooth
from pibooth.utils import LOGGER
from pibooth import camera

__version__ = "1.0.0"

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    cv_cam_proxy = camera.get_cv_camera_proxy()
    gp_cam_proxy = camera.get_gp_camera_proxy()

    if cv_cam_proxy and gp_cam_proxy:
        LOGGER.info("Configuring hybrid camera with retry (OpenCV + gPhoto2) ...")
        return camera.HybridCvCamera(cv_cam_proxy, gp_cam_proxy)
    elif gp_cam_proxy:
        LOGGER.info("Configuring gPhoto2 camera with retry ...")
        return camera.GpCamera(gp_cam_proxy)
    elif cv_cam_proxy:
        LOGGER.info("Configuring OpenCV camera ...")
        return camera.CvCamera(cv_cam_proxy)
```

Note that I can test it, so I let you correct it if I made mistakes.
Can you give me the result of the following command:

```
vcgencmd get_camera
```
With the Arducam connected of course
hello
thank you i will try it this evening
but before, could you explain me this
`For instance, save this code in a file and setup it in th configuration file:`
i just put the name of the file , i created in the configuration file ?
thanks
If you want to learn about plugins, please have a look to the documentation: https://pibooth.readthedocs.io/en/latest/sources/plugins/plugins.html

In the configuration, you will find the key `[GENERAL][plugins]` where you can put the path the file of the custom plugin
hello
pi@raspberrypi:~ $ vcgencmd get_camera
supported=1 detected=0
for the plugins thanks..but i will try tomorow...
it seems that in my several test, i broke something because opencv doen't work anymore in my configuration
hello
i have tested and it is still the same message
VIDIOC_QUERYCTL: Innapropriaite ioctl for device

(i have the message that the plugin you gave me is installed at the boot of pibooth)

something at initialisation is still wrong

have you another idea ?
Can you post the complete log file? I believe that error is coming from OpenCV library, but I need to be sure
unfortunatly, this is the only message...repeating on every line
in loop
evven with
 `pibooth --verbose`
it is the only message
Can you try the python script in chapter `2.2.2 Use v4l2loopback` of the link that you gave to install library?

https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/how-to-access-and-program-the-pivariety-camera-using-command-line-python-opencv-and-gstreamer/

```python
import cv2
cap = cv2.VideoCapture(3)
while True:
	ret, frame = cap.read()
	cv2.imshow("Arducam", frame)
	key = cv2.waitKey(1)
	if key == ord('q'):
		break
```

Does it work?
i have already tested this with no result
i make a v4l2loopback in /dev/video3
then i run :
 ```gst-launch-1.0 libcamerasrc ! 'video/x-raw,width=1920,height=1080' ! videoconvert ! tee ! v4l2sink device=/dev/video3```

when i test the  python script the result is no good..seem about "imshow"
````
pi@raspberrypi:~ $ python3 test3.py
VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV
OpenCV Error: Assertion failed (size.width>0 && size.height>0) in imshow, file /                                                                              build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp, line 304
Traceback (most recent call last):
  File "test3.py", line 5, in <module>
    cv2.imshow("arducam",frame)
cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp                                                                              :304: error: (-215) size.width>0 && size.height>0 in function imshow
```
when i asked support about it at arducam they tell me that they have i librairie for arducam and python
i installed it and tried with their python script wich work
```javascript
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30

    # Instantiate the libcamera class
    cam = libcamera.libcamera()

    """
    Initialize the camera

    :param1 width: Set image width
    :param2 height: Set image height
    :param3 pixelFormat: Set image format
    :param4 buffercount: Set the number of buffers
    :param5 rotation: Set the image rotation angle
    :returns ret: Whether the camera is initialized successfully
    """
    ret = cam.initCamera(width, height, libcamera.PixelFormat.RGB888, buffercount=4, rotation=0)

    if not ret:

        # Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:

            """
            Read image information

            :returns ret: Whether the image is successfully read
            :returns data: Image data information
            """
            ret, data = cam.readFrame()

            if not ret:
                continue

            # Get image data
            # At present, only RGB888, BGR888, XRGB8888 can be displayed directly, no conversion
            frame = data.imageData

            cv2.imshow("image", frame)

            key = cv2.waitKey(1)
            if key == ord("q"):
                break
            elif key == ord("s"):
                cv2.imwrite("test.jpg", frame)

            display_fps(0)

            """
            Return image buffer

            :param data: Send image data back
            """
            cam.returnFrameBuffer(data)

        cam.stopCamera()

    cam.closeCamera()
```
one more thing to help (or not)
for now i think it will be better not to use v4l2loopback and gst-launch-1.0 , because thez gave me their libcamera-python
i tried the script python 
```pythonscript
import cv2
cap = cv2.VideoCapture(3)
while True:
	ret, frame = cap.read()
	cv2.imshow("Arducam", frame)
	key = cv2.waitKey(1)
	if key == ord('q'):
		break
```
changing `cap = cv2.VideoCapture(3)` with VideoCapture(0) or VideoCapture(1)
```javascript
pi@raspberrypi:~/libcamera_python/libcamera_python $ ls /dev/video* -l
crw-rw----+ 1 root video 81,  8 oct.   7 23:13 /dev/video0
crw-rw----+ 1 root video 81,  9 oct.   7 23:13 /dev/video1
crw-rw----+ 1 root video 81,  4 oct.   7 23:13 /dev/video10
crw-rw----+ 1 root video 81,  5 oct.   7 23:13 /dev/video11
crw-rw----+ 1 root video 81,  6 oct.   7 23:13 /dev/video12
crw-rw----+ 1 root video 81,  0 oct.   7 23:13 /dev/video13
crw-rw----+ 1 root video 81,  1 oct.   7 23:13 /dev/video14
crw-rw----+ 1 root video 81,  2 oct.   7 23:13 /dev/video15
crw-rw----+ 1 root video 81,  3 oct.   7 23:13 /dev/video16
crw-rw----+ 1 root video 81,  7 oct.   7 23:13 /dev/video18
crw-rw----+ 1 root video 81, 11 oct.   9 19:32 /dev/video3
```
the /dev/video3 is created for v4l2loopback

when i test `VideoCapture(0)` for `/dev/video0`
the result is the same 
```
```
pi@raspberrypi:~ $ python3 test3.py VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV OpenCV Error: Assertion failed (size.width>0 && size.height>0) in imshow, file / build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp, line 304 Traceback (most recent call last): File "test3.py", line 5, in <module> cv2.imshow("arducam",frame) cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp :304: error: (-215) size.width>0 && size.height>0 in function imshow
```

but when i try with  `VideoCapture(1)` for `/dev/video1`
i have the same as pibooth
```
........
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
.......
```
Hi @tityann, it seems that `pibooth` has found something connected to `/dev/video1`, and that the reason why we get the `VIDIOC_QUERYCTRL: Inappropriate ioctl for device` error.

If you want to use the `libcamera`, you have to develop your own camera driver in a plugin. Here is a starter code. I can't develop it myself as I have no arducam to test it.

```python
"""Plugin to use libcamera camera."""

import time
import pibooth
from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera

import libcamera

__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255
        self._preview_resolution = None

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1], libcamera.PixelFormat.RGB888, buffercount=4, rotation=self.preview_rotation)

    # complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```
hello
thanks for your help
i don't know how to code unfortunaltly....

if you would like to help me a little more...
this is the result with the new plugin/driver
```
Libcam_cam_proxy = Libcamera.Libcamera()
NameError: name 'Libcamera is not defined
```

and i don't know what to do with
```
# complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py````
I missed `import libcamera` at the beginning of the plugin.
The comment is to indicate that you have to implement all the methods (using libcamera API of course) as it is done for OpenCV driver located in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py
:-)
thanks it is better now, pibooth is launching until to take the photo, and then pibooth crash
maybe it is what you say to implement methode of using libcamera
i will have a look in the link and chexk if i understand something
thanks for help
I've quickly made a draft of the driver class. But I can't debug it. Now it's your job ;-)

```python

class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcamCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.capture_rotation)
        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()

```
hello @anxuae 
so i tested this plugin/driver

in line 32 i change LibcamCamera by LibcameraCamera ( proud of me :-) )

i dont't know what i must erase or keep at the begginning ... there is two 
```
class LibcameraCamera(BaseCamera):
```
but when i group all the information in one, it d'on't work at all :-(

 In your first example you put
``` self._preview_resolution = None``` in the```def __init__(self, camera_proxy):```
not in the second example

in your seond example you put more in the `````def _specific_initialization(self):````` it may be normal

for now the error is 
``` raise IOError("Can not capture frame")````
i found it at 
```
 LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")
```
so ``` ret, data = self._cam.readFrame()``` may have no result , but i don't know what to do
maybe initialisation of camera is no good..maybe with the first line of what i explain first ?


```javascript
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255
        self._preview_resolution = None

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1], libcamera.PixelFormat.RGB888, buffercount=4, rotation=self.preview_rotation)

    # complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)

class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.capture_rotation)
        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()
````

First of all, you have 2 class LibcameraCamera in the plugin, so please remove one.

Then, it seems that taking a picture has failed... but I can not help because I can not test.

Retry with this code:

```python
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)

```
Hi @tityann , did you test the changes that I made?
During your first try, did you get the preview?
Hello i have tested your code
But it is still the same ...not working....
I try to make something putting the code example of the libcamera-python in
your driver....but ibdon't manage it
I have take a look  on python.....but xith no good result
When it seem to pass initialisation...it crash juste After


I still don't have preview


Le jeu. 14 oct. 2021 à 20:02, Anxuae ***@***.***> a écrit :

> Hi @tityann <https://github.com/tityann> , did you test the changes that
> I made?
> During your first try, did you get the preview?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pibooth/pibooth/issues/290#issuecomment-943593177>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AIMQUU3BSYU62KMHHSV2OKDUG4LLFANCNFSM5E2F7XQQ>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

My bad, I missed to start the camera in the `def preview(self, window, flip=True):` function.

Please add `self._cam.startCamera()` just after `self.preview_flip = flip`

Could you provide log file? (path should be `/tmp/pibooth.log`)

hello 
so that what i have test to include in your code from the example libcamera-python
```javascript
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30
````

and

```javascript
# Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:
````

but it's not ok

the log won't very help because i am not a coder...and i think i don't include the code as i must do


i will try with your last script you modified

so with your driver/plugin edited on 11/10/2021
and includ the ```self._cam.startCamera()```

```javascript
import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(te                                                                                                                                                             xt), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.res                                                                                                                                                             olution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect                                                                                                                                                             .width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.res                                                                                                                                                             olution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.                                                                                                                                                             resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._cam.startCamera()
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".for                                                                                                                                                             mat(effect, self.IMAGE_EFFECTS))

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```

and the log is
```
pi@raspberrypi:/tmp $ cat pibooth.log
[ DEBUG   ] pibooth           : Plugin found at '/home/pi/pibooth_driver_arducam.py'
[ INFO    ] pibooth           : Installed plugins: driver_arducam-1.0.0
[ INFO    ] pibooth           : Starting the photo booth application on Raspberry pi 4B
[ INFO    ] pibooth           : Configuring hybrid camera (libcamera) ...
[ WARNING ] pibooth           : No printer found (pycups or pycups-notify not installed)
[ DEBUG   ] pibooth           : Activate state 'wait'
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] pibooth           : Activate state 'choose'
[ INFO    ] pibooth           : Show picture choice (nothing selected)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] pibooth           : took 0.194 seconds
[ DEBUG   ] pibooth           : Activate state 'chosen'
[ INFO    ] pibooth           : Show picture choice (1 captures selected)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] pibooth           : took 0.110 seconds
[ DEBUG   ] pibooth           : Activate state 'preview'
[ INFO    ] pibooth           : Show preview before next capture
[ ERROR   ] pibooth           : Can not get camera preview image
[ DEBUG   ] pibooth           : Back to failsafe state due to error:
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.7/site-packages/pibooth/states.py", line 91, in set_state
    hook(cfg=self.cfg, app=self.app, win=self.win)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 327, in traced_hookexec
    return outcome.get_result()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 33, in from_call
    result = func()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 324, in <lambda>
    lambda: oldcall(hook_name, hook_impls, kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "/home/pi/.local/lib/python3.7/site-packages/pibooth/plugins/camera_plugin.py", line 69, in state_preview_enter
    app.camera.preview(win)
  File "/home/pi/pibooth_driver_arducam.py", line 105, in preview
    self._window.show_image(self._get_preview_image())
  File "/home/pi/pibooth_driver_arducam.py", line 61, in _get_preview_image
    raise IOError("Can not get camera preview image")
OSError: Can not get camera preview image
[ DEBUG   ] pibooth           : Activate state 'failsafe'
[ ERROR   ] pibooth           : system='Linux', node='raspberrypi', release='5.10.63-v7l+', version='#1457 SMP Tue Sep 28 11:26:14 BST 2021', machine='armv7l', processor=''
 ***********************************************************************************
 *                     Oops! It seems that pibooth has crached                     *
 *     You can report an issue on https://github.com/pibooth/pibooth/issues/new    *
 *                       and post the file: /tmp/pibooth.log                       *
 ***********************************************************************************
[ DEBUG   ] pibooth           : Activate state 'wait'
````
i tried to put the 
```self._cam.startCamera()```
in the section ```def _specific_initialization(self):```
and now i pass the response ```raise IOError("Can not get camera preview image")```
now i have
```
File "/home/pi/pibooth_driver_arducam.py", line 64, in _get_preview_image
    image = Image.open(io.BytesIO(data.imageData))
NameError: name 'Image' is not defined
````
maybe a mistake in defining ""image" ???
sometimes there is "image" sometimes Images"
i tested with "image" but ther is an another bug


i just test with
```javascript
from PIL import Image
``` 
but now i have
```
image = Image.open(io.BytesIO(data.imageData))
NameError: name 'io' is not defined
```
?!?!
i tezsted with ````` import io```
i have now 
```
  File "/home/pi/pibooth_driver_arducam.py", line 65, in _get_preview_image
    image = Image.open(io.BytesIO(data.imageData))
  File "/usr/local/lib/python3.7/dist-packages/PIL/Image.py", line 3024, in open
    "cannot identify image file %r" % (filename if filename else fp)
PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7de3c390>
```
Can you ask to libcamera team how to load image with PIL lib?
Hi @tityann , can you try to replace `Image.open(io.BytesIO(data.imageData))`  with `Image.fromarray(data.imageData)` ?
hello @anxuae 
now the new issue is
`````
 File "/home/pi/pibooth_driver_arducam.py", line 67, in _get_preview_image
    image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
NameError: name 'sizing' is not defined
```

I don't manage to identify which library i have to import
for "size" it seem to depend from
```import numpy as np```
but still the same error....
i don't manage to find information for "sizing"
ok i found, i had to insert
```from pibooth.pictures import sizing```
and for the next severals errors i put
```
from pibooth.utils import PoolingTimer, LOGGER```
import pygame
from pibooth.language import get_translated_text
```
now i have
````
 image = Image.open(io.BytesIO(frame))
  File "/usr/local/lib/python3.7/dist-packages/PIL/Image.py", line 3024, in open
    "cannot identify image file %r" % (filename if filename else fp)
````
I done the same modification from
```
mage = Image.open(io.BytesIO(frame))
```
to
```
image = Image.fromarray(frame)
````
and now there is no bugs....
but at the end the photo is only black

It seems to be promising ;-).

I gess that the format returned by libcamera is not well understood by PIL. Can you try this:

`image = Image.fromarray(np.uint8(frame))`


at the beginning of the plugin add `import numpy as np`
i have tried..but still black
i don't know if the camera is well intitialized
because if i ask to take another photo it crash with
````
  self._window.show_image(self._get_preview_image())
  File "/home/pi/pibooth_driver_arducam.py", line 69, in _get_preview_image
    raise IOError("Can not get camera preview image")
````
when i told you that i have moved the init of the camera earlier in the script, do i did it well ?
i put it there (at the end of the init)
```javascript
   def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.startCamera()
```
No, it shall be put in the `preview` function. 
```python
def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._cam.startCamera()
        self._window.show_image(self._get_preview_image())
```
son now i return to the old bug
```
  File "/home/pi/pibooth_driver_arducam.py", line 69, in _get_preview_image
    raise IOError("Can not get camera preview image")
```
Can you post your current plugin?
```python
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera
import pygame
from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera
from pibooth.pictures import sizing
from pibooth.utils import PoolingTimer, LOGGER
from pibooth.language import get_translated_text
from PIL import Image
import cv2

__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.startCamera()
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.set(libcamera.ExposureTime, 500)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")
        cv2.imwrite('frame.jpg', data.imageData)
        image = Image.fromarray(data.imageData)

        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.fromarray(frame)

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        # if self.capture_iso != self.preview_iso:
        #     self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

       #  if self.capture_iso != self.preview_iso:
       #      self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.stopCamera()
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```
just for the example
this is a code which use opencv to work and it works
"i" and "d" for exposure , "s" to take a photo and "q" to quit
```python
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30

    # Instantiate the libcamera class
    cam = libcamera.libcamera()

    """
    Initialize the camera

    :param1 width: Set image width
    :param2 height: Set image height
    :param3 pixelFormat: Set image format
    :param4 buffercount: Set the number of buffers
    :param5 rotation: Set the image rotation angle
    :returns ret: Whether the camera is initialized successfully
    """
    ret = cam.initCamera(width, height, libcamera.PixelFormat.RGB888, buffercount=4, rotation=0)

    if not ret:

        # Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:

            """
            Read image information

            :returns ret: Whether the image is successfully read
            :returns data: Image data information
            """
            ret, data = cam.readFrame()

            if not ret:
                continue

            # Get image data
            # At present, only RGB888, BGR888, XRGB8888 can be displayed directly, no conversion
            frame = data.imageData

            cv2.imshow("image", frame)

            key = cv2.waitKey(1)
            if key == ord("q"):
                break
            elif key == ord("s"):
                cv2.imwrite("test.jpg", frame)

            display_fps(0)

            """
            Return image buffer

            :param data: Send image data back
            """
            cam.returnFrameBuffer(data)

        cam.stopCamera()

    cam.closeCamera()
```
I made some modifications in the plugin code that you posted in the comment above, can you retest?
(I have no other solution to test, I can use my raspberrypi camera with libcamera)
hello
thanks a lot for the help
```
File "/home/pi/pibooth_driver_arducam.py", line 63, in _get_preview_image
    raise IOError("Can not get camera preview image")
OSError: Can not get camera preview image
````

the only way by the past to go over this error was to put the "camstart" earlier in the script.
when i putted it in the init there was no error

after testing the camstart in the init, i confirme that i have no bug...but the photo is still black
I made new changes, and commented the ExposureTime setting why maybe cause the black image, can you have a new try?
ok i will try this evening.
you commented the "ExposureTime setting", but this option is only on the capture ? , the preview have no "ExposureTime setting" but the preview is also black
i have tested your new modification
there is no crash
but the photo is (buggy) with color line
i ddon't know if there is a difference with black photo, because i have tested today with a lot of light
Can you save the frame in a file and attache it? I will try to load it.

I've modified the plugin to write frames in a file `frame.txt`
this is the frame.txt

https://transfert.free.fr/download/5BbvXLcHalXiFM6GfzlMft7pvrA
Sorry it does not work properly, can you retry with the newly updated code and send the frame.txt file?
with your modification i get
```
 File "/home/pi/pibooth_driver_arducam.py", line 67, in _get_preview_image
    cv2.imwrite('frame.txt', data.imageData)
cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/imgcodecs/src/loadsave.cpp:605: error: (-2) could not find a writer for the specified extension in function imwrite_
```
ok, modified again, the file is now called `frame.jpg`. it should work
the first file in .txt was 14mo and this jpeg is only 1 mo
https://transfert.free.fr/download/Il8E4Xc5Uk6fAjXHDwIRf6ixj4A
I'm not able to improve the image, so there is really something wrong this the capture taken with the camera.
Maybe something wrong with this part of the code:

```python
        self._cam.startCamera()
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.set(libcamera.ExposureTime, 500)
```

But I'm not able to go further without a camera to test... Thus I think that only you can try different way to handle the camera. You have a good basis for your plugin.
hello @anxuae 
i have found by chance what are two of the problems
first i have forced 
```
width = 640
height = 480

 self._cam.initCamera(width, height,
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
````
a la place de 
```self.resolution[0], self.resolution[1]```

and the second problem is from

```self._overlay_alpha```
after changing the value from 255 to somethings else like 000 or 111 it works i had a photo..dirty but a photo

i will test what was wrong about the resolution and then found a quiet goog value for the "alpha"

next in resolution of the probleme
 result of ``` v4l2-ctl --list-formats-ext``` is
```
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'pBAA' (10-bit Bayer BGBG/GRGR Packed)
                Size: Discrete 4640x3472
                Size: Discrete 2320x1732
                Size: Discrete 3264x2464
                Size: Discrete 3840x2160
                Size: Discrete 2592x1944
                Size: Discrete 1920x1080
                Size: Discrete 1280x800
                Size: Discrete 1280x720
        [1]: 'BG10' (10-bit Bayer BGBG/GRGR)
                Size: Discrete 4640x3472
                Size: Discrete 2320x1732
                Size: Discrete 3264x2464
                Size: Discrete 3840x2160
                Size: Discrete 2592x1944
                Size: Discrete 1920x1080
                Size: Discrete 1280x800
                Size: Discrete 1280x720
```
so i put like the original ```self.resolution[0], self.resolution[1]```
and in the ```pibooth config file i test with ```2592 , 1944```` it works wih tags but the format is landscape instaed of portrait
so i tested ```1944 , 2592```` but tis one don(t works

so i let the ```2592 , 1944```` landscape format
because high resolution make lags, how can i do to make preview in low resolution and capture in high resolution ?
the 2592*1944 is normally @20fps... in my example is like 1 fps
i tested with the ```self._overlay_alpha``` and now even with 255 , it works
but the image is not fine...
hello
could someone tell what i could do to help this issue.
i can't do it alone
i check the source but i can't identify when the probblem come

If the Arducam works with OpenCV, you could create a plugin which gives priority to OpenCV camera (and ignore Raspberrypi camera).
For instance, save this code in a file and setup it in the configuration file:

```python
"""Plugin to use OpenCV and gPhoto2 camera."""

import time
import pibooth
from pibooth.utils import LOGGER
from pibooth import camera

__version__ = "1.0.0"

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    cv_cam_proxy = camera.get_cv_camera_proxy()
    gp_cam_proxy = camera.get_gp_camera_proxy()

    if cv_cam_proxy and gp_cam_proxy:
        LOGGER.info("Configuring hybrid camera with retry (OpenCV + gPhoto2) ...")
        return camera.HybridCvCamera(cv_cam_proxy, gp_cam_proxy)
    elif gp_cam_proxy:
        LOGGER.info("Configuring gPhoto2 camera with retry ...")
        return camera.GpCamera(gp_cam_proxy)
    elif cv_cam_proxy:
        LOGGER.info("Configuring OpenCV camera ...")
        return camera.CvCamera(cv_cam_proxy)
```

Note that I can test it, so I let you correct it if I made mistakes.
Can you give me the result of the following command:

```
vcgencmd get_camera
```
With the Arducam connected of course
hello
thank you i will try it this evening
but before, could you explain me this
`For instance, save this code in a file and setup it in th configuration file:`
i just put the name of the file , i created in the configuration file ?
thanks
If you want to learn about plugins, please have a look to the documentation: https://pibooth.readthedocs.io/en/latest/sources/plugins/plugins.html

In the configuration, you will find the key `[GENERAL][plugins]` where you can put the path the file of the custom plugin
hello
pi@raspberrypi:~ $ vcgencmd get_camera
supported=1 detected=0
for the plugins thanks..but i will try tomorow...
it seems that in my several test, i broke something because opencv doen't work anymore in my configuration
hello
i have tested and it is still the same message
VIDIOC_QUERYCTL: Innapropriaite ioctl for device

(i have the message that the plugin you gave me is installed at the boot of pibooth)

something at initialisation is still wrong

have you another idea ?
Can you post the complete log file? I believe that error is coming from OpenCV library, but I need to be sure
unfortunatly, this is the only message...repeating on every line
in loop
evven with
 `pibooth --verbose`
it is the only message
Can you try the python script in chapter `2.2.2 Use v4l2loopback` of the link that you gave to install library?

https://www.arducam.com/docs/cameras-for-raspberry-pi/pivariety/how-to-access-and-program-the-pivariety-camera-using-command-line-python-opencv-and-gstreamer/

```python
import cv2
cap = cv2.VideoCapture(3)
while True:
	ret, frame = cap.read()
	cv2.imshow("Arducam", frame)
	key = cv2.waitKey(1)
	if key == ord('q'):
		break
```

Does it work?
i have already tested this with no result
i make a v4l2loopback in /dev/video3
then i run :
 ```gst-launch-1.0 libcamerasrc ! 'video/x-raw,width=1920,height=1080' ! videoconvert ! tee ! v4l2sink device=/dev/video3```

when i test the  python script the result is no good..seem about "imshow"
````
pi@raspberrypi:~ $ python3 test3.py
VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV
OpenCV Error: Assertion failed (size.width>0 && size.height>0) in imshow, file /                                                                              build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp, line 304
Traceback (most recent call last):
  File "test3.py", line 5, in <module>
    cv2.imshow("arducam",frame)
cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp                                                                              :304: error: (-215) size.width>0 && size.height>0 in function imshow
```
when i asked support about it at arducam they tell me that they have i librairie for arducam and python
i installed it and tried with their python script wich work
```javascript
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30

    # Instantiate the libcamera class
    cam = libcamera.libcamera()

    """
    Initialize the camera

    :param1 width: Set image width
    :param2 height: Set image height
    :param3 pixelFormat: Set image format
    :param4 buffercount: Set the number of buffers
    :param5 rotation: Set the image rotation angle
    :returns ret: Whether the camera is initialized successfully
    """
    ret = cam.initCamera(width, height, libcamera.PixelFormat.RGB888, buffercount=4, rotation=0)

    if not ret:

        # Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:

            """
            Read image information

            :returns ret: Whether the image is successfully read
            :returns data: Image data information
            """
            ret, data = cam.readFrame()

            if not ret:
                continue

            # Get image data
            # At present, only RGB888, BGR888, XRGB8888 can be displayed directly, no conversion
            frame = data.imageData

            cv2.imshow("image", frame)

            key = cv2.waitKey(1)
            if key == ord("q"):
                break
            elif key == ord("s"):
                cv2.imwrite("test.jpg", frame)

            display_fps(0)

            """
            Return image buffer

            :param data: Send image data back
            """
            cam.returnFrameBuffer(data)

        cam.stopCamera()

    cam.closeCamera()
```
one more thing to help (or not)
for now i think it will be better not to use v4l2loopback and gst-launch-1.0 , because thez gave me their libcamera-python
i tried the script python 
```pythonscript
import cv2
cap = cv2.VideoCapture(3)
while True:
	ret, frame = cap.read()
	cv2.imshow("Arducam", frame)
	key = cv2.waitKey(1)
	if key == ord('q'):
		break
```
changing `cap = cv2.VideoCapture(3)` with VideoCapture(0) or VideoCapture(1)
```javascript
pi@raspberrypi:~/libcamera_python/libcamera_python $ ls /dev/video* -l
crw-rw----+ 1 root video 81,  8 oct.   7 23:13 /dev/video0
crw-rw----+ 1 root video 81,  9 oct.   7 23:13 /dev/video1
crw-rw----+ 1 root video 81,  4 oct.   7 23:13 /dev/video10
crw-rw----+ 1 root video 81,  5 oct.   7 23:13 /dev/video11
crw-rw----+ 1 root video 81,  6 oct.   7 23:13 /dev/video12
crw-rw----+ 1 root video 81,  0 oct.   7 23:13 /dev/video13
crw-rw----+ 1 root video 81,  1 oct.   7 23:13 /dev/video14
crw-rw----+ 1 root video 81,  2 oct.   7 23:13 /dev/video15
crw-rw----+ 1 root video 81,  3 oct.   7 23:13 /dev/video16
crw-rw----+ 1 root video 81,  7 oct.   7 23:13 /dev/video18
crw-rw----+ 1 root video 81, 11 oct.   9 19:32 /dev/video3
```
the /dev/video3 is created for v4l2loopback

when i test `VideoCapture(0)` for `/dev/video0`
the result is the same 
```
```
pi@raspberrypi:~ $ python3 test3.py VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV OpenCV Error: Assertion failed (size.width>0 && size.height>0) in imshow, file / build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp, line 304 Traceback (most recent call last): File "test3.py", line 5, in <module> cv2.imshow("arducam",frame) cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/highgui/src/window.cpp :304: error: (-215) size.width>0 && size.height>0 in function imshow
```

but when i try with  `VideoCapture(1)` for `/dev/video1`
i have the same as pibooth
```
........
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
VIDIOC_QUERYCTRL: Inappropriate ioctl for device
.......
```
Hi @tityann, it seems that `pibooth` has found something connected to `/dev/video1`, and that the reason why we get the `VIDIOC_QUERYCTRL: Inappropriate ioctl for device` error.

If you want to use the `libcamera`, you have to develop your own camera driver in a plugin. Here is a starter code. I can't develop it myself as I have no arducam to test it.

```python
"""Plugin to use libcamera camera."""

import time
import pibooth
from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera

import libcamera

__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255
        self._preview_resolution = None

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1], libcamera.PixelFormat.RGB888, buffercount=4, rotation=self.preview_rotation)

    # complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```
hello
thanks for your help
i don't know how to code unfortunaltly....

if you would like to help me a little more...
this is the result with the new plugin/driver
```
Libcam_cam_proxy = Libcamera.Libcamera()
NameError: name 'Libcamera is not defined
```

and i don't know what to do with
```
# complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py````
I missed `import libcamera` at the beginning of the plugin.
The comment is to indicate that you have to implement all the methods (using libcamera API of course) as it is done for OpenCV driver located in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py
:-)
thanks it is better now, pibooth is launching until to take the photo, and then pibooth crash
maybe it is what you say to implement methode of using libcamera
i will have a look in the link and chexk if i understand something
thanks for help
I've quickly made a draft of the driver class. But I can't debug it. Now it's your job ;-)

```python

class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcamCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.capture_rotation)
        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()

```
hello @anxuae 
so i tested this plugin/driver

in line 32 i change LibcamCamera by LibcameraCamera ( proud of me :-) )

i dont't know what i must erase or keep at the begginning ... there is two 
```
class LibcameraCamera(BaseCamera):
```
but when i group all the information in one, it d'on't work at all :-(

 In your first example you put
``` self._preview_resolution = None``` in the```def __init__(self, camera_proxy):```
not in the second example

in your seond example you put more in the `````def _specific_initialization(self):````` it may be normal

for now the error is 
``` raise IOError("Can not capture frame")````
i found it at 
```
 LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")
```
so ``` ret, data = self._cam.readFrame()``` may have no result , but i don't know what to do
maybe initialisation of camera is no good..maybe with the first line of what i explain first ?


```javascript
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255
        self._preview_resolution = None

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1], libcamera.PixelFormat.RGB888, buffercount=4, rotation=self.preview_rotation)

    # complete the driver following the example in https://github.com/pibooth/pibooth/blob/master/pibooth/camera/opencv.py

@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)

class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.capture_rotation)
        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()
````

First of all, you have 2 class LibcameraCamera in the plugin, so please remove one.

Then, it seems that taking a picture has failed... but I can not help because I can not test.

Retry with this code:

```python
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)

```
Hi @tityann , did you test the changes that I made?
During your first try, did you get the preview?
Hello i have tested your code
But it is still the same ...not working....
I try to make something putting the code example of the libcamera-python in
your driver....but ibdon't manage it
I have take a look  on python.....but xith no good result
When it seem to pass initialisation...it crash juste After


I still don't have preview


Le jeu. 14 oct. 2021 à 20:02, Anxuae ***@***.***> a écrit :

> Hi @tityann <https://github.com/tityann> , did you test the changes that
> I made?
> During your first try, did you get the preview?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pibooth/pibooth/issues/290#issuecomment-943593177>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AIMQUU3BSYU62KMHHSV2OKDUG4LLFANCNFSM5E2F7XQQ>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

My bad, I missed to start the camera in the `def preview(self, window, flip=True):` function.

Please add `self._cam.startCamera()` just after `self.preview_flip = flip`

Could you provide log file? (path should be `/tmp/pibooth.log`)

hello 
so that what i have test to include in your code from the example libcamera-python
```javascript
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30
````

and

```javascript
# Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:
````

but it's not ok

the log won't very help because i am not a coder...and i think i don't include the code as i must do


i will try with your last script you modified

so with your driver/plugin edited on 11/10/2021
and includ the ```self._cam.startCamera()```

```javascript
import time
import pibooth
import libcamera

from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera


__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(te                                                                                                                                                             xt), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")

        image = Image.open(io.BytesIO(data.imageData))
        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.res                                                                                                                                                             olution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect                                                                                                                                                             .width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.open(io.BytesIO(frame))

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.res                                                                                                                                                             olution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.                                                                                                                                                             resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._cam.startCamera()
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._cam.stopCamera()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".for                                                                                                                                                             mat(effect, self.IMAGE_EFFECTS))

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

        if self.capture_iso != self.preview_iso:
            self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```

and the log is
```
pi@raspberrypi:/tmp $ cat pibooth.log
[ DEBUG   ] pibooth           : Plugin found at '/home/pi/pibooth_driver_arducam.py'
[ INFO    ] pibooth           : Installed plugins: driver_arducam-1.0.0
[ INFO    ] pibooth           : Starting the photo booth application on Raspberry pi 4B
[ INFO    ] pibooth           : Configuring hybrid camera (libcamera) ...
[ WARNING ] pibooth           : No printer found (pycups or pycups-notify not installed)
[ DEBUG   ] pibooth           : Activate state 'wait'
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] pibooth           : Activate state 'choose'
[ INFO    ] pibooth           : Show picture choice (nothing selected)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 41 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 41 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 59 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 80 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 80 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iTXt' 99 29
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 140 5756
[ DEBUG   ] pibooth           : took 0.194 seconds
[ DEBUG   ] pibooth           : Activate state 'chosen'
[ INFO    ] pibooth           : Show picture choice (1 captures selected)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IHDR' 16 13
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'iCCP' 41 388
[ DEBUG   ] PIL.PngImagePlugin: iCCP profile name b'ICC profile'
[ DEBUG   ] PIL.PngImagePlugin: Compression method 0
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'bKGD' 441 6
[ DEBUG   ] PIL.PngImagePlugin: b'bKGD' 441 6 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'pHYs' 459 9
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tIME' 480 7
[ DEBUG   ] PIL.PngImagePlugin: b'tIME' 480 7 (unknown)
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'tEXt' 499 25
[ DEBUG   ] PIL.PngImagePlugin: STREAM b'IDAT' 536 8192
[ DEBUG   ] pibooth           : took 0.110 seconds
[ DEBUG   ] pibooth           : Activate state 'preview'
[ INFO    ] pibooth           : Show preview before next capture
[ ERROR   ] pibooth           : Can not get camera preview image
[ DEBUG   ] pibooth           : Back to failsafe state due to error:
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.7/site-packages/pibooth/states.py", line 91, in set_state
    hook(cfg=self.cfg, app=self.app, win=self.win)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 327, in traced_hookexec
    return outcome.get_result()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 33, in from_call
    result = func()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_manager.py", line 324, in <lambda>
    lambda: oldcall(hook_name, hook_impls, kwargs, firstresult)
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/usr/local/lib/python3.7/dist-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "/home/pi/.local/lib/python3.7/site-packages/pibooth/plugins/camera_plugin.py", line 69, in state_preview_enter
    app.camera.preview(win)
  File "/home/pi/pibooth_driver_arducam.py", line 105, in preview
    self._window.show_image(self._get_preview_image())
  File "/home/pi/pibooth_driver_arducam.py", line 61, in _get_preview_image
    raise IOError("Can not get camera preview image")
OSError: Can not get camera preview image
[ DEBUG   ] pibooth           : Activate state 'failsafe'
[ ERROR   ] pibooth           : system='Linux', node='raspberrypi', release='5.10.63-v7l+', version='#1457 SMP Tue Sep 28 11:26:14 BST 2021', machine='armv7l', processor=''
 ***********************************************************************************
 *                     Oops! It seems that pibooth has crached                     *
 *     You can report an issue on https://github.com/pibooth/pibooth/issues/new    *
 *                       and post the file: /tmp/pibooth.log                       *
 ***********************************************************************************
[ DEBUG   ] pibooth           : Activate state 'wait'
````
i tried to put the 
```self._cam.startCamera()```
in the section ```def _specific_initialization(self):```
and now i pass the response ```raise IOError("Can not get camera preview image")```
now i have
```
File "/home/pi/pibooth_driver_arducam.py", line 64, in _get_preview_image
    image = Image.open(io.BytesIO(data.imageData))
NameError: name 'Image' is not defined
````
maybe a mistake in defining ""image" ???
sometimes there is "image" sometimes Images"
i tested with "image" but ther is an another bug


i just test with
```javascript
from PIL import Image
``` 
but now i have
```
image = Image.open(io.BytesIO(data.imageData))
NameError: name 'io' is not defined
```
?!?!
i tezsted with ````` import io```
i have now 
```
  File "/home/pi/pibooth_driver_arducam.py", line 65, in _get_preview_image
    image = Image.open(io.BytesIO(data.imageData))
  File "/usr/local/lib/python3.7/dist-packages/PIL/Image.py", line 3024, in open
    "cannot identify image file %r" % (filename if filename else fp)
PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7de3c390>
```
Can you ask to libcamera team how to load image with PIL lib?
Hi @tityann , can you try to replace `Image.open(io.BytesIO(data.imageData))`  with `Image.fromarray(data.imageData)` ?
hello @anxuae 
now the new issue is
`````
 File "/home/pi/pibooth_driver_arducam.py", line 67, in _get_preview_image
    image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
NameError: name 'sizing' is not defined
```

I don't manage to identify which library i have to import
for "size" it seem to depend from
```import numpy as np```
but still the same error....
i don't manage to find information for "sizing"
ok i found, i had to insert
```from pibooth.pictures import sizing```
and for the next severals errors i put
```
from pibooth.utils import PoolingTimer, LOGGER```
import pygame
from pibooth.language import get_translated_text
```
now i have
````
 image = Image.open(io.BytesIO(frame))
  File "/usr/local/lib/python3.7/dist-packages/PIL/Image.py", line 3024, in open
    "cannot identify image file %r" % (filename if filename else fp)
````
I done the same modification from
```
mage = Image.open(io.BytesIO(frame))
```
to
```
image = Image.fromarray(frame)
````
and now there is no bugs....
but at the end the photo is only black

It seems to be promising ;-).

I gess that the format returned by libcamera is not well understood by PIL. Can you try this:

`image = Image.fromarray(np.uint8(frame))`


at the beginning of the plugin add `import numpy as np`
i have tried..but still black
i don't know if the camera is well intitialized
because if i ask to take another photo it crash with
````
  self._window.show_image(self._get_preview_image())
  File "/home/pi/pibooth_driver_arducam.py", line 69, in _get_preview_image
    raise IOError("Can not get camera preview image")
````
when i told you that i have moved the init of the camera earlier in the script, do i did it well ?
i put it there (at the end of the init)
```javascript
   def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.set(libcamera.ExposureTime, self.preview_iso)
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.startCamera()
```
No, it shall be put in the `preview` function. 
```python
def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._cam.startCamera()
        self._window.show_image(self._get_preview_image())
```
son now i return to the old bug
```
  File "/home/pi/pibooth_driver_arducam.py", line 69, in _get_preview_image
    raise IOError("Can not get camera preview image")
```
Can you post your current plugin?
```python
"""Plugin to use libcamera camera."""

import time
import pibooth
import libcamera
import pygame
from pibooth.utils import LOGGER
from pibooth.camera.base import BaseCamera
from pibooth.pictures import sizing
from pibooth.utils import PoolingTimer, LOGGER
from pibooth.language import get_translated_text
from PIL import Image
import cv2

__version__ = "1.0.0"


class LibcameraCamera(BaseCamera):

    """Libcamera camera management.
    """

    IMAGE_EFFECTS = [u'none',
                     u'blur',
                     u'contour',
                     u'detail',
                     u'edge_enhance',
                     u'edge_enhance_more',
                     u'emboss',
                     u'find_edges',
                     u'smooth',
                     u'smooth_more',
                     u'sharpen']

    def __init__(self, camera_proxy):
        super(LibcameraCamera, self).__init__(camera_proxy)
        self._overlay_alpha = 255

    def _specific_initialization(self):
        """Camera initialization.
        """
        self._cam.initCamera(self.resolution[0], self.resolution[1],
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
        self._cam.startCamera()
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.set(libcamera.ExposureTime, 500)

    def _show_overlay(self, text, alpha):
        """Add an image as an overlay.
        """
        if self._window:  # No window means no preview displayed
            rect = self.get_rect()
            self._overlay_alpha = alpha
            self._overlay = self.build_overlay((rect.width, rect.height), str(text), alpha)

    def _get_preview_image(self):
        """Capture a new preview image.
        """
        rect = self.get_rect()

        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not get camera preview image")
        cv2.imwrite('frame.jpg', data.imageData)
        image = Image.fromarray(data.imageData)

        self._cam.returnFrameBuffer(data)
        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the available space in the window
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, (rect.width, rect.height), 'outer'))

        if self.preview_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if self._overlay:
            image.paste(self._overlay, (0, 0), self._overlay)
        return image

    def _post_process_capture(self, capture_data):
        """Rework capture data.

        :param capture_data: couple (frame, effect)
        :type capture_data: tuple
        """
        frame, effect = capture_data
        image = Image.fromarray(frame)

        # Crop to keep aspect ratio of the resolution
        image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution))
        # Resize to fit the resolution
        image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer'))

        if self.capture_flip:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)

        if effect != 'none':
            image = image.filter(getattr(ImageFilter, effect.upper()))

        return image

    def preview(self, window, flip=True):
        """Setup the preview.
        """
        self._window = window
        self.preview_flip = flip
        self._window.show_image(self._get_preview_image())

    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if self._overlay is None or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining

            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def preview_wait(self, timeout, alpha=80):
        """Wait the given time.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            updated_rect = self._window.show_image(self._get_preview_image())
            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(get_translated_text('smile'), alpha)
        self._window.show_image(self._get_preview_image())

    def stop_preview(self):
        """Stop the preview.
        """
        self._hide_overlay()
        self._window = None

    def capture(self, effect=None):
        """Capture a new picture.
        """
        effect = str(effect).lower()
        if effect not in self.IMAGE_EFFECTS:
            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))

        # if self.capture_iso != self.preview_iso:
        #     self._cam.set(libcamera.ExposureTime, self.capture_iso)

        LOGGER.debug("Taking capture at resolution %s", self.resolution)
        ret, data = self._cam.readFrame()
        if not ret:
            raise IOError("Can not capture frame")

       #  if self.capture_iso != self.preview_iso:
       #      self._cam.set(libcamera.ExposureTime, self.preview_iso)

        self._captures.append((data.imageData, effect))
        self._cam.returnFrameBuffer(data)
        time.sleep(0.5)  # To let time to see "Smile"

        self._hide_overlay()  # If stop_preview() has not been called

    def quit(self):
        """Close the camera driver, it's definitive.
        """
        if self._cam:
            self._cam.stopCamera()
            self._cam.closeCamera()


@pibooth.hookimpl
def pibooth_setup_camera(cfg):
    libcam_cam_proxy = libcamera.libcamera()

    LOGGER.info("Configuring hybrid camera (libcamera) ...")
    return LibcameraCamera(libcam_cam_proxy)
```
just for the example
this is a code which use opencv to work and it works
"i" and "d" for exposure , "s" to take a photo and "q" to quit
```python
import libcamera
import cv2
import time
import numpy as np

def display_fps(index):
    display_fps.frame_count += 1
    current = time.time()
    if current - display_fps.start >= 1:
        print("fps: {}".format(display_fps.frame_count))
        display_fps.frame_count = 0
        display_fps.start = current

display_fps.start = time.time()
display_fps.frame_count = 0

if __name__ == "__main__":
    width = 640
    height = 480
    framerate = 30

    # Instantiate the libcamera class
    cam = libcamera.libcamera()

    """
    Initialize the camera

    :param1 width: Set image width
    :param2 height: Set image height
    :param3 pixelFormat: Set image format
    :param4 buffercount: Set the number of buffers
    :param5 rotation: Set the image rotation angle
    :returns ret: Whether the camera is initialized successfully
    """
    ret = cam.initCamera(width, height, libcamera.PixelFormat.RGB888, buffercount=4, rotation=0)

    if not ret:

        # Turn on the camera
        ret = cam.startCamera()

        # Set frame rate
        frame_time = 1000000 // framerate
        cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        cam.set(libcamera.ExposureTime, 500)
        while True:

            """
            Read image information

            :returns ret: Whether the image is successfully read
            :returns data: Image data information
            """
            ret, data = cam.readFrame()

            if not ret:
                continue

            # Get image data
            # At present, only RGB888, BGR888, XRGB8888 can be displayed directly, no conversion
            frame = data.imageData

            cv2.imshow("image", frame)

            key = cv2.waitKey(1)
            if key == ord("q"):
                break
            elif key == ord("s"):
                cv2.imwrite("test.jpg", frame)

            display_fps(0)

            """
            Return image buffer

            :param data: Send image data back
            """
            cam.returnFrameBuffer(data)

        cam.stopCamera()

    cam.closeCamera()
```
I made some modifications in the plugin code that you posted in the comment above, can you retest?
(I have no other solution to test, I can use my raspberrypi camera with libcamera)
hello
thanks a lot for the help
```
File "/home/pi/pibooth_driver_arducam.py", line 63, in _get_preview_image
    raise IOError("Can not get camera preview image")
OSError: Can not get camera preview image
````

the only way by the past to go over this error was to put the "camstart" earlier in the script.
when i putted it in the init there was no error

after testing the camstart in the init, i confirme that i have no bug...but the photo is still black
I made new changes, and commented the ExposureTime setting why maybe cause the black image, can you have a new try?
ok i will try this evening.
you commented the "ExposureTime setting", but this option is only on the capture ? , the preview have no "ExposureTime setting" but the preview is also black
i have tested your new modification
there is no crash
but the photo is (buggy) with color line
i ddon't know if there is a difference with black photo, because i have tested today with a lot of light
Can you save the frame in a file and attache it? I will try to load it.

I've modified the plugin to write frames in a file `frame.txt`
this is the frame.txt

https://transfert.free.fr/download/5BbvXLcHalXiFM6GfzlMft7pvrA
Sorry it does not work properly, can you retry with the newly updated code and send the frame.txt file?
with your modification i get
```
 File "/home/pi/pibooth_driver_arducam.py", line 67, in _get_preview_image
    cv2.imwrite('frame.txt', data.imageData)
cv2.error: /build/opencv-L65chJ/opencv-3.2.0+dfsg/modules/imgcodecs/src/loadsave.cpp:605: error: (-2) could not find a writer for the specified extension in function imwrite_
```
ok, modified again, the file is now called `frame.jpg`. it should work
the first file in .txt was 14mo and this jpeg is only 1 mo
https://transfert.free.fr/download/Il8E4Xc5Uk6fAjXHDwIRf6ixj4A
I'm not able to improve the image, so there is really something wrong this the capture taken with the camera.
Maybe something wrong with this part of the code:

```python
        self._cam.startCamera()
        framerate = 30
        frame_time = 1000000 // framerate
        self._cam.set(libcamera.FrameDurationLimits, [frame_time, frame_time])
        self._cam.set(libcamera.ExposureTime, 500)
```

But I'm not able to go further without a camera to test... Thus I think that only you can try different way to handle the camera. You have a good basis for your plugin.
hello @anxuae 
i have found by chance what are two of the problems
first i have forced 
```
width = 640
height = 480

 self._cam.initCamera(width, height,
                             libcamera.PixelFormat.RGB888, buffercount=4,
                             rotation=self.preview_rotation)
````
a la place de 
```self.resolution[0], self.resolution[1]```

and the second problem is from

```self._overlay_alpha```
after changing the value from 255 to somethings else like 000 or 111 it works i had a photo..dirty but a photo

i will test what was wrong about the resolution and then found a quiet goog value for the "alpha"

next in resolution of the probleme
 result of ``` v4l2-ctl --list-formats-ext``` is
```
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'pBAA' (10-bit Bayer BGBG/GRGR Packed)
                Size: Discrete 4640x3472
                Size: Discrete 2320x1732
                Size: Discrete 3264x2464
                Size: Discrete 3840x2160
                Size: Discrete 2592x1944
                Size: Discrete 1920x1080
                Size: Discrete 1280x800
                Size: Discrete 1280x720
        [1]: 'BG10' (10-bit Bayer BGBG/GRGR)
                Size: Discrete 4640x3472
                Size: Discrete 2320x1732
                Size: Discrete 3264x2464
                Size: Discrete 3840x2160
                Size: Discrete 2592x1944
                Size: Discrete 1920x1080
                Size: Discrete 1280x800
                Size: Discrete 1280x720
```
so i put like the original ```self.resolution[0], self.resolution[1]```
and in the ```pibooth config file i test with ```2592 , 1944```` it works wih tags but the format is landscape instaed of portrait
so i tested ```1944 , 2592```` but tis one don(t works

so i let the ```2592 , 1944```` landscape format
because high resolution make lags, how can i do to make preview in low resolution and capture in high resolution ?
the 2592*1944 is normally @20fps... in my example is like 1 fps
i tested with the ```self._overlay_alpha``` and now even with 255 , it works
but the image is not fine...

Fix patch:
diff --git a/.pylintrc b/.pylintrc
index bf04d0f0..5f88f696 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -21,13 +21,13 @@ analyse-fallback-blocks=no
 # A comma-separated list of package or module names from where C extensions may
 # be loaded. Extensions are loading into the active Python interpreter and may
 # run arbitrary code.
-extension-pkg-allow-list=
+extension-pkg-allow-list=pygame,cv2,gphoto2
 
 # A comma-separated list of package or module names from where C extensions may
 # be loaded. Extensions are loading into the active Python interpreter and may
 # run arbitrary code. (This is an alternative name to extension-pkg-allow-list
 # for backward compatibility.)
-extension-pkg-whitelist=pygame
+extension-pkg-whitelist=
 
 # Return non-zero exit code if any of these messages/categories are detected,
 # even if score is above --fail-under value. Syntax same as enable. Messages
@@ -218,7 +218,7 @@ contextmanager-decorators=contextlib.contextmanager
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E1101 when accessed. Python regular
 # expressions are accepted.
-generated-members=
+generated-members=cv2.*
 
 # Tells whether to warn about missing members when the owner of the attribute
 # is inferred to be None.
diff --git a/docs/sources/install.rst b/docs/sources/install.rst
index aa07e797..698a26a4 100644
--- a/docs/sources/install.rst
+++ b/docs/sources/install.rst
@@ -28,10 +28,10 @@ Software
 ^^^^^^^^
 
 * Raspberry Pi OS **Bullseye** with desktop
-* Python ``3.7.3``
+* Python ``3.9.2``
 * libsdl2 ``2.0``
 * libgphoto2 ``2.5.27``
-* libcups ``2.2.10``
+* libcups ``2.3.3``
 
 
 Install
@@ -66,7 +66,7 @@ Manual procedure
 
    .. code-block:: bash
 
-        sudo apt-get install libsdl2-*
+        sudo apt-get install -y libsdl2-*
 
 5. Optionally install the last stable ``gPhoto2`` version (required only for
    DSLR camera):
@@ -84,8 +84,7 @@ Manual procedure
 
    .. code-block:: bash
 
-        sudo apt-get install cups libcups2-dev
-        sudo apt-get install printer-driver-gutenprint
+        sudo apt-get install -y cups libcups2-dev printer-driver-gutenprint
 
 7. Optionally install ``OpenCV`` to improve images generation efficiency or if a
    Webcam is used:
diff --git a/docs/sources/plugins/pibooth_custom_camera.py b/docs/sources/plugins/pibooth_custom_camera.py
index 83fe3474..9e5e374b 100644
--- a/docs/sources/plugins/pibooth_custom_camera.py
+++ b/docs/sources/plugins/pibooth_custom_camera.py
@@ -20,7 +20,7 @@ def get_capture_image(self, effect=None):
             except Exception:
                 LOGGER.warning("Gphoto2 fails to capture, trying again...")
             retry += 1
-        raise EnvironmentError("Gphoto2 fails to capture {} times".format(max_retry))
+        raise EnvironmentError(f"Gphoto2 fails to capture {max_retry} times")
 
 
 class HybridRpiCameraRetry(camera.HybridRpiCamera):
diff --git a/docs/sources/plugins/pibooth_ftp.py b/docs/sources/plugins/pibooth_ftp.py
index 878ce128..df956f3e 100644
--- a/docs/sources/plugins/pibooth_ftp.py
+++ b/docs/sources/plugins/pibooth_ftp.py
@@ -6,6 +6,7 @@
 
 __version__ = "0.0.2"
 
+
 @pibooth.hookimpl
 def pibooth_startup(app):
     app.ftp = FTP()
@@ -13,12 +14,14 @@ def pibooth_startup(app):
     app.ftp.connect("ftp.pibooth.org", 21)
     app.ftp.login("pibooth", "1h!gR4/opK")
 
+
 @pibooth.hookimpl
 def state_processing_exit(app):
     name = os.path.basename(app.previous_picture_file)
 
     with open(app.previous_picture_file, 'rb') as fp:
-        app.ftp.storbinary('STOR {}'.format(name), fp, 1024)
+        app.ftp.storbinary(f'STOR {name}', fp, 1024)
+
 
 @pibooth.hookimpl
 def pibooth_cleanup(app):
diff --git a/pibooth/__main__.py b/pibooth/__main__.py
index 4fa27f70..e66c958f 100644
--- a/pibooth/__main__.py
+++ b/pibooth/__main__.py
@@ -26,7 +26,7 @@
 # Set the default pin factory to a mock factory if pibooth is not started a Raspberry Pi
 try:
     filterwarnings("ignore", category=PinFactoryFallback)
-    GPIO_INFO = "on Raspberry pi {0}".format(pi_info().model)
+    GPIO_INFO = f"on Raspberry pi {pi_info().model}"
 except BadPinFactory:
     Device.pin_factory = MockFactory()
     GPIO_INFO = "without physical GPIO, fallback to GPIO mock"
diff --git a/pibooth/app.py b/pibooth/app.py
index c45feb47..006be009 100644
--- a/pibooth/app.py
+++ b/pibooth/app.py
@@ -36,7 +36,7 @@ def load_last_saved_picture(path):
     return (None, None)
 
 
-class PiboothApplication(object):
+class PiboothApplication:
 
     """Main class representing the ``pibooth`` software.
 
@@ -256,7 +256,7 @@ def update(self, events):
                 self.leds.off()
                 self._initialize()
                 self._machine.set_state('wait')
-        else:
+        elif not self._window.is_menu_shown:
             self._machine.process(events)
 
     def exec(self, enable_profiler=False):
diff --git a/pibooth/camera/__init__.py b/pibooth/camera/__init__.py
index e6e20f40..5451e5f6 100644
--- a/pibooth/camera/__init__.py
+++ b/pibooth/camera/__init__.py
@@ -4,10 +4,11 @@
 from pibooth.camera.rpi import RpiCamera, get_rpi_camera_proxy
 from pibooth.camera.gphoto import GpCamera, get_gp_camera_proxy
 from pibooth.camera.opencv import CvCamera, get_cv_camera_proxy
-from pibooth.camera.hybrid import HybridRpiCamera, HybridCvCamera
+from pibooth.camera.libcamera import LibCamera, get_libcamera_camera_proxy
+from pibooth.camera.hybrid import HybridLibCamera, HybridRpiCamera, HybridCvCamera
 
 
-def close_proxy(rpi_cam_proxy, gp_cam_proxy, cv_cam_proxy):
+def close_proxy(rpi_cam_proxy, gp_cam_proxy, cv_cam_proxy, lib_cam_proxy):
     """Close proxy drivers.
     """
     if rpi_cam_proxy:
@@ -16,6 +17,8 @@ def close_proxy(rpi_cam_proxy, gp_cam_proxy, cv_cam_proxy):
         GpCamera(gp_cam_proxy).quit()
     if cv_cam_proxy:
         CvCamera(cv_cam_proxy).quit()
+    if lib_cam_proxy:
+        LibCamera(lib_cam_proxy).quit()
 
 
 def find_camera():
@@ -27,26 +30,35 @@ def find_camera():
     rpi_cam_proxy = get_rpi_camera_proxy()
     gp_cam_proxy = get_gp_camera_proxy()
     cv_cam_proxy = get_cv_camera_proxy()
+    lib_cam_proxy = get_libcamera_camera_proxy()
 
+    if lib_cam_proxy and gp_cam_proxy:
+        LOGGER.info("Configuring hybrid camera (Libcamera + gPhoto2) ...")
+        close_proxy(rpi_cam_proxy, None, cv_cam_proxy, None)
+        return HybridLibCamera(lib_cam_proxy, gp_cam_proxy)
     if rpi_cam_proxy and gp_cam_proxy:
         LOGGER.info("Configuring hybrid camera (Picamera + gPhoto2) ...")
-        close_proxy(None, None, cv_cam_proxy)
+        close_proxy(None, None, cv_cam_proxy, lib_cam_proxy)
         return HybridRpiCamera(rpi_cam_proxy, gp_cam_proxy)
-    elif cv_cam_proxy and gp_cam_proxy:
+    if cv_cam_proxy and gp_cam_proxy:
         LOGGER.info("Configuring hybrid camera (OpenCV + gPhoto2) ...")
-        close_proxy(rpi_cam_proxy, None, None)
+        close_proxy(rpi_cam_proxy, None, None, lib_cam_proxy)
         return HybridCvCamera(cv_cam_proxy, gp_cam_proxy)
-    elif gp_cam_proxy:
+    if gp_cam_proxy:
         LOGGER.info("Configuring gPhoto2 camera ...")
-        close_proxy(rpi_cam_proxy, None, cv_cam_proxy)
+        close_proxy(rpi_cam_proxy, None, cv_cam_proxy, lib_cam_proxy)
         return GpCamera(gp_cam_proxy)
-    elif rpi_cam_proxy:
+    if lib_cam_proxy:
+        LOGGER.info("Configuring Libcamera camera ...")
+        close_proxy(rpi_cam_proxy, gp_cam_proxy, cv_cam_proxy, None)
+        return LibCamera(lib_cam_proxy)
+    if rpi_cam_proxy:
         LOGGER.info("Configuring Picamera camera ...")
-        close_proxy(None, gp_cam_proxy, cv_cam_proxy)
+        close_proxy(None, gp_cam_proxy, cv_cam_proxy, lib_cam_proxy)
         return RpiCamera(rpi_cam_proxy)
-    elif cv_cam_proxy:
+    if cv_cam_proxy:
         LOGGER.info("Configuring OpenCV camera ...")
-        close_proxy(rpi_cam_proxy, gp_cam_proxy, None)
+        close_proxy(rpi_cam_proxy, gp_cam_proxy, None, lib_cam_proxy)
         return CvCamera(cv_cam_proxy)
 
-    raise EnvironmentError("Neither Raspberry Pi nor GPhoto2 nor OpenCV camera detected")
+    raise EnvironmentError("Neither Raspberry Pi nor GPhoto2 nor Libcamera nor OpenCV camera detected")
diff --git a/pibooth/camera/base.py b/pibooth/camera/base.py
index 26f957cb..893ad68e 100644
--- a/pibooth/camera/base.py
+++ b/pibooth/camera/base.py
@@ -8,7 +8,7 @@
 from pibooth.pictures import sizing
 
 
-class BaseCamera(object):
+class BaseCamera:
     """Base class for camera.
 
     :py:class:`BaseCamera` emits the following events consumed by plugins:
@@ -17,7 +17,17 @@ class BaseCamera(object):
     - EVT_PIBOOTH_CAM_CAPTURE
     """
 
-    IMAGE_EFFECTS = ['none']
+    IMAGE_EFFECTS = [u'none',
+                     u'blur',
+                     u'contour',
+                     u'detail',
+                     u'edge_enhance',
+                     u'edge_enhance_more',
+                     u'emboss',
+                     u'find_edges',
+                     u'smooth',
+                     u'smooth_more',
+                     u'sharpen']
 
     def __init__(self, camera_proxy):
         self._cam = camera_proxy
@@ -41,10 +51,9 @@ def initialize(self, iso, resolution, rotation=0, flip=False, delete_internal_me
             rotation = (rotation, rotation)
         self.preview_rotation, self.capture_rotation = rotation
         for name in ('preview', 'capture'):
-            rotation = getattr(self, '{}_rotation'.format(name))
+            rotation = getattr(self, f'{name}_rotation')
             if rotation not in (0, 90, 180, 270):
-                raise ValueError(
-                    "Invalid {} camera rotation value '{}' (should be 0, 90, 180 or 270)".format(name, rotation))
+                raise ValueError(f"Invalid {name} camera rotation value '{rotation}' (should be 0, 90, 180 or 270)")
         self.resolution = resolution
         self.capture_flip = flip
         if not isinstance(iso, (tuple, list)):
@@ -142,7 +151,7 @@ def capture(self, effect=None, wait=False):
         """
         effect = str(effect).lower()
         if effect not in self.IMAGE_EFFECTS:
-            raise ValueError("Invalid capture effect '{}' (choose among {})".format(effect, self.IMAGE_EFFECTS))
+            raise ValueError(f"Invalid capture effect '{effect}' (choose among {self.IMAGE_EFFECTS})")
 
         if self._worker:
             self.stop_preview()
diff --git a/pibooth/camera/gphoto.py b/pibooth/camera/gphoto.py
index d4d41f89..58e1a8a7 100644
--- a/pibooth/camera/gphoto.py
+++ b/pibooth/camera/gphoto.py
@@ -35,8 +35,12 @@ def get_gp_camera_proxy(port=None):
         abilities_list.load()
         cameras = abilities_list.detect(port_info_list)
     if cameras:
-        LOGGER.debug("Found gPhoto2 cameras on ports: '%s'", "' / '".join([p for _, p in cameras]))
-        # Initialize first camera proxy and return it
+        LOGGER.debug("Found gPhoto2 cameras:")
+        for index, (name, addr) in enumerate(sorted(cameras, key=lambda x: x[0])):
+            selected = ">" if addr == port or (port is None and index == 0) else "*"
+            LOGGER.debug("  %s %s : name-> %s | addr-> %s", selected, f"{index:02d}", name, addr)
+
+        # Initialize camera proxy and return it
         camera = gp.Camera()
         if port is not None:
             port_info_list = gp.PortInfoList()
@@ -64,18 +68,6 @@ class GpCamera(BaseCamera):
     """gPhoto2 camera management.
     """
 
-    IMAGE_EFFECTS = ['none',
-                     'blur',
-                     'contour',
-                     'detail',
-                     'edge_enhance',
-                     'edge_enhance_more',
-                     'emboss',
-                     'find_edges',
-                     'smooth',
-                     'smooth_more',
-                     'sharpen']
-
     def __init__(self, camera_proxy):
         super().__init__(camera_proxy)
         self._gp_logcb = None
@@ -138,7 +130,7 @@ def get_config_value(self, section, option):
             LOGGER.debug('Getting option %s/%s=%s', section, option, value)
             return value
         except gp.GPhoto2Error:
-            raise ValueError('Unknown option {}/{}'.format(section, option))
+            raise ValueError(f'Unknown option {section}/{option}')
 
     def _rotate_image(self, image, rotation):
         """Rotate a PIL image, same direction than RpiCamera.
diff --git a/pibooth/camera/hybrid.py b/pibooth/camera/hybrid.py
index ea763858..26de51e7 100644
--- a/pibooth/camera/hybrid.py
+++ b/pibooth/camera/hybrid.py
@@ -1,10 +1,51 @@
 # -*- coding: utf-8 -*-
 
+from pibooth.camera.libcamera import LibCamera
 from pibooth.camera.rpi import RpiCamera
 from pibooth.camera.opencv import CvCamera
 from pibooth.camera.gphoto import GpCamera
 
 
+class HybridLibCamera(LibCamera):
+
+    """Camera management using the Raspberry Pi camera with LibCamera driver for the
+    preview (better video rendering) and a gPhoto2 compatible camera for the capture
+    (higher resolution)
+    """
+
+    IMAGE_EFFECTS = GpCamera.IMAGE_EFFECTS
+
+    def __init__(self, libcamera_camera_proxy, gp_camera_proxy):
+        super().__init__(libcamera_camera_proxy)
+        self._gp_cam = GpCamera(gp_camera_proxy)
+        self._gp_cam._captures = self._captures  # Same dict for both cameras
+
+    def initialize(self, *args, **kwargs):
+        """Ensure that both cameras are initialized.
+        """
+        super().initialize(*args, **kwargs)
+        self._gp_cam.initialize(*args, **kwargs)
+
+    def _process_capture(self, capture_data):
+        """Rework capture data.
+
+        :param capture_data: couple (GPhotoPath, effect)
+        :type capture_data: tuple
+        """
+        return self._gp_cam._process_capture(capture_data)
+
+    def get_capture_image(self, effect=None):
+        """Capture a picture in a file.
+        """
+        return self._gp_cam.get_capture_image(effect)
+
+    def _specific_cleanup(self):
+        """Ensure that both cameras are cleaned.
+        """
+        super()._specific_cleanup()
+        self._gp_cam._specific_cleanup()
+
+
 class HybridRpiCamera(RpiCamera):
 
     """Camera management using the Raspberry Pi camera for the preview (better
diff --git a/pibooth/camera/libcamera.py b/pibooth/camera/libcamera.py
new file mode 100644
index 00000000..3b13833c
--- /dev/null
+++ b/pibooth/camera/libcamera.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import numpy
+import pygame
+try:
+    import picamera2
+    from picamera2 import Picamera2, Preview
+    from libcamera import Transform
+except ImportError:
+    picamera2 = None  # picamera2 is optional
+from PIL import ImageFilter
+from pibooth.pictures import sizing
+from pibooth.utils import LOGGER
+from pibooth.camera.base import BaseCamera
+
+
+def get_libcamera_camera_proxy(port=None):
+    """Return camera proxy if a Raspberry Pi compatible camera is found
+    else return None.
+
+    :param port: look on given index number
+    :type port: int
+    """
+    if not picamera2:
+        return None  # picamera2 is not installed
+
+    cameras = Picamera2.global_camera_info()
+    if cameras:
+        LOGGER.debug("Found libcamera cameras:")
+        for index, info in enumerate(cameras):
+            selected = ">" if index == port or (port is None and index == 0) else "*"
+            LOGGER.debug("  %s %s : name-> %s | addr-> %s", selected, f"{index:02d}", info["Model"], info["Location"])
+
+        if port is not None:
+            return Picamera2(camera_num=port)
+        return Picamera2()
+
+    return None
+
+
+class LibCamera(BaseCamera):
+
+    """Libcamera camera management.
+    """
+
+    def __init__(self, libcamera_camera_proxy):
+        super().__init__(libcamera_camera_proxy)
+        self._preview_config = self._cam.create_preview_configuration()
+        self._capture_config = self._cam.create_still_configuration()
+
+    def _specific_initialization(self):
+        """Camera initialization.
+        """
+        self._cam.stop()
+        self._preview_config['transform'] = Transform(hflip=self.preview_flip)
+        self._cam.configure(self._preview_config)
+
+        self._capture_config['size'] = self.resolution
+        self._capture_config['transform'] = Transform(hflip=self.capture_flip)
+        self._cam.start()
+
+    def _show_overlay(self):
+        """Add an image as an overlay.
+        """
+        # Create an image padded to the required size (required by picamera)
+        size = (((self._rect.width + 31) // 32) * 32, ((self._rect.height + 15) // 16) * 16)
+
+        self._overlay = self.build_overlay(size, self._overlay_text, self._overlay_alpha)
+        self._cam.set_overlay(numpy.array(self._overlay))
+
+    def _hide_overlay(self):
+        """Remove any existing overlay.
+        """
+        if self._overlay:
+            self._cam.set_overlay(None)
+            self._overlay = None
+            self._overlay_text = None
+
+    def _process_capture(self, capture_data):
+        """Rework capture data.
+
+        :param capture_data: couple (PIL Image, effect)
+        :type capture_data: tuple
+        """
+        image, effect = capture_data
+        if effect != 'none':
+            image = image.filter(getattr(ImageFilter, effect.upper()))
+        return image
+
+    def preview(self, rect, flip=True):
+        """Display a preview on the given Rect (flip if necessary).
+        """
+        if self._cam._preview is not None:
+            # Already running
+            return
+
+        self.preview_flip = flip
+        self._preview_config['transform'] = Transform(hflip=self.preview_flip)
+
+        # Define Rect() object for resizing preview captures to fit to the defined
+        # preview rect keeping same aspect ratio than camera resolution.
+        size = sizing.new_size_keep_aspect_ratio(self.resolution, (rect.width, rect.height))
+        self._rect = pygame.Rect(rect.centerx - size[0] // 2, rect.centery - size[1] // 2, size[0], size[1])
+
+        self._cam.stop()
+        self._cam.configure(self._preview_config)
+        self._cam.start_preview(Preview.DRM, x=self._rect.x, y=self._rect.y,
+                                width=self._rect.width, height=self._rect.height)
+        self._cam.start()
+
+    def stop_preview(self):
+        """Stop the preview.
+        """
+        self._rect = None
+        self._hide_overlay()
+        if self._cam._preview is not None:
+            self._cam.stop_preview()
+
+    def get_capture_image(self, effect=None):
+        """Capture a new picture in a file.
+        """
+        image = self._cam.switch_mode_and_capture_image(self._capture_config, "main")
+        self._captures.append((image, effect))
+        return image
+
+    def _specific_cleanup(self):
+        """Close the camera driver, it's definitive.
+        """
+        self._cam.close()
diff --git a/pibooth/camera/opencv.py b/pibooth/camera/opencv.py
index e6a8dc45..08b295ea 100644
--- a/pibooth/camera/opencv.py
+++ b/pibooth/camera/opencv.py
@@ -15,7 +15,7 @@ def get_cv_camera_proxy(port=None):
     """Return camera proxy if an OpenCV compatible camera is found
     else return None.
 
-    :param port: look on given port number
+    :param port: look on given index number
     :type port: int
     """
     if not cv2:
@@ -23,7 +23,7 @@ def get_cv_camera_proxy(port=None):
 
     if port is not None:
         if not isinstance(port, int):
-            raise TypeError("Invalid OpenCV camera port '{}'".format(type(port)))
+            raise TypeError(f"Invalid OpenCV camera port '{type(port)}'")
         camera = cv2.VideoCapture(port)
         if camera.isOpened():
             return camera
@@ -41,18 +41,6 @@ class CvCamera(BaseCamera):
     """OpenCV camera management.
     """
 
-    IMAGE_EFFECTS = ['none',
-                     'blur',
-                     'contour',
-                     'detail',
-                     'edge_enhance',
-                     'edge_enhance_more',
-                     'emboss',
-                     'find_edges',
-                     'smooth',
-                     'smooth_more',
-                     'sharpen']
-
     def __init__(self, camera_proxy):
         super().__init__(camera_proxy)
         self._overlay_alpha = 255
diff --git a/pibooth/camera/rpi.py b/pibooth/camera/rpi.py
index eea19fa6..d645a73f 100644
--- a/pibooth/camera/rpi.py
+++ b/pibooth/camera/rpi.py
@@ -41,8 +41,6 @@ class RpiCamera(BaseCamera):
 
     if picamera:
         IMAGE_EFFECTS = list(picamera.PiCamera.IMAGE_EFFECTS.keys())
-    else:
-        IMAGE_EFFECTS = []
 
     def _specific_initialization(self):
         """Camera initialization.
diff --git a/pibooth/config/default.py b/pibooth/config/default.py
index fd9a93d9..96dd01e4 100644
--- a/pibooth/config/default.py
+++ b/pibooth/config/default.py
@@ -23,7 +23,7 @@ def add_default_option(section, option, default, description, menu_name=None, me
 # --- GENERAL ------------------------------------------------------------------
 
 add_default_option("GENERAL", "language", "en",
-                   "User interface language: {}".format(values_list_repr(language.get_supported_languages())),
+                   f"User interface language: {values_list_repr(language.get_supported_languages())}",
                    "UI language", language.get_supported_languages())
 
 add_default_option("GENERAL", "directory", "~/Pictures/pibooth",
diff --git a/pibooth/config/parser.py b/pibooth/config/parser.py
index a8fe40d5..5932246c 100644
--- a/pibooth/config/parser.py
+++ b/pibooth/config/parser.py
@@ -63,6 +63,7 @@ def save(self, default=False):
 
         with io.open(self.filename, 'w', encoding="utf-8") as fp:
             for section, options in DEFAULT.items():
+
                 # 1. Write defined options
                 fp.write(f"[{section}]\n")
                 for name, value in options.items():
@@ -88,6 +89,7 @@ def save(self, default=False):
                             fp.write(option_pattern.format(comment="Unknown option, maybe from a disabled plugin?",
                                                            name=name, value=value))
 
+
         self.handle_autostart()
 
     def load(self, clean=False):
@@ -177,11 +179,11 @@ def add_option(self, section, option, default, description, menu_name=None, menu
 
         # Check that the option is not already created
         if section in DEFAULT and option in DEFAULT[section]:
-            raise ValueError("The plugin '{}' try to define the option [{}][{}] "
-                             "which is already defined.".format(plugin_name, section, option))
+            raise ValueError(f"The plugin '{plugin_name}' try to define the option [{section}][{option}] "
+                             "which is already defined.")
 
         # Add the option to the default dictionary
-        description = "{}\n# Required by '{}' plugin".format(description, plugin_name)
+        description = f"{description}\n# Required by '{plugin_name}' plugin"
         add_default_option(section, option, default, description, menu_name, menu_choices)
 
     def get(self, section, option, **kwargs):
@@ -288,7 +290,7 @@ def gettuple(self, section, option, types, extend=0):
 
         if not isinstance(values, (tuple, list)):
             if not isinstance(values, types):
-                raise ValueError("Invalid config value [{}][{}]={}".format(section, option, values))
+                raise ValueError(f"Invalid config value [{section}][{option}]={values}")
             if values == '' and extend == 0:
                 # Empty config key and empty tuple accepted
                 values = ()
@@ -299,7 +301,7 @@ def gettuple(self, section, option, types, extend=0):
             if color and len(values) == 3 and all(isinstance(elem, int) for elem in values):
                 values = (values,)
             elif not all(isinstance(elem, types) for elem in values):
-                raise ValueError("Invalid config value [{}][{}]={}".format(section, option, values))
+                raise ValueError(f"Invalid config value [{section}][{option}]={values}")
 
         if path:
             new_values = []
diff --git a/pibooth/counters.py b/pibooth/counters.py
index 9f37b55c..0add4e93 100644
--- a/pibooth/counters.py
+++ b/pibooth/counters.py
@@ -5,7 +5,7 @@
 from pibooth.utils import LOGGER
 
 
-class Counters(object):
+class Counters:
 
     def __init__(self, filename='', **kwargs):
         self.data = kwargs.copy()
@@ -15,7 +15,7 @@ def __init__(self, filename='', **kwargs):
             self.load()
 
     def __str__(self):
-        return ", ".join("{}:{}".format(key, value) for key, value in self.data.items())
+        return ", ".join(f"{key}:{value}" for key, value in self.data.items())
 
     def __iter__(self):
         """Iterate over counters names.
@@ -31,7 +31,7 @@ def __getattr__(self, name):
         """Called only when an attribute does not exist.
         """
         if name not in self.data:
-            raise AttributeError("No counter with name '{}'".format(name))
+            raise AttributeError(f"No counter with name '{name}'")
         return self.data[name]
 
     def __setattr__(self, name, value):
diff --git a/pibooth/fonts/__init__.py b/pibooth/fonts/__init__.py
index 97e5d77f..031b34af 100644
--- a/pibooth/fonts/__init__.py
+++ b/pibooth/fonts/__init__.py
@@ -50,7 +50,7 @@ def get_filename(name):
         if sim > most_similar_ratio:
             most_similar = font_name
             most_similar_ratio = sim
-    raise ValueError('System font "{0}" unknown, maybe you mean "{1}"'.format(name, most_similar))
+    raise ValueError(f"System font '{name}' unknown, maybe you mean '{most_similar}'")
 
 
 def get_pil_font(text, font_name, max_width, max_height):
diff --git a/pibooth/language.py b/pibooth/language.py
index 9c873244..63a98c4a 100644
--- a/pibooth/language.py
+++ b/pibooth/language.py
@@ -212,13 +212,13 @@ def init(filename, clear=False):
 
         with io.open(PARSER.filename, 'w', encoding="utf-8") as fp:
             for section, options in DEFAULT.items():
-                fp.write("[{}]\n".format(section))
+                fp.write(f"[{section}]\n")
                 for name, value in options.items():
                     value = value.splitlines()
-                    fp.write("{} = {}\n".format(name, value[0]))
+                    fp.write(f"{name} = {value[0]}\n")
                     if len(value) > 1:
                         for part in value[1:]:
-                            fp.write("    {}\n".format(part))
+                            fp.write(f"    {part}\n")
                 fp.write("\n\n")
 
     PARSER.read(PARSER.filename, encoding='utf-8')
diff --git a/pibooth/pictures/__init__.py b/pibooth/pictures/__init__.py
index b4d7f730..9a1dd7cb 100644
--- a/pibooth/pictures/__init__.py
+++ b/pibooth/pictures/__init__.py
@@ -55,10 +55,11 @@ def colorize_pil_image(pil_image, color, bg_color=None):
     """
     if not bg_color:
         bg_color = (abs(color[0] - 255), abs(color[1] - 255), abs(color[2] - 255))
-    _, _, _, alpha = pil_image.split()
     gray_pil_image = pil_image.convert('L')
     new_pil_image = ImageOps.colorize(gray_pil_image, black=bg_color, white=color)
-    new_pil_image.putalpha(alpha)
+    if pil_image.mode in ('RGBA', 'LA') or (pil_image.mode == 'P' and 'transparency' in pil_image.info):
+        _, _, _, alpha = pil_image.split()
+        new_pil_image.putalpha(alpha)
     return new_pil_image
 
 
diff --git a/pibooth/pictures/factory.py b/pibooth/pictures/factory.py
index 176b31d6..ddbd5b6d 100644
--- a/pibooth/pictures/factory.py
+++ b/pibooth/pictures/factory.py
@@ -2,19 +2,19 @@
 
 import os
 import os.path as osp
-from pibooth import fonts
-from pibooth.utils import LOGGER
-from pibooth.pictures import sizing
 from PIL import Image, ImageDraw
-
 try:
     import cv2
     import numpy as np
 except ImportError:
     cv2 = None
 
+from pibooth import fonts
+from pibooth.utils import LOGGER
+from pibooth.pictures import sizing
+
 
-class PictureFactory(object):
+class PictureFactory:
 
     """
     Concatenate up to 4 PIL images in portrait orientation...
@@ -270,7 +270,7 @@ def add_text(self, text, font_name, color, align=CENTER):
         :param align: text alignment: left, right or center
         :type align: str
         """
-        assert align in [self.CENTER, self.RIGHT, self.LEFT], "Unknown aligment '{}'".format(align)
+        assert align in [self.CENTER, self.RIGHT, self.LEFT], f"Unknown aligment '{align}'"
         self._texts.append((text, fonts.get_filename(font_name), color, align))
         if self.is_portrait:
             self._texts_height = int(self.height // 6)
@@ -290,7 +290,7 @@ def set_background(self, color_or_path):
             self._background_color = color_or_path
         else:
             if not osp.isfile(color_or_path):
-                raise ValueError("Invalid background image '{}'".format(color_or_path))
+                raise ValueError(f"Invalid background image '{color_or_path}'")
             self._background_image = color_or_path
         self._final = None  # Force rebuild
 
@@ -301,7 +301,7 @@ def set_overlay(self, image_path):
         :type image_path: str
         """
         if not osp.isfile(image_path):
-            raise ValueError("Invalid background image '{}'".format(image_path))
+            raise ValueError(f"Invalid background image '{image_path}'")
         self._overlay_image = image_path
         self._final = None  # Force rebuild
 
diff --git a/pibooth/plugins/__init__.py b/pibooth/plugins/__init__.py
index 27abea82..e6269f26 100644
--- a/pibooth/plugins/__init__.py
+++ b/pibooth/plugins/__init__.py
@@ -129,9 +129,9 @@ def get_friendly_name(self, plugin, version=True):
             vnumber = getattr(plugin, '__version__', '?.?.?')
 
         if version:
-            name = "{}-{}".format(name, vnumber)
+            name = f"{name}-{vnumber}"
         else:
-            name = "{}".format(name)
+            name = f"{name}"
 
         # Questionable convenience, but it keeps things short
         if name.startswith("pibooth-") or name.startswith("pibooth_"):
diff --git a/pibooth/plugins/camera_plugin.py b/pibooth/plugins/camera_plugin.py
index 5080cbdb..2b5b3731 100644
--- a/pibooth/plugins/camera_plugin.py
+++ b/pibooth/plugins/camera_plugin.py
@@ -9,7 +9,7 @@
 from pibooth.utils import LOGGER, PollingTimer
 
 
-class CameraPlugin(object):
+class CameraPlugin:
 
     """Plugin to manage the camera captures.
     """
@@ -92,7 +92,7 @@ def state_capture_enter(self, cfg, app):
             effect = effects[self.capture_count]
         else:
             # Not possible
-            raise ValueError("Not enough effects defined for {} captures {}".format(app.capture_nbr, effects))
+            raise ValueError(f"Not enough effects defined for {app.capture_nbr} captures {effects}")
 
         LOGGER.info("Take a capture")
         app.camera.capture(effect)
@@ -104,9 +104,7 @@ def state_capture_do(self, events):
             self.capture_count += 1
 
     @pibooth.hookimpl
-    def state_capture_exit(self, app):
-        app.camera.stop_preview()
-
-    @pibooth.hookimpl
-    def state_processing_enter(self):
+    def state_processing_enter(self, app):
         self.capture_count = 0
+        LOGGER.info("Stopping preview")
+        app.camera.stop_preview()
diff --git a/pibooth/plugins/lights_plugin.py b/pibooth/plugins/lights_plugin.py
index 89720a76..60798077 100644
--- a/pibooth/plugins/lights_plugin.py
+++ b/pibooth/plugins/lights_plugin.py
@@ -4,7 +4,7 @@
 from pibooth import evts
 
 
-class LightsPlugin(object):
+class LightsPlugin:
     """Plugin to manage the lights via GPIO.
     """
 
diff --git a/pibooth/plugins/picture_plugin.py b/pibooth/plugins/picture_plugin.py
index be1d841e..84e99aca 100644
--- a/pibooth/plugins/picture_plugin.py
+++ b/pibooth/plugins/picture_plugin.py
@@ -18,7 +18,7 @@ def build_and_save(factory, savedirs, date, filename):
         os.makedirs(rawdir)
 
         for count, capture in enumerate(factory._images):
-            capture.save(osp.join(rawdir, "pibooth{:03}.jpg".format(count)))
+            capture.save(osp.join(rawdir, f"pibooth{count:03}.jpg"))
 
     LOGGER.info("Creating the final picture")
     factory.build()
@@ -29,7 +29,7 @@ def build_and_save(factory, savedirs, date, filename):
     return factory, picture_path
 
 
-class PicturePlugin(object):
+class PicturePlugin:
 
     """Plugin to build the final picture.
     """
diff --git a/pibooth/plugins/printer_plugin.py b/pibooth/plugins/printer_plugin.py
index 9356405d..7cedc500 100644
--- a/pibooth/plugins/printer_plugin.py
+++ b/pibooth/plugins/printer_plugin.py
@@ -6,7 +6,7 @@
 from pibooth.printer import Printer
 
 
-class PrinterPlugin(object):
+class PrinterPlugin:
 
     """Plugin to manage the printer.
     """
diff --git a/pibooth/plugins/view_plugin.py b/pibooth/plugins/view_plugin.py
index a10b45da..385b3cbb 100644
--- a/pibooth/plugins/view_plugin.py
+++ b/pibooth/plugins/view_plugin.py
@@ -6,7 +6,7 @@
 from pibooth.utils import LOGGER, get_crash_message, PollingTimer
 
 
-class ViewPlugin(object):
+class ViewPlugin:
 
     """Plugin to manage the pibooth window dans transitions.
     """
diff --git a/pibooth/printer.py b/pibooth/printer.py
index 0fa58527..b22dabfe 100644
--- a/pibooth/printer.py
+++ b/pibooth/printer.py
@@ -24,7 +24,7 @@
 }
 
 
-class Printer(object):
+class Printer:
 
     """Printer driver.
 
@@ -92,7 +92,7 @@ def print_file(self, filename):
         if not self.name:
             raise EnvironmentError("No printer found (check config file or CUPS config)")
         if not osp.isfile(filename):
-            raise IOError("No such file or directory: {}".format(filename))
+            raise IOError(f"No such file or directory: {filename}")
         if self._notifier and not self._notifier.is_subscribed(self._on_event):
             self._notifier.subscribe(self._on_event, [event.CUPS_EVT_JOB_COMPLETED,
                                                       event.CUPS_EVT_JOB_CREATED,
diff --git a/pibooth/scripts/count.py b/pibooth/scripts/count.py
index 4730ebbd..75fa9951 100644
--- a/pibooth/scripts/count.py
+++ b/pibooth/scripts/count.py
@@ -28,7 +28,7 @@ def main():
         try:
             print("\nUpdating counters (current value in square bracket):\n")
             for name in counters:
-                value = input(" -> {:.<20} [{:>4}] : ".format(name.capitalize(), counters[name]))
+                value = input(f" -> {name.capitalize():.<20} [{counters[name]:>4}] : ")
                 if value.strip():
                     setattr(counters, name, int(value))
         except KeyboardInterrupt:
@@ -37,7 +37,7 @@ def main():
     else:
         print("\nListing current counters:\n")
         for name in counters:
-            print(" -> {:.<27} : {:>4}".format(name.capitalize(), counters[name]))
+            print(f" -> {name.capitalize():.<27} : {counters[name]:>4}")
         print()
 
 
diff --git a/pibooth/scripts/diagnostic.py b/pibooth/scripts/diagnostic.py
index 69224c43..f4ce5769 100755
--- a/pibooth/scripts/diagnostic.py
+++ b/pibooth/scripts/diagnostic.py
@@ -21,7 +21,7 @@
 
 
 def gp_logging(level, domain, string, data=None):
-    write_log('Gphoto2: {}: {}'.format(domain, string))
+    write_log(f'Gphoto2: {domain}: {string}')
 
 
 def write_log(text, new_section=False):
@@ -60,20 +60,20 @@ def print_config(config, parent=''):
         if child.get_type() == gp.GP_WIDGET_SECTION:
             print_config(child, path)
         else:
-            write_log('{}'.format(path))
-            write_log('  Label       : {}'.format(child.get_label()))
-            write_log('  Readonly    : {}'.format('yes' if child.get_readonly() else 'no'))
-            write_log('  Data type   : {}'.format(type(child.get_value())))
-            write_log('  Widget type : {}'.format(gp_widget_types[child.get_type()]))
-            write_log('  Current     : {}'.format(child.get_value()))
+            write_log(f'{path}')
+            write_log(f'  Label       : {child.get_label()}')
+            write_log(f'  Readonly    : {"yes" if child.get_readonly() else "no"}')
+            write_log(f'  Data type   : {type(child.get_value())}')
+            write_log(f'  Widget type : {gp_widget_types[child.get_type()]}')
+            write_log(f'  Current     : {child.get_value()}')
             if child.get_type() == gp.GP_WIDGET_RADIO:
-                write_log('  Choices     : {}'.format([c for c in child.get_choices()]))
+                write_log(f'  Choices     : {[c for c in child.get_choices()]}')
             elif child.get_type() == gp.GP_WIDGET_RANGE:
                 write_log('  Choices     : min={}, max={}, step={}'.format(*child.get_range()))
             elif child.get_type() == gp.GP_WIDGET_TOGGLE:
                 write_log('  Choices     : [0, 1]')
             elif child.get_type() == gp.GP_WIDGET_MENU:
-                write_log('  Choices     : {}'.format([child.get_choice(n) for n in range(child.count_choices())]))
+                write_log(f'  Choices     : {[child.get_choice(n) for n in range(child.count_choices())]}')
             else:
                 write_log('  Choices     : n/a')
 
@@ -81,7 +81,7 @@ def print_config(config, parent=''):
 def set_config_value(camera, section, option, value):
     """Set camera configuration. """
     try:
-        write_log('Setting option {}/{}="{}"'.format(section, option, value))
+        write_log(f'Setting option {section}/{option}="{value}"')
         config = camera.get_config()
         child = config.get_child_by_name(section).get_child_by_name(option)
         if child.get_type() == gp.GP_WIDGET_RADIO:
@@ -90,11 +90,11 @@ def set_config_value(camera, section, option, value):
             choices = None
 
         if choices and value not in choices:
-            write_log("   -> invalid value '{}' for option {} (possible choices: {})".format(value, option, choices))
+            write_log(f"   -> invalid value '{value}' for option {option} (possible choices: {choices})")
         child.set_value(value)
         camera.set_config(config)
     except gp.GPhoto2Error:
-        write_log('   -> unsupported setting {}/{}={} (nothing configured on DSLR)'.format(section, option, value))
+        write_log(f"   -> unsupported setting {section}/{option}={value} (nothing configured on DSLR)")
 
 
 def get_config_value(camera, section, option):
@@ -104,10 +104,10 @@ def get_config_value(camera, section, option):
         config = camera.get_config()
         child = config.get_child_by_name(section).get_child_by_name(option)
         value = child.get_value()
-        write_log('Getting option {}/{}={}'.format(section, option, value))
+        write_log(f'Getting option {section}/{option}={value}')
         return value
     except gp.GPhoto2Error:
-        write_log('Unknown option {}/{}'.format(section, option))
+        write_log(f'Unknown option {section}/{option}')
 
 
 def camera_connected():
@@ -128,7 +128,7 @@ def camera_connected():
 def main():
     error = False
     configure_logging()
-    write_log("Pibooth version installed: {}".format(pibooth.__version__))
+    write_log(f"Pibooth version installed: {pibooth.__version__}")
 
     plugin_manager = create_plugin_manager()
     config = PiboothConfigParser("~/.config/pibooth/pibooth.cfg", plugin_manager)
@@ -146,9 +146,9 @@ def main():
     else:
         try:
             info = gp.version.gp_library_version(gp.version.GP_VERSION_VERBOSE)
-            write_log("GPhoto2 version installed: {}".format(info[0]))
+            write_log(f"GPhoto2 version installed: {info[0]}")
             for opt in info[1:]:
-                write_log("  - {}".format(opt))
+                write_log(f"  - {opt}")
         except Exception:
             pass
 
@@ -162,19 +162,17 @@ def main():
 
     cameras_list = sorted(cameras_list, key=lambda x: x[0])
     for index, (name, addr) in enumerate(cameras_list):
-        write_log("{:02d} : addr-> {}  name-> {}".format(index, addr, name))
+        write_log(f"{index:02d} : addr-> {addr}  name-> {name}")
 
     write_log("Stating diagnostic of connected DSLR camera", True)
     camera = gp.Camera()
     camera.init()
 
     abilities = camera.get_abilities()
-    preview_compat = gp.GP_OPERATION_CAPTURE_PREVIEW ==\
-        abilities.operations & gp.GP_OPERATION_CAPTURE_PREVIEW
-    write_log("* Preview compatible: {}".format(preview_compat))
-    capture_compat = gp.GP_OPERATION_CAPTURE_IMAGE ==\
-        abilities.operations & gp.GP_OPERATION_CAPTURE_IMAGE
-    write_log("* Capture compatible: {}".format(capture_compat))
+    preview_compat = gp.GP_OPERATION_CAPTURE_PREVIEW == abilities.operations & gp.GP_OPERATION_CAPTURE_PREVIEW
+    write_log(f"* Preview compatible: {preview_compat}")
+    capture_compat = gp.GP_OPERATION_CAPTURE_IMAGE == abilities.operations & gp.GP_OPERATION_CAPTURE_IMAGE
+    write_log(f"* Capture compatible: {capture_compat}")
 
     if capture_compat:
         try:
@@ -209,7 +207,7 @@ def main():
             image.save(APPNAME + '.jpg')
 
         except Exception as ex:
-            write_log("ABORT   : exception occures: {}".format(ex), True)
+            write_log(f"ABORT   : exception occures: {ex}", True)
             error = True
 
     if not error:
@@ -219,5 +217,5 @@ def main():
     camera.exit()
 
     write_log("If you are investigating why pibooth does not work with your DSLR camera,")
-    write_log("please paste the content of generated file '{}'".format(APPNAME + '.log'))
+    write_log(f"please paste the content of generated file '{APPNAME}.log'")
     write_log("on https://github.com/pibooth/pibooth/issues")
diff --git a/pibooth/scripts/printer.py b/pibooth/scripts/printer.py
index 28afc528..1f60c196 100644
--- a/pibooth/scripts/printer.py
+++ b/pibooth/scripts/printer.py
@@ -43,7 +43,7 @@ def main():
     groups = ppd.optionGroups
     options = []
     for group in groups:
-        group_name = "{} - {}".format(group.name, group.text)
+        group_name = f"{group.name} - {group.text}"
         for opt in group.options:
             option = {'group': group_name}
             values = list(map(lambda x: x["choice"], opt.choices))
@@ -61,13 +61,13 @@ def main():
         print(json.dumps(dict([(option['keyword'], option['value']) for option in options])))
     else:
         for option in options:
-            print("{} = {}".format(option['keyword'], option['value']))
-            print("     Description: {}".format(option['description']))
+            print(f"{option['keyword']} = {option['value']}")
+            print(f"     Description: {option['description']}")
             if isinstance(option['choices'], dict):
-                choices = ["{} = {}".format(value, descr) for value, descr in option['choices'].items()]
-                print("     Choices:     {}".format(choices[0]))
+                choices = [f"{value} = {descr}" for value, descr in option['choices'].items()]
+                print(f"     Choices:     {choices[0]}")
                 for choice in choices[1:]:
-                    print("                  {}".format(choice))
+                    print(f"                  {choice}")
             else:
                 print("     Choices:     {}".format(", ".join(option['choices'])))
 
diff --git a/pibooth/states.py b/pibooth/states.py
index 5bedf3e2..2a73f4bd 100644
--- a/pibooth/states.py
+++ b/pibooth/states.py
@@ -7,7 +7,7 @@
 from pibooth.utils import LOGGER, BlockConsoleHandler
 
 
-class StateMachine(object):
+class StateMachine:
 
     def __init__(self, plugins_manager, configuration, application, window):
         self.states = set()
diff --git a/pibooth/tasks.py b/pibooth/tasks.py
index 8e472c44..9a0318c7 100644
--- a/pibooth/tasks.py
+++ b/pibooth/tasks.py
@@ -9,7 +9,7 @@
 from pibooth import evts, utils
 
 
-class AsyncTasksPool(object):
+class AsyncTasksPool:
 
     """Class to manage a pool of asynchronous tasks.
     """
@@ -50,7 +50,7 @@ def quit(self):
         self._pool.shutdown(wait=True)
 
 
-class AsyncTask(object):
+class AsyncTask:
 
     """Class to execute an asynchronous task.
 
@@ -121,6 +121,6 @@ def kill(self):
         self._stop_event.set()
         self.future.cancel()
         try:
-            self.wait(30)  # Max 30 seconds
+            self.wait(10)  # Max 10 seconds
         except Exception as ex:
             utils.LOGGER.warning("AsyncTask.kill() failed: %s", ex)
diff --git a/pibooth/utils.py b/pibooth/utils.py
index a3650300..822c682f 100644
--- a/pibooth/utils.py
+++ b/pibooth/utils.py
@@ -9,13 +9,13 @@
 import types
 import os.path as osp
 import logging
-import psutil
 import platform
 import contextlib
 import errno
 import subprocess
 from fnmatch import fnmatchcase
 from itertools import zip_longest, islice
+import psutil
 try:
     from pip._internal.operations import freeze
 except ImportError:  # pip < 10.0
@@ -36,7 +36,7 @@ class BlockConsoleHandler(logging.StreamHandler):
     def emit(self, record):
         cls = self.__class__
         if cls.is_debug():
-            record.msg = '{}{}'.format(cls.current_indent, record.msg)
+            record.msg = f'{cls.current_indent}{record.msg}'
         logging.StreamHandler.emit(self, record)
 
         if cls.current_indent.endswith(cls.pattern_start):
@@ -68,7 +68,7 @@ def dedent(cls):
             cls.current_indent = (cls.current_indent[:-len(cls.pattern_block)] + cls.pattern_end)
 
 
-class PollingTimer(object):
+class PollingTimer:
 
     """
     Timer to be used in a pooling loop to check if timeout has been exceed.
@@ -236,7 +236,7 @@ def get_crash_message():
     msg += " * " + "Oops! It seems that pibooth has crashed".center(80) + "*\n"
     msg += " * " + "You can report an issue on https://github.com/pibooth/pibooth/issues/new".center(80) + "*\n"
     if get_logging_filename():
-        msg += " * " + ("and post the file: {}".format(get_logging_filename())).center(80) + "*\n"
+        msg += " * " + f"and post the file: {get_logging_filename()}".center(80) + "*\n"
     msg += " " + "*" * 83
     return msg
 
@@ -267,8 +267,8 @@ def pkill(pattern):
             try:
                 proc.kill()
             except psutil.AccessDenied:
-                raise EnvironmentError("Can not kill '{}', root access is required. "
-                                       "(kill it manually before starting pibooth)".format(proc.name()))
+                raise EnvironmentError(f"Can not kill '{proc.name()}', root access is required. "
+                                       "(kill it manually before starting pibooth)")
 
 
 def open_text_editor(filename):
@@ -311,7 +311,7 @@ def load_module(path):
     """Load a Python module dynamically.
     """
     if not osp.isfile(path):
-        raise ValueError("Invalid Python module path '{}'".format(path))
+        raise ValueError(f"Invalid Python module path '{path}'")
 
     dirname, filename = osp.split(path)
     modname = osp.splitext(filename)[0]
diff --git a/pibooth/view/base.py b/pibooth/view/base.py
index a596be2f..6cfc776d 100644
--- a/pibooth/view/base.py
+++ b/pibooth/view/base.py
@@ -7,7 +7,7 @@
 from pibooth.utils import LOGGER
 
 
-class BaseScene(object):
+class BaseScene:
     """Base class for scene.
 
     Methodes defined here are abstarct and shall be implemented in child classes.
@@ -64,7 +64,7 @@ def set_arrows(self, location, offset):
         raise NotImplementedError
 
 
-class BaseWindow(object):
+class BaseWindow:
 
     """Base class for window.
 
@@ -129,7 +129,7 @@ def set_scene(self, name):
         """Set the current scene.
         """
         if name not in self.scenes:
-            raise ValueError('"{}" not in registered scenes...'.format(name))
+            raise ValueError(f"'{name}' not in registered scenes...")
 
         LOGGER.debug("Set scene '%s'", name)
         self.scene = self.scenes[name]
@@ -153,7 +153,8 @@ def resize(self, size):
             self._size = size  # Size of the window when not fullscreen
 
         # Call get_rect() to take new computed size if != self._size
-        self.scene.resize(self.get_rect().size)
+        if self.scene:
+            self.scene.resize(self.get_rect().size)
 
     def get_rect(self, absolute=False):
         """Return a Rect object (as defined in pygame) for this window.
diff --git a/pibooth/view/pygame/menu.py b/pibooth/view/pygame/menu.py
index 7d0f7077..529c38cd 100644
--- a/pibooth/view/pygame/menu.py
+++ b/pibooth/view/pygame/menu.py
@@ -82,7 +82,7 @@ def _counters(counters):
     return [pattern.format(name.replace("_", " ").capitalize(), counters[name]) for name in counters]
 
 
-class PygameMenu(object):
+class PygameMenu:
 
     def __init__(self, size, application, configuration, plugin_manager, callback=None):
         self.app = application
@@ -95,7 +95,7 @@ def __init__(self, size, application, configuration, plugin_manager, callback=No
 
     def _build_menu(self):
         """Create all sub-menus"""
-        self._main_menu = pgm.Menu(title="Settings v{}".format(pibooth.__version__),
+        self._main_menu = pgm.Menu(title=f"Settings v{pibooth.__version__}",
                                    width=self.size[0],
                                    height=self.size[1],
                                    theme=THEME_DARK,
@@ -364,8 +364,8 @@ def update(self, events):
                 selected = self._main_menu.get_current().get_selected_widget()
                 if isinstance(selected, pgm.widgets.TextInput) and self.cfg.getboolean('GENERAL', 'vkeyboard'):
                     for event in events:
-                        if (event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.FINGERDOWN)\
-                                and selected.get_scrollarea().collide(selected, event):
+                        if event.type in (pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN) and\
+                                selected.get_scrollarea().collide(selected, event):
                             if isinstance(selected, pgm.widgets.ColorInput):
                                 evts.post(EVT_MENU_TEXT_EDIT, text=",".join([str(c) for c in selected.get_value()]))
                             else:
diff --git a/pibooth/view/pygame/sprites.py b/pibooth/view/pygame/sprites.py
index be919b7c..79d99ead 100644
--- a/pibooth/view/pygame/sprites.py
+++ b/pibooth/view/pygame/sprites.py
@@ -24,7 +24,7 @@ def __init__(self, sprite, color=(255, 0, 0)):
         :param color: RGB color tuple for the outlines
         :type color: tuple
         """
-        super(OutlinesSprite, self).__init__()
+        super().__init__()
         self.image = pygame.Surface((10, 10), pygame.SRCALPHA, 32)
         self.rect = sprite.rect
         self.color = color
@@ -76,7 +76,7 @@ class BaseSprite(pygame.sprite.DirtySprite):
     """
 
     def __init__(self, size=(10, 10)):
-        super(BaseSprite, self).__init__()
+        super().__init__()
         self.image = None
         self.rect = pygame.Rect((0, 0), size)
         self.color = None
@@ -152,8 +152,7 @@ def get_color(self, factor=1):
         """
         if self.color:
             return tuple(min(int(c * abs(factor)), 255) for c in self.color)
-        else:
-            return pygame.transform.average_color(self.image)
+        return pygame.transform.average_color(self.image)
 
     def set_pressed(self, state, toggle_timeout=None):
         """Set the pressed state (1 for pressed 0 for released)
@@ -202,7 +201,7 @@ def __init__(self, skin=None, size=(10, 10), colorize=True):
         :param colorize: recolorize picture if a color is set.
         :type colorize: tuple
         """
-        super(ImageSprite, self).__init__(size)
+        super().__init__(size)
         self.image_orig = None
         self.path = None
         self.crop = False
@@ -296,7 +295,7 @@ def set_angle(self, angle=0):
     def update(self, events):
         """Draw image if has changed and visible.
         """
-        super(ImageSprite, self).update(events)
+        super().update(events)
         if self.image is None and self.visible:
             if self.image_orig is None:
                 if self.path:
@@ -333,7 +332,7 @@ def __init__(self, text='', size=(10, 10), font_name=None):
         :param font_name: font name
         :type font_name: str
         """
-        super(TextSprite, self).__init__(size)
+        super().__init__(size)
         self.text = text
         self.color = (255, 255, 255)
         self.align = pictures.ALIGN_CENTER
@@ -369,7 +368,7 @@ def set_align(self, align):
     def update(self, events):
         """Draw image if has changed and visible.
         """
-        super(TextSprite, self).update(events)
+        super().update(events)
         if self.image is None and self.visible:
             self.image = pictures.text_to_pygame_image(
                 self.text, self.rect.size, self.color, self.align, font_name=self.font_name)
@@ -382,7 +381,7 @@ class ArrowSprite(ImageSprite):
     """
 
     def __init__(self, *args, **kwargs):
-        super(ArrowSprite, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.location = BaseScene.ARROW_BOTTOM
         self.offset = 0
 
@@ -403,11 +402,11 @@ class LeftArrowSprite(ArrowSprite):
     """
 
     def __init__(self, *args, **kwargs):
-        super(LeftArrowSprite, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.set_skin('arrow.png')
 
     def set_location(self, location):
-        super(LeftArrowSprite, self).set_location(location)
+        super().set_location(location)
         if location == BaseScene.ARROW_BOTTOM:
             self.set_flip(vflip=False)
             self.show()
@@ -423,10 +422,10 @@ def set_rect(self, x, y, width, height):
         """Set the sprite absolute position and size.
         """
         x -= self.offset
-        super(LeftArrowSprite, self).set_rect(x, y, width, height)
+        super().set_rect(x, y, width, height)
 
     def update(self, events):
-        super(LeftArrowSprite, self).update(events)
+        super().update(events)
         if self.visible:
             for event in events:
                 if event.type == evts.EVT_BUTTON_CAPTURE:
@@ -439,7 +438,7 @@ class RightArrowSprite(LeftArrowSprite):
     """
 
     def __init__(self, *args, **kwargs):
-        super(RightArrowSprite, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.set_flip(hflip=True)
 
     def set_rect(self, x, y, width, height):
@@ -462,7 +461,7 @@ class DotsSprite(BaseSprite):
     """
 
     def __init__(self, nbr_dots=4):
-        super(DotsSprite, self).__init__((100, 50))
+        super().__init__((100, 50))
         self.dots = []
         self.checked_orig = pictures.load_pygame_image('dot_checked.png')
         self.empty_orig = pictures.load_pygame_image('dot.png')
@@ -480,7 +479,7 @@ def set_status(self, current, total):
             self.dirty = 1
 
     def update(self, events):
-        super(DotsSprite, self).update(events)
+        super().update(events)
         if self.image is None and self.visible:
             self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32)
             border = 20
@@ -616,7 +615,6 @@ def add_sprite(self, sprite, outlines=True, layer=None):
             layer = 2
         elif layer is None and isinstance(sprite, TextSprite):
             layer = 1
-
         if layer in (0, 3, 5):
             for oldsprite in self.sprites.get_sprites_from_layer(layer):
                 if layer == 0:
diff --git a/pibooth/view/pygame/window.py b/pibooth/view/pygame/window.py
index 2c39ef3d..d9c2f36f 100644
--- a/pibooth/view/pygame/window.py
+++ b/pibooth/view/pygame/window.py
@@ -125,6 +125,7 @@ def resize(self, size):
         self.statusbar_sprite.set_rect(0, self.get_rect().height - height, width, height)
         if self.scene:
             self.scene.resize(self.get_rect().size)
+            self.scene.update([])  # Do not acts on scene, but recreate sprites with correct size
 
     def get_rect(self, absolute=False):
         """Return a Rect object (as defined in pygame) for this window.
@@ -136,8 +137,7 @@ def get_rect(self, absolute=False):
             if 'SDL_VIDEO_WINDOW_POS' in os.environ:
                 posx, posy = [int(v) for v in os.environ['SDL_VIDEO_WINDOW_POS'].split(',')]
                 return self.surface.get_rect(x=posx, y=posy)
-            else:
-                return self.surface.get_rect(center=(self.display_size[0] / 2, self.display_size[1] / 2))
+            return self.surface.get_rect(center=(self.display_size[0] / 2, self.display_size[1] / 2))
         return self.surface.get_rect()
 
     def toggle_fullscreen(self):
@@ -156,8 +156,6 @@ def toggle_fullscreen(self):
 
         size = self.get_rect().size
         self.resize(size)
-        if self.scene:
-            self.scene.update([])  # Do not acts on scenes, but recreate sprites with correct size
         if self._menu:
             self._menu.resize((min(size[0]*0.75, 600), size[1]*0.75))
 
@@ -185,7 +183,6 @@ def update(self, events):
                 # Manual resizing
                 self.surface = pygame.display.set_mode(event.size, pygame.RESIZABLE)
                 self.resize(event.size)
-                self.scene.update([])  # Do not acts on scenes, but recreate sprites with correct size
                 if self._menu:
                     self._menu.resize((min(event.size[0]*0.75, 600), event.size[1]*0.75))
 
@@ -241,13 +238,17 @@ def update(self, events):
         elif self._menu and self._menu.is_enabled():
             # Events only acts on the menu
             self._menu.update(events)
-        else:
+        if self.scene and (not self._menu or not self._menu.is_enabled()):
             self.scene.update(events)
 
     def draw(self):
         """Draw all Sprites on surface and return updated Pygame rects.
         """
-        rects = self.scene.draw(self.surface, self._force_redraw)
+        rects = []
+
+        if self.scene and (not self._menu or not self._menu.is_enabled()):
+            rects += self.scene.draw(self.surface, self._force_redraw)
+
         rects += self._keyboard.draw(self.surface, self._force_redraw)
 
         if not self._keyboard.is_enabled() and self._menu and self._menu.is_enabled():
diff --git a/setup.py b/setup.py
index a7618ea2..b247fd51 100644
--- a/setup.py
+++ b/setup.py
@@ -49,7 +49,7 @@ def main():
         ],
         author="Vincent Verdeil, Antoine Rousseaux",
         url="https://github.com/pibooth/pibooth",
-        download_url="https://github.com/pibooth/pibooth/archive/{}.tar.gz".format(pibooth.__version__),
+        download_url=f"https://github.com/pibooth/pibooth/archive/{pibooth.__version__}.tar.gz",
         license='MIT license',
         platforms=['unix', 'linux'],
         keywords=[
@@ -66,7 +66,7 @@ def main():
         include_package_data=True,
         python_requires=">=3.6",
         install_requires=[
-            'picamera2>=0.3.2 ; platform_machine>="armv0l" and platform_machine<="armv9l"',
+            'picamera2>=0.3.9 ; platform_machine>="armv0l" and platform_machine<="armv9l"',
             'Pillow>=9.2.0',
             'pygame>=2.1.2',
             'pygame-menu==4.3.9',


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
index 9be05b73..e1ac706c 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -36,7 +36,7 @@ def test_save_default(cfg):
     cfg.load(clean=True)
     assert cfg.get('GENERAL', 'autostart_delay') == '0'
     with pytest.raises(KeyError):
-        cfg.get('OTHER', 'toto') == 'is a hero'
+        cfg.get('OTHER', 'toto')
 
 
 def test_handle_autostart(cfg):