Merge branch 'lst-image' into 'master'

Added interface to get LST as a PNG image

See merge request openflexure/openflexure-microscope-server!87
This commit is contained in:
Joel Collins 2020-11-18 15:37:26 +00:00
commit bdd6d27abf
13 changed files with 198 additions and 100 deletions

View file

@ -29,10 +29,10 @@ from labthings import create_app
from labthings.extensions import find_extensions
from labthings.views import View
from openflexure_microscope.microscope import Microscope
from openflexure_microscope.api.utilities import init_default_extensions, list_routes
from openflexure_microscope.api.v2 import views
from openflexure_microscope.json import JSONEncoder
from openflexure_microscope.microscope import Microscope
from openflexure_microscope.paths import (
OPENFLEXURE_EXTENSIONS_PATH,
OPENFLEXURE_VAR_PATH,
@ -135,6 +135,9 @@ labthing.add_root_link(views.ConfigurationProperty, "instrumentConfiguration")
# Attach stage resources
labthing.add_view(views.StageTypeProperty, "/instrument/stage/type")
# Attach camera resources
labthing.add_view(views.LSTImageProperty, "/instrument/camera/lst")
# Attach streams resources
labthing.add_view(views.MjpegStream, "/streams/mjpeg")
labthing.add_view(views.SnapshotStream, "/streams/snapshot")

View file

@ -1,6 +1,18 @@
<template>
<div id="CSMSettings">
<CSMCalibrationSettings />
<div
id="CSMSettings"
class="uk-grid uk-grid-divider uk-child-width-expand"
uk-grid
>
<div class="uk-width-large">
<h3>Camera/stage mapping</h3>
<p>
Camera/stage mapping allows the stage to move relative to the camera
view. This enables functions like click-to-move, and more precise tile
scans.
</p>
<CSMCalibrationSettings />
</div>
<div id="mini-stream">
<miniStreamDisplay />
</div>
@ -147,7 +159,8 @@ export default {
margin-right: auto;
}
#mini-stream {
width: 500px;
min-width: 300px;
max-width: 600px;
text-align: center;
margin-left: auto;
margin-right: auto;

View file

@ -167,11 +167,4 @@ export default {
margin-left: auto;
margin-right: auto;
}
#mini-stream {
width: 500px;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
}
</style>

View file

@ -1,5 +1,5 @@
<template>
<div id="appSettings">
<div id="appSettings" class="uk-width-large">
<h3>Appearance</h3>
<p>
<label>

View file

@ -1,67 +1,68 @@
<template>
<div v-if="settings" id="cameraSettings">
<div>
<h3>Manual camera settings</h3>
<form @submit.prevent="applyConfigRequest">
<div v-if="settings.picamera">
<!--PiCamera settings block-->
<div v-if="settings.picamera.shutter_speed !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Exposure time</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.shutter_speed"
class="uk-input uk-form-small"
type="number"
/>
<div class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
<div class="uk-width-large">
<h3>Manual camera settings</h3>
<form @submit.prevent="applyConfigRequest">
<div v-if="settings.picamera">
<!--PiCamera settings block-->
<div v-if="settings.picamera.shutter_speed !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Exposure time</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.shutter_speed"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div v-if="settings.picamera.analog_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Analogue gain</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.analog_gain"
class="uk-input uk-form-small"
type="number"
step="0.000001"
/>
</div>
</div>
<div v-if="settings.picamera.digital_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Digital gain</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.digital_gain"
class="uk-input uk-form-small"
type="number"
step="0.000001"
/>
</div>
</div>
</div>
<div v-if="settings.picamera.analog_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Analogue gain</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.analog_gain"
class="uk-input uk-form-small"
type="number"
step="0.000001"
/>
</div>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</form>
<div v-if="settings.picamera.digital_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Digital gain</label
>
<div class="uk-form-controls">
<input
v-model="settings.picamera.digital_gain"
class="uk-input uk-form-small"
type="number"
step="0.000001"
/>
</div>
</div>
</div>
<h3>Automatic calibration</h3>
<cameraCalibrationSettings></cameraCalibrationSettings>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</form>
</div>
<!--Show auto calibrate if default plugin is enabled-->
<h3>Automatic calibration</h3>
<cameraCalibrationSettings></cameraCalibrationSettings>
<div id="mini-stream">
<miniStreamDisplay />
<div id="mini-stream">
<miniStreamDisplay />
</div>
</div>
</div>
</template>
@ -141,7 +142,8 @@ export default {
<style lang="less">
#mini-stream {
width: 500px;
min-width: 300px;
max-width: 600px;
text-align: center;
margin-left: auto;
margin-right: auto;

View file

@ -16,24 +16,31 @@
</taskSubmitter>
</div>
<div v-show="showExtraSettings" class="uk-child-width-expand" uk-grid>
<div v-if="'flatten_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="flattenLensShadingTableRequest"
>
Disable flat-field correction
</button>
</div>
<div v-show="showExtraSettings" class="uk-child-width-expand">
<button
v-if="'flatten_lens_shading_table' in recalibrationLinks"
class="uk-button uk-button-danger uk-width-1-1"
@click="flattenLensShadingTableRequest"
>
Disable flat-field correction
</button>
<div v-if="'delete_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="deleteLensShadingTableRequest"
>
Adaptive flat-field correction
</button>
</div>
<button
v-if="'delete_lens_shading_table' in recalibrationLinks"
class="uk-button uk-button-danger uk-margin-small-top uk-width-1-1"
@click="deleteLensShadingTableRequest"
>
Adaptive flat-field correction
</button>
</div>
<div v-if="LstDownloadEnabled">
<a
class="uk-button uk-button-default uk-width-large uk-margin-small-top uk-align-center"
:href="LstDownloadUri"
download
>Download Lens-Shading Table</a
>
</div>
</div>
</template>
@ -61,18 +68,23 @@ export default {
data: function() {
return {
recalibrationLinks: {},
isCalibrating: false
isCalibrating: false,
LstDownloadEnabled: false
};
},
computed: {
pluginsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/extensions`;
},
LstDownloadUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/camera/lst`;
}
},
mounted() {
this.updateRecalibrationLinks();
this.checkLstDownload();
},
methods: {
@ -97,6 +109,21 @@ export default {
});
},
checkLstDownload: function() {
axios
.get(this.LstDownloadUri) // Get a list of plugins
.then(response => {
if (response.status === 200) {
this.LstDownloadEnabled = true;
} else {
this.LstDownloadEnabled = false;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished recalibration.");
},

View file

@ -1,5 +1,5 @@
<template>
<div id="appSettings">
<div id="appSettings" class="uk-width-large">
<h3>Additional features</h3>
<p class="uk-margin-small">
<label
@ -7,7 +7,7 @@
Enable IHI Slide Scan</label
>
</p>
<p class="uk-margin-small uk-width-xlarge">
<p class="uk-margin-small">
Enabling IHI Slide Scan will add a new tab designed to simplify acquiring
blood smear tile scans. <br />
While this may be useful for other applications, it is primarily designed

View file

@ -1,5 +1,5 @@
<template>
<div v-if="settings" id="microscopeSettings">
<div v-if="settings" id="microscopeSettings" class="uk-width-large">
<form @submit.prevent="applyConfigRequest">
<label class="uk-form-label" for="form-stacked-text"
>Backlash compensation</label

View file

@ -1,5 +1,5 @@
<template>
<div id="stageSettings">
<div id="stageSettings" class="uk-width-large">
<div>
<h3>Stage settings</h3>
<p>

View file

@ -1,11 +1,14 @@
<template>
<div id="streamSettings">
<div id="streamSettings" class="uk-width-large">
<div>
<h3>Stream settings</h3>
<label
><input v-model="disableStream" class="uk-checkbox" type="checkbox" />
Disable live stream</label
Disable web stream</label
>
<p class="uk-margin-small">
This will disable the embedded web stream of the camera.
</p>
</div>
<br />
@ -30,6 +33,18 @@
>
</div>
</div>
<p>
Enable GPU preview turns on a low-latency camera preview drawn directly
to the Raspberry Pi display output.
</p>
<p class="uk-margin-small">
<b
>Content, such as your mouse, won't be visible behind this preview.</b
>
Track Window will attempt to resize the GPU preview based on the current
window size, however this is often imperfect and cannot track window
movement.
</p>
</div>
</div>
</template>

View file

@ -133,12 +133,6 @@
:current-tab="currentTab"
>
<div class="settings-pane uk-padding-small">
<h3>Camera/stage mapping</h3>
<p>
Camera/stage mapping allows the stage to move relative to the camera
view. This enables functions like click-to-move, and more precise
tile scans.
</p>
<CSMSettings />
</div>
</tabContent>

View file

@ -1,4 +1,5 @@
from .actions import enabled_root_actions
from .camera import *
from .captures import *
from .instrument import *
from .stage import *

View file

@ -0,0 +1,50 @@
import logging
from io import BytesIO
import datetime
import numpy as np
from flask import send_file
from labthings import find_component
from labthings.views import PropertyView
from PIL import Image
class LSTImageProperty(PropertyView):
"""Gets the lens-shading table as an image"""
responses = {
200: {
"content_type": "image/png",
"description": "Lens-shading table in RGB format",
}
}
def get(self):
"""
Get the stage geometry.
"""
microscope = find_component("org.openflexure.microscope")
# Return intentionally empty response if no LST is available
if microscope.get_configuration()["camera"]["type"] != "PiCameraStreamer":
logging.warning("Requested an LST from a non-PiCameraStreamer camera")
return ("", 204)
# Return intentionally empty response if we have a valid PiCamera but no LST
elif getattr(microscope.camera.camera, "lens_shading_table") is None:
logging.warning("PiCamera.lens_shading_table returned as None")
return ("", 204)
else:
lst = np.asarray(microscope.camera.camera.lens_shading_table)
# R next to G1
top = np.concatenate(lst[:2], axis=1)
# G2 next to B
bottom = np.concatenate(lst[2:], axis=1)
# Combined into a 2x2 grid of greyscale images
all_channels = np.concatenate((top, bottom), axis=0)
# Create a PNG
img_bytes = BytesIO()
Image.fromarray(np.uint8(all_channels), "L").save(img_bytes, "PNG")
img_bytes.seek(0)
# Return the image
#return Response(img_bytes.getvalue(), mimetype="image/png")
fname = f"lst-{datetime.date.today().isoformat()}.png"
return send_file(img_bytes, as_attachment=True, attachment_filename=fname)