Drafted integrated slide scan
This commit is contained in:
parent
2ce58f2aee
commit
de7f4f6dfe
5 changed files with 362 additions and 8 deletions
|
|
@ -10,7 +10,7 @@
|
|||
"license": "GNU General Public License v3.0 ",
|
||||
"scripts": {
|
||||
"build": "vue-cli-service build --mode production",
|
||||
"serve": "vue-cli-service serve --mode development"
|
||||
"serve": "vue-cli-service serve --mode development"
|
||||
},
|
||||
"dependencies": {
|
||||
"material-design-icons": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@
|
|||
>
|
||||
<i class="material-icons">camera_alt</i>
|
||||
</tabIcon>
|
||||
<tabIcon
|
||||
id="slidescan-tab-icon"
|
||||
tab-i-d="slidescan"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
<i class="material-icons">camera_alt</i>
|
||||
</tabIcon>
|
||||
<tabIcon
|
||||
id="settings-tab-icon"
|
||||
tab-i-d="settings"
|
||||
|
|
@ -125,6 +134,13 @@
|
|||
>
|
||||
<captureContent />
|
||||
</tabContent>
|
||||
<tabContent
|
||||
tab-i-d="slidescan"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<slideScanContent />
|
||||
</tabContent>
|
||||
<tabContent
|
||||
tab-i-d="settings"
|
||||
:require-connection="false"
|
||||
|
|
@ -170,6 +186,7 @@ import tabContent from "./genericComponents/tabContent";
|
|||
// Import new content components
|
||||
import navigateContent from "./tabContentComponents/navigateContent.vue";
|
||||
import captureContent from "./tabContentComponents/captureContent.vue";
|
||||
import slideScanContent from "./tabContentComponents/slideScanContent.vue";
|
||||
import viewContent from "./tabContentComponents/viewContent.vue";
|
||||
import settingsContent from "./tabContentComponents/settingsContent.vue";
|
||||
import galleryContent from "./tabContentComponents/galleryContent.vue";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,312 @@
|
|||
<template>
|
||||
<div class="medscan">
|
||||
<div v-show="!scanUri">No scan extension found</div>
|
||||
<div v-show="scanUri">
|
||||
<h1>Sample Scan Wizard</h1>
|
||||
|
||||
<form v-on:submit.prevent v-on:keyup.enter="increment()">
|
||||
<div id="step-user" v-show="stepValue==0">
|
||||
<h2>User information (1/4)</h2>
|
||||
|
||||
<label for="username">Your name:</label>
|
||||
<input type="text" id="username" name="username" v-model="username" required />
|
||||
<br />
|
||||
<label for="currentTime">Current time (check this is correct):</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="currentTime"
|
||||
name="currentTime"
|
||||
v-model="currentTimeForm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step-patient" v-show="stepValue==1">
|
||||
<h2>Patient information (2/4)</h2>
|
||||
|
||||
<label for="patientID">Patient ID:</label>
|
||||
<input type="text" id="patientID" name="patientID" v-model="patientID" required />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<div id="step-sample" v-show="stepValue==2">
|
||||
<h2>Sample information (3/4)</h2>
|
||||
|
||||
<label>Sample type:</label>
|
||||
<br />
|
||||
<input type="radio" id="typeThin" value="thin" v-model="sampleType" />
|
||||
<label for="typeThin">Thin smear</label>
|
||||
<br />
|
||||
<input type="radio" id="typeThick" value="thick" v-model="sampleType" />
|
||||
<label for="typeThick">Thick smear</label>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<div id="step-scan" v-show="stepValue==3">
|
||||
<h2>Start scan (4/4)</h2>
|
||||
|
||||
<label>Check details:</label>
|
||||
<p>
|
||||
<b>Your name:</b>
|
||||
{{ username }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Patient ID:</b>
|
||||
{{ patientID }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Sample type:</b>
|
||||
{{ sampleType }}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="form-stepper">
|
||||
<p class="warning">{{ message }}</p>
|
||||
<taskSubmitter
|
||||
v-if="scanUri"
|
||||
v-show="stepValue == 3"
|
||||
:base-url="baseURL"
|
||||
:submit-url="scanUri"
|
||||
:submit-data="payload"
|
||||
submit-label="Start scan"
|
||||
@submit="scanRunning=true"
|
||||
@response="scanRunning=false"
|
||||
@error="scanRunning=false"
|
||||
></taskSubmitter>
|
||||
<br />
|
||||
|
||||
<div class="grid-container">
|
||||
<button
|
||||
:disabled="stepValue <= 0 || scanRunning"
|
||||
type="button"
|
||||
v-on:click="decrement()"
|
||||
>Previous</button>
|
||||
<button
|
||||
:disabled="stepValue >= 3 || scanRunning"
|
||||
type="button"
|
||||
v-on:click="increment()"
|
||||
>Next</button>
|
||||
</div>
|
||||
<p>
|
||||
<button
|
||||
v-show="stepValue == 3"
|
||||
:disabled="scanRunning"
|
||||
type="button"
|
||||
v-on:click="restart()"
|
||||
>Restart</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
taskSubmitter
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
scanUri: null,
|
||||
stepValue: 0,
|
||||
hostDeviceName: null,
|
||||
message: null,
|
||||
username: "",
|
||||
currentTime: this.getLocalDatetimeString(),
|
||||
patientID: "",
|
||||
sampleType: "thin",
|
||||
scanRunning: false,
|
||||
baseURL: `${window.location.origin}/api/v2`
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
baseURL: function (val) {
|
||||
if (this.baseURL) {
|
||||
this.updateScanUri();
|
||||
} else {
|
||||
this.message = "No baseURL given";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
if (this.baseURL) {
|
||||
this.updateScanUri();
|
||||
} else {
|
||||
this.message = "No baseURL given";
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateScanUri: function() {
|
||||
axios
|
||||
.get(this.pluginsUri) // Get a list of plugins
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
var plugins = response.data;
|
||||
var foundExtension = plugins.find(
|
||||
e => e.title === "org.openflexure.scan"
|
||||
);
|
||||
// if ScanPlugin is enabled
|
||||
if (foundExtension) {
|
||||
// Get plugin action link
|
||||
this.scanUri = foundExtension.links.tile.href;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
decrement: function() {
|
||||
if (this.stepValue > 0) {
|
||||
this.stepValue = this.stepValue - 1;
|
||||
}
|
||||
},
|
||||
|
||||
increment: function() {
|
||||
// Validate sections
|
||||
if (this.stepValue == 0) {
|
||||
if (!this.username) {
|
||||
this.message = "Please enter your name";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.stepValue == 1) {
|
||||
if (!this.patientID) {
|
||||
this.message = "Please enter a patient ID";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Upper bound on section number
|
||||
if (this.stepValue < 3) {
|
||||
this.stepValue = this.stepValue + 1;
|
||||
this.message = "";
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
restart: function() {
|
||||
this.stepValue = 0;
|
||||
this.patientID = "";
|
||||
this.currentTime = this.getLocalDatetimeString();
|
||||
},
|
||||
|
||||
getLocalDatetimeString: function() {
|
||||
var dt = new Date();
|
||||
var tzo = -dt.getTimezoneOffset(),
|
||||
dif = tzo >= 0 ? "+" : "-",
|
||||
pad = function(num) {
|
||||
var norm = Math.floor(Math.abs(num));
|
||||
return (norm < 10 ? "0" : "") + norm;
|
||||
};
|
||||
return (
|
||||
dt.getFullYear() +
|
||||
"-" +
|
||||
pad(dt.getMonth() + 1) +
|
||||
"-" +
|
||||
pad(dt.getDate()) +
|
||||
"T" +
|
||||
pad(dt.getHours()) +
|
||||
":" +
|
||||
pad(dt.getMinutes()) +
|
||||
":" +
|
||||
pad(dt.getSeconds()) +
|
||||
dif +
|
||||
pad(tzo / 60) +
|
||||
":" +
|
||||
pad(tzo % 60)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
pluginsUri: function() {
|
||||
return `${this.baseURL}/extensions`;
|
||||
},
|
||||
|
||||
currentTimeForm: {
|
||||
get() {
|
||||
// Chop the timezone information from the end
|
||||
return this.currentTime.substr(0, 19);
|
||||
},
|
||||
set(val) {
|
||||
console.log(val);
|
||||
// Get timezone
|
||||
var dt = new Date();
|
||||
var tzo = -dt.getTimezoneOffset(),
|
||||
dif = tzo >= 0 ? "+" : "-",
|
||||
pad = function(num) {
|
||||
var norm = Math.floor(Math.abs(num));
|
||||
return (norm < 10 ? "0" : "") + norm;
|
||||
};
|
||||
// Stick to the end of the new timestring from the form
|
||||
this.currentTime = val + dif + pad(tzo / 60) + ":" + pad(tzo % 60);
|
||||
}
|
||||
},
|
||||
|
||||
payload: {
|
||||
get() {
|
||||
return {
|
||||
filename: `${this.patientID}_${this.sampleType}`,
|
||||
temporary: false,
|
||||
bayer: false,
|
||||
grid: [10, 10, 9],
|
||||
stride_size: [800, 600, 10],
|
||||
fast_autofocus: true,
|
||||
autofocus_dz: 2000,
|
||||
use_video_port: false,
|
||||
annotations: {
|
||||
patientID: this.patientID,
|
||||
username: this.username,
|
||||
clientDatetime: this.currentTime
|
||||
},
|
||||
tags: ["medscan"]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.medscan {
|
||||
font-family: sans-serif;
|
||||
color: #444;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="datetime-local"] {
|
||||
display: block;
|
||||
margin: 0 10px 0 0;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
margin: 0 10px 0 0;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-column-gap: 10px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #c11;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<!-- Grid managing tab content -->
|
||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="control-component">
|
||||
<paneSlideScan />
|
||||
</div>
|
||||
<div class="view-component uk-width-expand">
|
||||
<streamDisplay />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import paneSlideScan from "../controlComponents/paneSlideScan";
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
|
||||
export default {
|
||||
name: "NavigateContent",
|
||||
|
||||
components: {
|
||||
paneSlideScan,
|
||||
streamDisplay
|
||||
}
|
||||
};
|
||||
</script>
|
||||
14
poetry.lock
generated
14
poetry.lock
generated
|
|
@ -302,7 +302,7 @@ webargs = "^6.0.0"
|
|||
zeroconf = ">=0.24.5,<0.29.0"
|
||||
|
||||
[package.source]
|
||||
reference = "c79e6f28d5ffbd61dcfe7fc94130db604cdb4e2d"
|
||||
reference = "bf41b5009e8e0e465222399f38c0cd2104dbd0b7"
|
||||
type = "git"
|
||||
url = "https://github.com/labthings/python-labthings.git"
|
||||
[[package]]
|
||||
|
|
@ -718,16 +718,16 @@ description = "Declarative parsing and validation of HTTP request objects, with
|
|||
name = "webargs"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "6.1.0"
|
||||
version = "6.1.1"
|
||||
|
||||
[package.dependencies]
|
||||
marshmallow = ">=2.15.2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "webtest (2.0.35)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)", "mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox", "mock"]
|
||||
docs = ["Sphinx (3.0.3)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.8.0)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)"]
|
||||
dev = ["pytest", "webtest (2.0.35)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)", "mypy (0.782)", "flake8 (3.8.3)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)", "tox", "mock"]
|
||||
docs = ["Sphinx (3.2.1)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.8.0)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)"]
|
||||
frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)"]
|
||||
lint = ["mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"]
|
||||
lint = ["mypy (0.782)", "flake8 (3.8.3)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)"]
|
||||
tests = ["pytest", "webtest (2.0.35)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=2.0.0)", "aiohttp (>=3.0.0)", "mock"]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1220,8 +1220,8 @@ urllib3 = [
|
|||
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
|
||||
]
|
||||
webargs = [
|
||||
{file = "webargs-6.1.0-py2.py3-none-any.whl", hash = "sha256:cf0b5e2fdfb81f28b9332fce15621069d3fbc910a01c7ca8a5e166371699927b"},
|
||||
{file = "webargs-6.1.0.tar.gz", hash = "sha256:ebb47fb35c3c4fc764213a17d1686e82fec54759ebed2b0715907d566668bb3f"},
|
||||
{file = "webargs-6.1.1-py2.py3-none-any.whl", hash = "sha256:2ead6ce38559152043ab4ada4d389aef6c804b0c169482e7c92b3f498f690b2c"},
|
||||
{file = "webargs-6.1.1.tar.gz", hash = "sha256:412ecadd977afdea0ed6fa5f5b65ddd13a099269e622ec537f9c74c443ce4d0b"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue