Finalized camera.ir.mlx90640 web interface

This commit is contained in:
Fabio Manganiello 2019-09-26 18:33:44 +02:00
parent d7dc74beed
commit 168b1b0e5a
6 changed files with 109 additions and 28 deletions

View file

@ -0,0 +1,60 @@
import base64
import os
import tempfile
import time
from flask import Response, request, Blueprint, send_from_directory
from platypush.backend.http.app import template_folder
from platypush.backend.http.app.utils import authenticate, send_request
camera_ir_mlx90640 = Blueprint('camera.ir.mlx90640', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
camera_ir_mlx90640,
]
def get_feed(**args):
try:
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')
@camera_ir_mlx90640.route('/camera/ir/mlx90640/frame', methods=['GET'])
@authenticate()
def get_frame_route():
f = tempfile.NamedTemporaryFile(prefix='ir_camera_frame_', suffix='.jpg', delete=False)
args = {
'grayscale': bool(int(request.args.get('grayscale', 0))),
'scale_factor': int(request.args.get('scale_factor', 1)),
'rotate': int(request.args.get('rotate', 0)),
'output_file': f.name,
}
send_request(action='camera.ir.mlx90640.capture', **args)
return send_from_directory(os.path.dirname(f.name),
os.path.basename(f.name))
@camera_ir_mlx90640.route('/camera/ir/mlx90640/stream', methods=['GET'])
@authenticate()
def get_feed_route():
args = {
'grayscale': bool(int(request.args.get('grayscale', 0))),
'scale_factor': int(request.args.get('scale_factor', 1)),
'rotate': int(request.args.get('rotate', 0)),
'format': 'jpeg',
}
return Response(get_feed(**args),
mimetype='multipart/x-mixed-replace; boundary=frame')
# vim:sw=4:ts=4:et:

View file

@ -1,9 +1,9 @@
@import 'common/vars'; @import 'common/vars';
.camera { .camera {
height: 90%; min-height: 90%;
margin-top: 7%; margin-top: 4%;
overflow: hidden; overflow: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View file

@ -17,22 +17,43 @@ Vue.component('camera-ir-mlx90640', {
return; return;
this.capturing = true; this.capturing = true;
this.$refs.frame.setAttribute('src', '/camera/ir/mlx90640/stream?rotate='
while (this.capturing) { + this.rotate + '&grayscale=' + (this.grayscale ? 1 : 0) + '&t='
const img = await request('camera.ir.mlx90640.capture', { + (new Date()).getTime());
format: 'png',
rotate: this.rotate,
grayscale: this.grayscale,
});
this.$refs.frame.setAttribute('src', 'data:image/png;base64,' + img);
}
}, },
stopStreaming: async function() { stopStreaming: async function() {
await request('camera.ir.mlx90640.stop'); await request('camera.ir.mlx90640.stop');
this.$refs.frame.removeAttribute('src');
this.capturing = false; this.capturing = false;
}, },
onRotationChange: function() {
this.rotate = parseInt(this.$refs.rotate.value);
const cameraContainer = this.$el.querySelector('.camera-container');
switch (this.rotate) {
case 0:
case 180:
cameraContainer.style.width = '640px';
cameraContainer.style.minWidth = '640px';
cameraContainer.style.height = '480px';
cameraContainer.style.minHeight = '480px';
break;
case 90:
case 270:
cameraContainer.style.width = '480px';
cameraContainer.style.minWidth = '480px';
cameraContainer.style.height = '640px';
cameraContainer.style.minHeight = '640px';
break;
}
},
},
mounted: function() {
this.onRotationChange();
}, },
}); });

View file

@ -14,7 +14,7 @@
<i class="fa fa-stop"></i>&nbsp; Stop streaming <i class="fa fa-stop"></i>&nbsp; Stop streaming
</button> </button>
<select ref="rotate" @change="rotate = $event.target.value" :disabled="capturing"> <select ref="rotate" @change="onRotationChange" :disabled="capturing">
<option value="0" :selected="rotate == 0">0 degrees</option> <option value="0" :selected="rotate == 0">0 degrees</option>
<option value="90" :selected="rotate == 90">90 degrees</option> <option value="90" :selected="rotate == 90">90 degrees</option>
<option value="180" :selected="rotate == 180">180 degrees</option> <option value="180" :selected="rotate == 180">180 degrees</option>

View file

@ -36,6 +36,11 @@ class CameraIrMlx90640Plugin(Plugin):
""" """
_img_size = (32, 24) _img_size = (32, 24)
_rotate_values = {
90: Image.ROTATE_90,
180: Image.ROTATE_180,
270: Image.ROTATE_270,
}
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, rawrgb_path=None, **kwargs):
""" """
@ -74,7 +79,7 @@ class CameraIrMlx90640Plugin(Plugin):
return self._capture_proc return self._capture_proc
@action @action
def capture(self, output_file=None, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None, rotate=None, format=None): def capture(self, output_file=None, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None, rotate=None, format='jpeg'):
""" """
Capture one or multiple frames and return them as raw RGB Capture one or multiple frames and return them as raw RGB
@ -100,18 +105,18 @@ class CameraIrMlx90640Plugin(Plugin):
:param rotate: If set it overrides the rotate parameter specified on the object (default: None) :param rotate: If set it overrides the rotate parameter specified on the object (default: None)
:type rotate: int :type rotate: int
:param format: Output image format if output_file is not specified(default: None, raw RGB). :param format: Output image format if output_file is not specified (default: jpeg).
It can be jpg, png, gif or any format supported by PIL It can be jpg, png, gif or any format supported by PIL
:type format: str :type format: str
:returns: list[str]. Each item is a base64 encoded raw RGB representation of a frame if output_file is not set, otherwise a list with :returns: list[str]. Each item is a base64 encoded representation of a frame in the specified format if output_file is not set, otherwise a list with
the captured image files will be returned. the captured image files will be returned.
""" """
fps = self.fps if fps is None else fps fps = self.fps if fps is None else fps
skip_frames = self.skip_frames if skip_frames is None else skip_frames 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 scale_factor = self.scale_factor if scale_factor is None else scale_factor
rotate = self.rotate if rotate is None else rotate rotate = self._rotate_values.get(self.rotate if rotate is None else rotate, 0)
size = self._img_size size = self._img_size
sleep_time = 1.0 / self.fps sleep_time = 1.0 / self.fps
@ -132,21 +137,16 @@ class CameraIrMlx90640Plugin(Plugin):
if grayscale: if grayscale:
image = self._convert_to_grayscale(image) image = self._convert_to_grayscale(image)
if rotate:
image = image.rotate(rotate)
if scale_factor != 1: if scale_factor != 1:
size = tuple(i*scale_factor for i in size) size = tuple(i*scale_factor for i in size)
image = image.resize(size, Image.ANTIALIAS) image = image.resize(size, Image.ANTIALIAS)
if rotate:
frame = image.getdata() image = image.transpose(rotate)
if not output_file: if not output_file:
if format:
temp = io.BytesIO() temp = io.BytesIO()
image.save(temp, format=format) image.save(temp, format=format)
frame = temp.getvalue() frame = base64.encodebytes(temp.getvalue()).decode()
frame = base64.encodebytes(frame).decode()
captured_frames.append(frame) captured_frames.append(frame)
else: else:
image_file = os.path.abspath(os.path.expanduser(output_file.format(n_captured_frames))) image_file = os.path.abspath(os.path.expanduser(output_file.format(n_captured_frames)))