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),
|
||||
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
|
||||
number_of_images: int
|
||||
stitch_available: bool
|
||||
dzi: Optional[str]
|
||||
|
||||
|
||||
DOWNLOADABLE_SCAN_FILES = (
|
||||
|
|
@ -819,6 +820,11 @@ class SmartScanThing(Thing):
|
|||
]
|
||||
number_of_images = len(scan_images)
|
||||
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:
|
||||
number_of_images = 0
|
||||
stitch_available = False
|
||||
|
|
@ -831,6 +837,7 @@ class SmartScanThing(Thing):
|
|||
modified=modified,
|
||||
number_of_images=number_of_images,
|
||||
stitch_available=stitch_available,
|
||||
dzi=dzi,
|
||||
)
|
||||
)
|
||||
return scans
|
||||
|
|
@ -925,7 +932,7 @@ class SmartScanThing(Thing):
|
|||
raise FileNotFoundError("No latest scan found")
|
||||
|
||||
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):
|
||||
raise FileNotFoundError("Latest scan has no preview stitch")
|
||||
return stitch_path
|
||||
|
|
@ -980,7 +987,7 @@ class SmartScanThing(Thing):
|
|||
[
|
||||
STITCHING_CMD,
|
||||
"--stitching_mode",
|
||||
"only_stage_stitch",
|
||||
"preview_stitch",
|
||||
"--minimum_overlap",
|
||||
f"{min_overlap}",
|
||||
self._ongoing_scan_images_dir,
|
||||
|
|
@ -1096,6 +1103,7 @@ class SmartScanThing(Thing):
|
|||
"--stitching_mode",
|
||||
"all",
|
||||
f"{tiff_arg}",
|
||||
"--stitch_dzi",
|
||||
"--minimum_overlap",
|
||||
f"{round(overlap * 0.9, 2)}",
|
||||
images_folder,
|
||||
|
|
@ -1158,6 +1166,7 @@ class SmartScanThing(Thing):
|
|||
"stitched_from",
|
||||
"stitched.om",
|
||||
"stitching_correlations",
|
||||
"preview.jp",
|
||||
]
|
||||
|
||||
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-symbols": "^0.14.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"openseadragon": "^5.0.0",
|
||||
"vue-observe-visibility": "^0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -10539,6 +10540,14 @@
|
|||
"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": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"material-design-icons": "^3.0",
|
||||
"material-symbols": "^0.14.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"openseadragon": "^5.0.0",
|
||||
"vue-observe-visibility": "^0.4"
|
||||
},
|
||||
"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>
|
||||
<div
|
||||
v-observe-visibility="visibilityChanged"
|
||||
class="galleryDisplay uk-padding uk-padding-remove-top"
|
||||
>
|
||||
class="galleryDisplay uk-padding uk-padding-remove-top">
|
||||
<!-- Gallery nav bar -->
|
||||
<nav
|
||||
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
||||
|
|
@ -35,6 +34,26 @@
|
|||
</div>
|
||||
</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 -->
|
||||
<div
|
||||
v-if="$store.getters.ready"
|
||||
|
|
@ -61,11 +80,13 @@
|
|||
class="thumbnail-fit"
|
||||
:src="thumbnailPath(item.name)"
|
||||
onerror="this.src='/titleiconpink.svg';"
|
||||
@click="showScan(item)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="uk-card-title" style="text-align: center;">{{ item.name }}</h3>
|
||||
<action-button
|
||||
<div class="button-container">
|
||||
<action-button
|
||||
thing="smart_scan"
|
||||
action="download_zip"
|
||||
submit-label="Download ZIP"
|
||||
|
|
@ -85,40 +106,54 @@
|
|||
submit-label="Stitch Images"
|
||||
thing="smart_scan"
|
||||
action="stitch_scan"
|
||||
v-if="item.can_stitch"
|
||||
v-if="item.can_stitch | !item.dzi"
|
||||
:can-terminate="false"
|
||||
:submit-data="{ scan_name: item.name }"
|
||||
:button-primary="false"
|
||||
:modal-progress="true"
|
||||
@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>
|
||||
<li>{{ item.number_of_images }} images</li>
|
||||
<li>created: {{ formatDate(item.created) }}</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>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import UIkit from "uikit";
|
||||
import actionButton from "../labThingsComponents/actionButton.vue";
|
||||
import OpenSeadragonViewer from "./scanListComponents/openSeadragonViewer.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "ScanListContent",
|
||||
components: { actionButton },
|
||||
components: { actionButton, OpenSeadragonViewer },
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
scans: []
|
||||
scans: [],
|
||||
selectedScan: null,
|
||||
osdViewer: null
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -133,6 +168,16 @@ export default {
|
|||
},
|
||||
scansEmpty() {
|
||||
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();
|
||||
}
|
||||
},
|
||||
goFullscreen() {
|
||||
this.$refs.openseadragon.openFullscreen();
|
||||
},
|
||||
async updateScans() {
|
||||
try {
|
||||
let scans = await this.readThingProperty("smart_scan", "scans");
|
||||
|
|
@ -256,7 +304,16 @@ export default {
|
|||
console.log(link);
|
||||
document.body.appendChild(link);
|
||||
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>
|
||||
|
|
@ -285,6 +342,20 @@ export default {
|
|||
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 {
|
||||
width: 300px;
|
||||
height: 100%; // Used to have all cards in a row match their heights
|
||||
|
|
@ -296,5 +367,18 @@ ul {
|
|||
display: inline-block;
|
||||
text-align: center;
|
||||
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue