[libcamera-devel,v3,20/30] py: cam: Drop PIL dependency
diff mbox series

Message ID 20220527144447.94891-21-tomi.valkeinen@ideasonboard.com
State Accepted
Headers show
Series
  • More misc Python patches
Related show

Commit Message

Tomi Valkeinen May 27, 2022, 2:44 p.m. UTC
We can use Qt directly to accomplish the same as we do with PIL.

A minor downside is that loading MJPEG frame with Qt produces a "Corrupt
JPEG data" warning. The resulting picture looks fine, though. So add a
message handler to ignore that warning.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 src/py/cam/cam_qt.py | 30 ++++++++++++++++++++++--------
 1 file changed, 22 insertions(+), 8 deletions(-)

Comments

Laurent Pinchart May 30, 2022, 9:38 a.m. UTC | #1
Hi Tomi,

Thank you for the patch.

On Fri, May 27, 2022 at 05:44:37PM +0300, Tomi Valkeinen wrote:
> We can use Qt directly to accomplish the same as we do with PIL.
> 
> A minor downside is that loading MJPEG frame with Qt produces a "Corrupt
> JPEG data" warning. The resulting picture looks fine, though. So add a
> message handler to ignore that warning.

What's the exact message being printed ?

> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  src/py/cam/cam_qt.py | 30 ++++++++++++++++++++++--------
>  1 file changed, 22 insertions(+), 8 deletions(-)
> 
> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
> index af4b0f86..b6412bdf 100644
> --- a/src/py/cam/cam_qt.py
> +++ b/src/py/cam/cam_qt.py
> @@ -2,17 +2,32 @@
>  # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>  
>  from helpers import mfb_to_rgb
> -from io import BytesIO
> -from PIL import Image
> -from PIL.ImageQt import ImageQt
>  from PyQt5 import QtCore, QtGui, QtWidgets
>  import libcamera as libcam
>  import libcamera.utils
>  import sys
>  
> +
> +# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these.
> +def qt_message_handler(msg_type, msg_log_context, msg_string):
> +    if msg_string.startswith("Corrupt JPEG data"):
> +        return
> +
> +    # For some reason qInstallMessageHandler returns None, so we won't
> +    # call the old handler

Have you seen that happening ? If that's the case, do we really need to
call print(), or is it a sign that there should be no logging ?

> +    if old_msg_handler is not None:
> +        old_msg_handler(msg_type, msg_log_context, msg_string)
> +    else:
> +        print(msg_string)
> +
> +
> +old_msg_handler = QtCore.qInstallMessageHandler(qt_message_handler)

Quite a bit of a hack, but I don't really see another viable option.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +
> +
>  def rgb_to_pix(rgb):
> -    img = Image.frombuffer('RGB', (rgb.shape[1], rgb.shape[0]), rgb)
> -    qim = ImageQt(img).copy()
> +    w = rgb.shape[1]
> +    h = rgb.shape[0]
> +    qim = QtGui.QImage(rgb, w, h, QtGui.QImage.Format.Format_RGB888)
>      pix = QtGui.QPixmap.fromImage(qim)
>      return pix
>  
> @@ -135,9 +150,8 @@ class MainWindow(QtWidgets.QWidget):
>              cfg = stream.configuration
>  
>              if cfg.pixel_format == libcam.formats.MJPEG:
> -                img = Image.open(BytesIO(mfb.planes[0]))
> -                qim = ImageQt(img).copy()
> -                pix = QtGui.QPixmap.fromImage(qim)
> +                pix = QtGui.QPixmap(cfg.size.width, cfg.size.height)
> +                pix.loadFromData(mfb.planes[0])
>              else:
>                  rgb = mfb_to_rgb(mfb, cfg)
>                  if rgb is None:
Tomi Valkeinen May 30, 2022, 9:45 a.m. UTC | #2
On 30/05/2022 12:38, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Fri, May 27, 2022 at 05:44:37PM +0300, Tomi Valkeinen wrote:
>> We can use Qt directly to accomplish the same as we do with PIL.
>>
>> A minor downside is that loading MJPEG frame with Qt produces a "Corrupt
>> JPEG data" warning. The resulting picture looks fine, though. So add a
>> message handler to ignore that warning.
> 
> What's the exact message being printed ?

It varies a bit, but I get a single print like these per frame:

Corrupt JPEG data: 12 extraneous bytes before marker 0xd5
Corrupt JPEG data: 2 extraneous bytes before marker 0xd4
Corrupt JPEG data: 3 extraneous bytes before marker 0xd3
Corrupt JPEG data: 1 extraneous bytes before marker 0xd5
Corrupt JPEG data: 1 extraneous bytes before marker 0xd2

Possibly MJPEG contains something extra and the jpeg decorer Qt uses 
doesn't cope with it? It's possible that PIL encounters the same issue 
but is just silent about it.

>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   src/py/cam/cam_qt.py | 30 ++++++++++++++++++++++--------
>>   1 file changed, 22 insertions(+), 8 deletions(-)
>>
>> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
>> index af4b0f86..b6412bdf 100644
>> --- a/src/py/cam/cam_qt.py
>> +++ b/src/py/cam/cam_qt.py
>> @@ -2,17 +2,32 @@
>>   # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>   
>>   from helpers import mfb_to_rgb
>> -from io import BytesIO
>> -from PIL import Image
>> -from PIL.ImageQt import ImageQt
>>   from PyQt5 import QtCore, QtGui, QtWidgets
>>   import libcamera as libcam
>>   import libcamera.utils
>>   import sys
>>   
>> +
>> +# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these.
>> +def qt_message_handler(msg_type, msg_log_context, msg_string):
>> +    if msg_string.startswith("Corrupt JPEG data"):
>> +        return
>> +
>> +    # For some reason qInstallMessageHandler returns None, so we won't
>> +    # call the old handler
> 
> Have you seen that happening ? If that's the case, do we really need to
> call print(), or is it a sign that there should be no logging ?

Yes, on my machine qInstallMessageHandler returns None. I don't know 
why. Afaics it's a bug somewhere in Qt, but I didn't spend much time 
debugging this.

  Tomi

Patch
diff mbox series

diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
index af4b0f86..b6412bdf 100644
--- a/src/py/cam/cam_qt.py
+++ b/src/py/cam/cam_qt.py
@@ -2,17 +2,32 @@ 
 # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
 
 from helpers import mfb_to_rgb
-from io import BytesIO
-from PIL import Image
-from PIL.ImageQt import ImageQt
 from PyQt5 import QtCore, QtGui, QtWidgets
 import libcamera as libcam
 import libcamera.utils
 import sys
 
+
+# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these.
+def qt_message_handler(msg_type, msg_log_context, msg_string):
+    if msg_string.startswith("Corrupt JPEG data"):
+        return
+
+    # For some reason qInstallMessageHandler returns None, so we won't
+    # call the old handler
+    if old_msg_handler is not None:
+        old_msg_handler(msg_type, msg_log_context, msg_string)
+    else:
+        print(msg_string)
+
+
+old_msg_handler = QtCore.qInstallMessageHandler(qt_message_handler)
+
+
 def rgb_to_pix(rgb):
-    img = Image.frombuffer('RGB', (rgb.shape[1], rgb.shape[0]), rgb)
-    qim = ImageQt(img).copy()
+    w = rgb.shape[1]
+    h = rgb.shape[0]
+    qim = QtGui.QImage(rgb, w, h, QtGui.QImage.Format.Format_RGB888)
     pix = QtGui.QPixmap.fromImage(qim)
     return pix
 
@@ -135,9 +150,8 @@  class MainWindow(QtWidgets.QWidget):
             cfg = stream.configuration
 
             if cfg.pixel_format == libcam.formats.MJPEG:
-                img = Image.open(BytesIO(mfb.planes[0]))
-                qim = ImageQt(img).copy()
-                pix = QtGui.QPixmap.fromImage(qim)
+                pix = QtGui.QPixmap(cfg.size.width, cfg.size.height)
+                pix.loadFromData(mfb.planes[0])
             else:
                 rgb = mfb_to_rgb(mfb, cfg)
                 if rgb is None: