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:
commit
bdd6d27abf
13 changed files with 198 additions and 100 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div id="appSettings">
|
||||
<div id="appSettings" class="uk-width-large">
|
||||
<h3>Appearance</h3>
|
||||
<p>
|
||||
<label>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div id="stageSettings">
|
||||
<div id="stageSettings" class="uk-width-large">
|
||||
<div>
|
||||
<h3>Stage settings</h3>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from .actions import enabled_root_actions
|
||||
from .camera import *
|
||||
from .captures import *
|
||||
from .instrument import *
|
||||
from .stage import *
|
||||
|
|
|
|||
50
openflexure_microscope/api/v2/views/camera.py
Normal file
50
openflexure_microscope/api/v2/views/camera.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue