Refactored MLX90640 plugin and HTTP route to work with direct BytesIO - it improves performance over using temporary files or base64-encoded responses

This commit is contained in:
Fabio Manganiello 2020-09-08 17:51:46 +02:00
parent ac02becba8
commit 53ddbad7ce
2 changed files with 59 additions and 16 deletions

View file

@ -1,11 +1,12 @@
import base64
import os
import tempfile
from flask import Response, request, Blueprint, send_from_directory
from platypush import Config
from platypush.backend.http.app import template_folder
from platypush.backend.http.app.utils import authenticate, send_request
from platypush.plugins.camera.ir.mlx90640 import CameraIrMlx90640Plugin
camera_ir_mlx90640 = Blueprint('camera.ir.mlx90640', __name__, template_folder=template_folder)
@ -15,15 +16,21 @@ __routes__ = [
]
def get_feed(**args):
try:
def get_feed(**_):
camera_conf = Config.get('camera.mlx90640') or {}
camera = CameraIrMlx90640Plugin(**camera_conf)
with camera:
while True:
frame = send_request(action='camera.ir.mlx90640.capture', **args).output[0]
frame = base64.decodebytes(frame.encode())
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
finally:
send_request(action='camera.ir.mlx90640.stop')
output = camera.get_stream()
with output.ready:
output.ready.wait()
frame = output.frame
if frame and len(frame):
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
@camera_ir_mlx90640.route('/camera/ir/mlx90640/frame', methods=['GET'])

View file

@ -2,12 +2,14 @@ import base64
import io
import os
import subprocess
import threading
import time
from platypush.plugins import Plugin, action
from platypush.plugins import action
from platypush.plugins.camera import CameraPlugin, StreamingOutput
class CameraIrMlx90640Plugin(Plugin):
class CameraIrMlx90640Plugin(CameraPlugin):
"""
Plugin to interact with a `ML90640 <https://shop.pimoroni.com/products/mlx90640-thermal-camera-breakout>`_
infrared thermal camera.
@ -35,13 +37,15 @@ class CameraIrMlx90640Plugin(Plugin):
_img_size = (32, 24)
_rotate_values = {}
def __init__(self, fps=16, skip_frames=2, scale_factor=1, rotate=0, rawrgb_path=None, **kwargs):
def __init__(self, fps=16, skip_frames=2, scale_factor=1, rotate=0, grayscale=False, rawrgb_path=None, **kwargs):
"""
:param fps: Frames per seconds (default: 16)
:param skip_frames: Number of frames to be skipped on sensor initialization/warmup (default: 2)
:param scale_factor: The camera outputs 24x32 pixels artifacts. Use scale_factor to scale them up to a larger
image (default: 1)
:param rotate: Rotation angle in degrees (default: 0)
:param grayscale: Save the image as grayscale - black pixels will be colder, white pixels warmer
(default: False = use false colors)
:param rawrgb_path: Specify it if the rawrgb executable compiled from
https://github.com/pimoroni/mlx90640-library is in another folder than
`<directory of this file>/lib/examples`.
@ -68,6 +72,7 @@ class CameraIrMlx90640Plugin(Plugin):
self.skip_frames = skip_frames
self.scale_factor = scale_factor
self.rawrgb_path = rawrgb_path
self.grayscale = grayscale
self._capture_proc = None
def _is_capture_proc_running(self):
@ -81,9 +86,40 @@ class CameraIrMlx90640Plugin(Plugin):
return self._capture_proc
def _raw_capture(self):
from PIL import Image
camera = self._get_capture_proc(fps=self.fps)
size = self._img_size
while self._is_capture_proc_running():
frame = camera.stdout.read(size[0] * size[1] * 3)
image = Image.frombytes('RGB', size, frame)
self._output.write(frame)
if self.grayscale:
image = self._convert_to_grayscale(image)
if self.scale_factor != 1:
size = tuple(i * self.scale_factor for i in size)
image = image.resize(size, Image.ANTIALIAS)
if self.rotate:
image = image.transpose(self.rotate)
temp = io.BytesIO()
image.save(temp, format='jpg')
self._output.write(temp.getvalue())
def __enter__(self):
self._output = StreamingOutput(raw=False)
self._capturing_thread = threading.Thread(target=self._raw_capture)
self._capturing_thread.start()
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
# noinspection PyShadowingBuiltins
@action
def capture(self, output_file=None, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None,
def capture(self, output_file=None, frames=1, grayscale=None, fps=None, skip_frames=None, scale_factor=None,
rotate=None, format='jpeg'):
"""
Capture one or multiple frames and return them as raw RGB
@ -97,8 +133,7 @@ class CameraIrMlx90640Plugin(Plugin):
`stop` is called.
:type frames: int
:param grayscale: Save the image as grayscale - black pixels will be colder, white pixels warmer
(default: False)
:param grayscale: Override the default ``grayscale`` parameter.
:type grayscale: bool
:param fps: If set it overrides the fps parameter specified on the object (default: None)
@ -126,9 +161,10 @@ class CameraIrMlx90640Plugin(Plugin):
skip_frames = self.skip_frames if skip_frames is None else skip_frames
scale_factor = self.scale_factor if scale_factor is None else scale_factor
rotate = self._rotate_values.get(self.rotate if rotate is None else rotate, 0)
grayscale = self.grayscale if grayscale is None else grayscale
size = self._img_size
sleep_time = 1.0 / self.fps
sleep_time = 1.0 / fps
captured_frames = []
n_captured_frames = 0
files = set()