Merge branch 'gui-dzi-viewer' into 'v3'
Gui dzi viewer See merge request openflexure/openflexure-microscope-server!266
This commit is contained in:
commit
58a4dc521d
6 changed files with 218 additions and 12 deletions
|
|
@ -44,3 +44,10 @@ def add_static_files(app: FastAPI) -> None:
|
||||||
StaticFiles(directory=fpath),
|
StaticFiles(directory=fpath),
|
||||||
name=f"static_{fname}",
|
name=f"static_{fname}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Mount the scan directory to .../scans/, to allow dzi viewing
|
||||||
|
app.mount(
|
||||||
|
"/scans/",
|
||||||
|
StaticFiles(directory="/var/openflexure/scans/"),
|
||||||
|
name="scans",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class ScanInfo(BaseModel):
|
||||||
modified: datetime
|
modified: datetime
|
||||||
number_of_images: int
|
number_of_images: int
|
||||||
stitch_available: bool
|
stitch_available: bool
|
||||||
|
dzi: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
DOWNLOADABLE_SCAN_FILES = (
|
DOWNLOADABLE_SCAN_FILES = (
|
||||||
|
|
@ -819,6 +820,11 @@ class SmartScanThing(Thing):
|
||||||
]
|
]
|
||||||
number_of_images = len(scan_images)
|
number_of_images = len(scan_images)
|
||||||
stitch_available = len(stitches) > 0
|
stitch_available = len(stitches) > 0
|
||||||
|
dzi = [i for i in folder_contents if i.endswith("dzi")]
|
||||||
|
if len(dzi) > 0:
|
||||||
|
dzi = str(dzi[0])
|
||||||
|
else:
|
||||||
|
dzi = None
|
||||||
else:
|
else:
|
||||||
number_of_images = 0
|
number_of_images = 0
|
||||||
stitch_available = False
|
stitch_available = False
|
||||||
|
|
@ -831,6 +837,7 @@ class SmartScanThing(Thing):
|
||||||
modified=modified,
|
modified=modified,
|
||||||
number_of_images=number_of_images,
|
number_of_images=number_of_images,
|
||||||
stitch_available=stitch_available,
|
stitch_available=stitch_available,
|
||||||
|
dzi=dzi,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return scans
|
return scans
|
||||||
|
|
@ -925,7 +932,7 @@ class SmartScanThing(Thing):
|
||||||
raise FileNotFoundError("No latest scan found")
|
raise FileNotFoundError("No latest scan found")
|
||||||
|
|
||||||
images_dir = self.images_dir_for_scan(self.latest_scan_name)
|
images_dir = self.images_dir_for_scan(self.latest_scan_name)
|
||||||
stitch_path = os.path.join(images_dir, "stitched_from_stage.jpg")
|
stitch_path = os.path.join(images_dir, "preview.jpg")
|
||||||
if not os.path.isfile(stitch_path):
|
if not os.path.isfile(stitch_path):
|
||||||
raise FileNotFoundError("Latest scan has no preview stitch")
|
raise FileNotFoundError("Latest scan has no preview stitch")
|
||||||
return stitch_path
|
return stitch_path
|
||||||
|
|
@ -980,7 +987,7 @@ class SmartScanThing(Thing):
|
||||||
[
|
[
|
||||||
STITCHING_CMD,
|
STITCHING_CMD,
|
||||||
"--stitching_mode",
|
"--stitching_mode",
|
||||||
"only_stage_stitch",
|
"preview_stitch",
|
||||||
"--minimum_overlap",
|
"--minimum_overlap",
|
||||||
f"{min_overlap}",
|
f"{min_overlap}",
|
||||||
self._ongoing_scan_images_dir,
|
self._ongoing_scan_images_dir,
|
||||||
|
|
@ -1096,6 +1103,7 @@ class SmartScanThing(Thing):
|
||||||
"--stitching_mode",
|
"--stitching_mode",
|
||||||
"all",
|
"all",
|
||||||
f"{tiff_arg}",
|
f"{tiff_arg}",
|
||||||
|
"--stitch_dzi",
|
||||||
"--minimum_overlap",
|
"--minimum_overlap",
|
||||||
f"{round(overlap * 0.9, 2)}",
|
f"{round(overlap * 0.9, 2)}",
|
||||||
images_folder,
|
images_folder,
|
||||||
|
|
@ -1158,6 +1166,7 @@ class SmartScanThing(Thing):
|
||||||
"stitched_from",
|
"stitched_from",
|
||||||
"stitched.om",
|
"stitched.om",
|
||||||
"stitching_correlations",
|
"stitching_correlations",
|
||||||
|
"preview.jp",
|
||||||
]
|
]
|
||||||
|
|
||||||
with zipfile.ZipFile(zip_fname, mode="a") as scan_zip:
|
with zipfile.ZipFile(zip_fname, mode="a") as scan_zip:
|
||||||
|
|
|
||||||
9
webapp/package-lock.json
generated
9
webapp/package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
||||||
"material-design-icons": "^3.0",
|
"material-design-icons": "^3.0",
|
||||||
"material-symbols": "^0.14.3",
|
"material-symbols": "^0.14.3",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
|
"openseadragon": "^5.0.0",
|
||||||
"vue-observe-visibility": "^0.4"
|
"vue-observe-visibility": "^0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -10539,6 +10540,14 @@
|
||||||
"opener": "bin/opener-bin.js"
|
"opener": "bin/opener-bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openseadragon": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/openseadragon/-/openseadragon-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-a/hjouW9i3UfWxRADVYN2MyRhXMGnE7x9VVL7/4jXCcDLFyO4UM5o4RStYtqa5BfaHw/wMNAaD2WbxQF8f1pJg==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/openseadragon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/opn": {
|
"node_modules/opn": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"material-design-icons": "^3.0",
|
"material-design-icons": "^3.0",
|
||||||
"material-symbols": "^0.14.3",
|
"material-symbols": "^0.14.3",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
|
"openseadragon": "^5.0.0",
|
||||||
"vue-observe-visibility": "^0.4"
|
"vue-observe-visibility": "^0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<div v-observe-visibility="visibilityChanged">
|
||||||
|
<div id="openseadragon"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import OpenSeaDragon from "openseadragon";
|
||||||
|
|
||||||
|
// Export main app
|
||||||
|
export default {
|
||||||
|
name: "OpenSeadragonViewer",
|
||||||
|
components: { },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
osdViewer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
src: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.loadOpenSeaDragon(newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
if (this.src) {
|
||||||
|
this.loadOpenSeaDragon(this.src);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
// Remove global signal listener to perform a gallery refresh
|
||||||
|
this.osdViewer.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
visibilityChanged(isVisible) {
|
||||||
|
if (isVisible) {
|
||||||
|
this.loadOpenSeaDragon();
|
||||||
|
}else{
|
||||||
|
this.osdViewer.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadOpenSeaDragon() {
|
||||||
|
if (this.osdViewer) {
|
||||||
|
this.osdViewer.destroy();
|
||||||
|
}
|
||||||
|
this.osdViewer = OpenSeaDragon({
|
||||||
|
id: "openseadragon",
|
||||||
|
crossOriginPolicy: 'Anonymous',
|
||||||
|
tileSources: this.src,
|
||||||
|
showNavigationControl: false,
|
||||||
|
maxZoomPixelRatio: 2,
|
||||||
|
gestureSettingsMouse: {
|
||||||
|
clickToZoom: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openFullscreen() {
|
||||||
|
if (this.osdViewer) {
|
||||||
|
this.osdViewer.setFullScreen(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
#openseadragon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: black;
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
|
#info-panel {
|
||||||
|
position: relative;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 1px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-observe-visibility="visibilityChanged"
|
v-observe-visibility="visibilityChanged"
|
||||||
class="galleryDisplay uk-padding uk-padding-remove-top"
|
class="galleryDisplay uk-padding uk-padding-remove-top">
|
||||||
>
|
|
||||||
<!-- Gallery nav bar -->
|
<!-- Gallery nav bar -->
|
||||||
<nav
|
<nav
|
||||||
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
||||||
|
|
@ -35,6 +34,26 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- Modal for scan display -->
|
||||||
|
<div id="scan-modal" ref="scanModal" style="padding: 10px;" uk-modal>
|
||||||
|
<div class="uk-modal-dialog uk-modal-body " v-if="selectedScan" style="padding: 10px; width: 95%; height: 95%;">
|
||||||
|
<h2 class="uk-modal-title">
|
||||||
|
{{ selectedScan.name }}
|
||||||
|
<button class="uk-modal-close uk-float-right" type="button"><span class="material-symbols-outlined">close</span></button>
|
||||||
|
<button class="uk-float-right" type="button" @click="goFullscreen">
|
||||||
|
<span class="material-symbols-outlined">fullscreen</span>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div v-if="selectedScanDZIAvailable" id="viewer_container" style="height: 80%;">
|
||||||
|
<OpenSeadragonViewer
|
||||||
|
id="openseadragon"
|
||||||
|
ref="openseadragon"
|
||||||
|
:src="selectedScanDZI"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Gallery -->
|
<!-- Gallery -->
|
||||||
<div
|
<div
|
||||||
v-if="$store.getters.ready"
|
v-if="$store.getters.ready"
|
||||||
|
|
@ -61,11 +80,13 @@
|
||||||
class="thumbnail-fit"
|
class="thumbnail-fit"
|
||||||
:src="thumbnailPath(item.name)"
|
:src="thumbnailPath(item.name)"
|
||||||
onerror="this.src='/titleiconpink.svg';"
|
onerror="this.src='/titleiconpink.svg';"
|
||||||
|
@click="showScan(item)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="uk-card-title" style="text-align: center;">{{ item.name }}</h3>
|
<h3 class="uk-card-title" style="text-align: center;">{{ item.name }}</h3>
|
||||||
<action-button
|
<div class="button-container">
|
||||||
|
<action-button
|
||||||
thing="smart_scan"
|
thing="smart_scan"
|
||||||
action="download_zip"
|
action="download_zip"
|
||||||
submit-label="Download ZIP"
|
submit-label="Download ZIP"
|
||||||
|
|
@ -85,40 +106,54 @@
|
||||||
submit-label="Stitch Images"
|
submit-label="Stitch Images"
|
||||||
thing="smart_scan"
|
thing="smart_scan"
|
||||||
action="stitch_scan"
|
action="stitch_scan"
|
||||||
v-if="item.can_stitch"
|
v-if="item.can_stitch | !item.dzi"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:submit-data="{ scan_name: item.name }"
|
:submit-data="{ scan_name: item.name }"
|
||||||
:button-primary="false"
|
:button-primary="false"
|
||||||
:modal-progress="true"
|
:modal-progress="true"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="item.dzi" class="uk-button uk-button-default uk-width-1-1"
|
||||||
|
@click="showScan(item)"
|
||||||
|
>
|
||||||
|
Show Stitched Scan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ item.number_of_images }} images</li>
|
<li>{{ item.number_of_images }} images</li>
|
||||||
<li>created: {{ formatDate(item.created) }}</li>
|
<li>created: {{ formatDate(item.created) }}</li>
|
||||||
<li>modified: {{ formatDate(item.modified) }}</li>
|
<li>modified: {{ formatDate(item.modified) }}</li>
|
||||||
<li v-if="item.number_of_images<3" style="color:red; font-weight: bold;">Not enough images to stitch</li>
|
|
||||||
<li v-else-if=!item.stitch_available style="color:red; font-weight: bold;">Scan not stitched</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<li v-if="item.number_of_images<3" class="warning-msg">Not enough images to stitch</li>
|
||||||
|
<li v-else-if="!item.dzi & item.stitch_available" class="alert-msg">Interactive preview not available</li>
|
||||||
|
<li v-else-if=!item.stitch_available class="alert-msg">High quality stitch not available</li>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import UIkit from "uikit";
|
||||||
import actionButton from "../labThingsComponents/actionButton.vue";
|
import actionButton from "../labThingsComponents/actionButton.vue";
|
||||||
|
import OpenSeadragonViewer from "./scanListComponents/openSeadragonViewer.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
name: "ScanListContent",
|
name: "ScanListContent",
|
||||||
components: { actionButton },
|
components: { actionButton, OpenSeadragonViewer },
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
scans: []
|
scans: [],
|
||||||
|
selectedScan: null,
|
||||||
|
osdViewer: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -133,6 +168,16 @@ export default {
|
||||||
},
|
},
|
||||||
scansEmpty() {
|
scansEmpty() {
|
||||||
return this.scans.length == 0;
|
return this.scans.length == 0;
|
||||||
|
},
|
||||||
|
selectedScanDZI() {
|
||||||
|
if (this.selectedScan && this.dzi!="") {
|
||||||
|
return `${this.$store.getters.baseUri}/scans/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedScanDZIAvailable() {
|
||||||
|
return this.selectedScan && this.selectedScan.dzi;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -185,6 +230,9 @@ export default {
|
||||||
this.updateScans();
|
this.updateScans();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
goFullscreen() {
|
||||||
|
this.$refs.openseadragon.openFullscreen();
|
||||||
|
},
|
||||||
async updateScans() {
|
async updateScans() {
|
||||||
try {
|
try {
|
||||||
let scans = await this.readThingProperty("smart_scan", "scans");
|
let scans = await this.readThingProperty("smart_scan", "scans");
|
||||||
|
|
@ -256,7 +304,16 @@ export default {
|
||||||
console.log(link);
|
console.log(link);
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
}
|
},
|
||||||
|
showScan(scan) {
|
||||||
|
if (scan.dzi){
|
||||||
|
this.selectedScan = scan;
|
||||||
|
UIkit.modal(this.$refs.scanModal).show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.modalError(`Scan not stitched for viewing in webapp, please download or stitch`)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -285,6 +342,20 @@ export default {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#openseadragon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#info-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 1px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
/deep/ .capture-card {
|
/deep/ .capture-card {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100%; // Used to have all cards in a row match their heights
|
height: 100%; // Used to have all cards in a row match their heights
|
||||||
|
|
@ -296,5 +367,18 @@ ul {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
list-style-type:none;
|
list-style-type:none;
|
||||||
|
margin: 5px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-msg {
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-msg{
|
||||||
|
color: orange;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue