Merge branch 'better-bitrate-controls' into 'master'
Better stream bitrate controls See merge request openflexure/openflexure-microscope-server!106
This commit is contained in:
commit
e6c63e2eaf
3 changed files with 184 additions and 91 deletions
|
|
@ -5,93 +5,154 @@
|
|||
<h3>Manual camera settings</h3>
|
||||
<form @submit.prevent="applySettingsRequest">
|
||||
<div class="uk-margin-small-bottom">
|
||||
<h4>Raspberry Pi Camera</h4>
|
||||
<!--PiCamera settings block-->
|
||||
<div v-if="picamera.shutter_speed !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Exposure time</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.shutter_speed"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Pi Camera Settings</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div v-if="picamera.shutter_speed !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Exposure time</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.shutter_speed"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="picamera.analog_gain !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Analogue gain</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.analog_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="picamera.analog_gain !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Analogue gain</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.analog_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="picamera.digital_gain !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Digital gain</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.digital_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small-bottom">
|
||||
<h4>Image quality</h4>
|
||||
|
||||
<div class="uk-child-width-1-2" uk-grid>
|
||||
<div v-if="mjpeg_bitrate !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Web stream bitrate</label
|
||||
>
|
||||
<select v-model="mjpeg_bitrate" class="uk-select uk-form-small">
|
||||
<option
|
||||
v-for="option in bitrateOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="jpeg_quality !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>JPEG capture quality (%)</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="jpeg_quality"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="1"
|
||||
/>
|
||||
<div v-if="picamera.digital_gain !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Digital gain</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="picamera.digital_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<span
|
||||
v-if="
|
||||
mjpeg_bitrate !== undefined &&
|
||||
mjpeg_bitrate < 17000000 &&
|
||||
mjpeg_bitrate != -1
|
||||
"
|
||||
class="uk-text-danger uk-margin-top-small"
|
||||
>This stream bitrate may impact fast autofocus performance</span
|
||||
>
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Image Quality</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-child-width-1-2" uk-grid>
|
||||
<div v-if="jpeg_quality !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>JPEG capture quality (%)</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="jpeg_quality"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="stream_resolution !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Stream resolution</label
|
||||
>
|
||||
<select
|
||||
v-model="stream_resolution"
|
||||
class="uk-select uk-form-small"
|
||||
>
|
||||
<option
|
||||
v-for="option in resolutionOptions"
|
||||
:key="option.value[0]"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Advanced</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div v-if="mjpeg_bitrate !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Camera bitrate</label
|
||||
>
|
||||
<select
|
||||
v-model="mjpeg_bitrate"
|
||||
class="uk-select uk-form-small"
|
||||
>
|
||||
<option
|
||||
v-for="option in bitrateOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
<p class="uk-margin-remove">
|
||||
This option sets the target bitrate for camera
|
||||
recordings.<br />
|
||||
<b
|
||||
>Limiting bitrate may impact fast-autofocus
|
||||
reliability.</b
|
||||
>
|
||||
However is guaranteed to fix the live stream disappearing
|
||||
for some highly detailed samples.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="picamera.framerate !== undefined"
|
||||
class="uk-margin-top"
|
||||
>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Camera framerate</label
|
||||
>
|
||||
<select
|
||||
v-model="picamera.framerate"
|
||||
class="uk-select uk-form-small"
|
||||
>
|
||||
<option
|
||||
v-for="option in framerateOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
<p class="uk-margin-remove">
|
||||
This option sets the framerate of the Pi Camera video
|
||||
encoder. The actual stream framerate is determined by
|
||||
network speed, and is limited by the encoder framerate.<br />
|
||||
<b
|
||||
>Lowering framerate may impact fast-autofocus
|
||||
accuracy.</b
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
@ -132,16 +193,27 @@ export default {
|
|||
picamera: {
|
||||
shutter_speed: undefined,
|
||||
analog_gain: undefined,
|
||||
digital_gain: undefined
|
||||
digital_gain: undefined,
|
||||
framerate: undefined
|
||||
},
|
||||
mjpeg_bitrate: undefined,
|
||||
stream_resolution: undefined,
|
||||
jpeg_quality: undefined,
|
||||
bitrateOptions: [
|
||||
{ text: "Maximum", value: -1 },
|
||||
{ text: "High", value: 25000000 },
|
||||
{ text: "Normal", value: 17000000 },
|
||||
{ text: "Low", value: 5000000 },
|
||||
{ text: "Very low", value: 2500000 }
|
||||
{ text: "Maximum (unlimited)", value: -1 },
|
||||
{ text: "High (25Mbps)", value: 25000000 },
|
||||
{ text: "Normal (17Mbps)", value: 17000000 },
|
||||
{ text: "Low (5Mbps)", value: 5000000 },
|
||||
{ text: "Very low (2.5Mbps)", value: 2500000 }
|
||||
],
|
||||
resolutionOptions: [
|
||||
{ text: "Higher (832, 624)", value: [832, 624] },
|
||||
{ text: "Normal (640, 480)", value: [640, 480] }
|
||||
],
|
||||
framerateOptions: [
|
||||
{ text: "Normal (30fps)", value: 30 },
|
||||
{ text: "Low (15fps)", value: 15 },
|
||||
{ text: "Very low (10fps)", value: 10 }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
|
@ -165,11 +237,13 @@ export default {
|
|||
// Get base camera settings
|
||||
this.mjpeg_bitrate = cameraSettings.mjpeg_bitrate;
|
||||
this.jpeg_quality = cameraSettings.jpeg_quality;
|
||||
this.stream_resolution = cameraSettings.stream_resolution;
|
||||
// Get Pi Camera settings if they exist
|
||||
if (cameraSettings.picamera) {
|
||||
this.picamera.analog_gain = cameraSettings.picamera.analog_gain;
|
||||
this.picamera.digital_gain = cameraSettings.picamera.digital_gain;
|
||||
this.picamera.shutter_speed = cameraSettings.picamera.shutter_speed;
|
||||
this.picamera.framerate = cameraSettings.picamera.framerate;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -184,10 +258,12 @@ export default {
|
|||
camera: {
|
||||
mjpeg_bitrate: parseInt(this.mjpeg_bitrate),
|
||||
jpeg_quality: parseInt(this.jpeg_quality),
|
||||
stream_resolution: this.stream_resolution,
|
||||
picamera: {
|
||||
shutter_speed: parseFloat(this.picamera.shutter_speed),
|
||||
analog_gain: parseFloat(this.picamera.analog_gain),
|
||||
digital_gain: parseFloat(this.picamera.digital_gain)
|
||||
digital_gain: parseFloat(this.picamera.digital_gain),
|
||||
framerate: parseInt(this.picamera.framerate)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import BinaryIO, List, NamedTuple, Optional, Tuple, Type, Union
|
|||
|
||||
from labthings import ClientEvent, StrictLock
|
||||
|
||||
JPEG_END_BYTES: bytes = b"\xff\xd9"
|
||||
|
||||
# Class to store a frames metadata
|
||||
class TrackerFrame(NamedTuple):
|
||||
|
|
@ -42,6 +43,8 @@ class FrameStream(io.BytesIO):
|
|||
# We use a ClientEvent so that each thread can call getvalue() independantly
|
||||
self.new_frame: ClientEvent = ClientEvent()
|
||||
|
||||
self._bad_frame: bool = False
|
||||
|
||||
def __enter__(self):
|
||||
self.start_tracking()
|
||||
return super().__enter__()
|
||||
|
|
@ -79,6 +82,20 @@ class FrameStream(io.BytesIO):
|
|||
3. Store the new frame image
|
||||
4. Set the new_frame event
|
||||
"""
|
||||
# If we get a bad frame, and the last frame was good
|
||||
if s[-2:] != JPEG_END_BYTES and not self._bad_frame:
|
||||
# TODO: Handle this more cleverly. Automatically lower bitrate to compensate?
|
||||
# Log error
|
||||
logging.error(
|
||||
"Incomplete frame data recieved. Camera bandwidth may have been exceeded. Consider lowing resolution, framerate, or target bitrate."
|
||||
)
|
||||
# Record that last frame was bad
|
||||
self._bad_frame = True
|
||||
# If the last frame was bad, but this frame was good
|
||||
elif self._bad_frame and s[-2:] == JPEG_END_BYTES:
|
||||
# Clear the bad frame record
|
||||
self._bad_frame = False
|
||||
# If we're tracking frame size
|
||||
if self.tracking:
|
||||
frame = TrackerFrame(size=len(s), time=time.time())
|
||||
self.frames.append(frame)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
|
|||
|
||||
[tool.poetry]
|
||||
name = "openflexure-microscope-server"
|
||||
version = "2.9.1"
|
||||
version = "2.9.2"
|
||||
description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope."
|
||||
|
||||
authors = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue