Improve basic layout and transition to landscape on smaller screens using css grid. Signed-off-by: Justin Georgi <justin.georgi@gmail.com> Reviewed-on: Georgi_Lab/ALVINN_f7#21
270 lines
9.9 KiB
Vue
270 lines
9.9 KiB
Vue
<template>
|
|
<f7-page name="detect">
|
|
<!-- Top Navbar -->
|
|
<f7-navbar :sliding="false" back-link="Back">
|
|
<f7-nav-title sliding>{{ regions[activeRegion] }}</f7-nav-title>
|
|
</f7-navbar>
|
|
<f7-block class="detect-grid">
|
|
<div class="image-container">
|
|
<img :src="imageView" id="im-display" ref="image_src" style="flex: 1 1 0%; object-fit: scale-down; max-width: 100%; max-height: 100%; min-width: 0; min-height: 0;" />
|
|
<div ref="structure_box" style="border: solid 3px yellow; position: absolute; display: none;" />
|
|
</div>
|
|
<div v-if="resultData && resultData.detections" class="chip-results" style="grid-area: result-view; flex: 0 0 auto; align-self: center;">
|
|
<f7-chip v-for="(result, idx) in resultData.detections" :class="(idx == selectedChip) ? 'selected-chip' : ''" :text="result.label" media=" " :tooltip="result.confidence.toFixed(1)" :media-bg-color="chipColor(result.confidence)" deleteable @click="selectChip(idx)" @delete="deleteChip(idx)" />
|
|
</div>
|
|
<f7-segmented class="image-menu" raised>
|
|
<f7-button popover-open="#region-popover">
|
|
<img :src="imageRegion" />
|
|
</f7-button>
|
|
<f7-button @click="setData" :class="(imageLoaded) ? '' : 'disabled'">
|
|
<img src="../assets/icons/visibility.svg" />
|
|
</f7-button>
|
|
<f7-button @click="selectImage">
|
|
<img src="../assets/icons/image.svg" />
|
|
</f7-button>
|
|
<f7-button @click="setData">
|
|
<img src="../assets/icons/videocam.svg" />
|
|
</f7-button>
|
|
</f7-segmented>
|
|
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" style="display: none;"/>
|
|
</f7-block>
|
|
</f7-page>
|
|
</template>
|
|
|
|
<style>
|
|
.detect-grid {
|
|
/*display: flex;
|
|
justify-content: flex-start;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
|
|
box-sizing: border-box;
|
|
width: 100vw;*/
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: 1fr auto min-content;
|
|
grid-template-areas:
|
|
"image-view"
|
|
"result-view"
|
|
"menu-view";
|
|
justify-items: center;
|
|
height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
|
|
max-height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
|
|
}
|
|
|
|
.image-container {
|
|
grid-area: image-view;
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
position: relative;
|
|
display: flex;
|
|
}
|
|
|
|
.chip-results {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 5px;
|
|
padding: 10px;
|
|
--f7-chip-border-radius: 16px;
|
|
--f7-chip-media-size: 32px;
|
|
--f7-chip-font-weight: normal;
|
|
}
|
|
|
|
.chip-results .chip {
|
|
padding-left: 8px;
|
|
}
|
|
|
|
.selected-chip {
|
|
font-weight: 500;
|
|
box-shadow: 4px 4px 1px var(--f7-theme-color);
|
|
transform: translate(-2px, -2px);
|
|
}
|
|
|
|
.image-menu {
|
|
grid-area: menu-view;
|
|
margin: 5px;
|
|
width: 50%;
|
|
max-width: 400px;
|
|
min-width: 192px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.image-menu .button {
|
|
aspect-ratio: 1;
|
|
height: auto;
|
|
padding: 5px;
|
|
}
|
|
|
|
.button > img {
|
|
height: 100%;
|
|
}
|
|
|
|
@media (max-height: 450px) and (orientation: landscape) {
|
|
.detect-grid {
|
|
grid-template-columns: minmax(0,1fr) max-content auto;
|
|
grid-template-rows: calc(100vh - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom) - 64px);
|
|
grid-template-areas:
|
|
"image-view result-view menu-view";
|
|
justify-items: center;
|
|
align-items: stretch;
|
|
height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
|
|
max-height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
|
|
position: relative;
|
|
}
|
|
|
|
.chip-results {
|
|
flex-direction: column;
|
|
max-height: 100%;
|
|
justify-self: start;
|
|
flex-wrap: nowrap;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
.image-container {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.image-menu {
|
|
flex-direction: column;
|
|
width: auto;
|
|
min-width: 0;
|
|
flex: 1 1 0%;
|
|
max-height: 100%;
|
|
}
|
|
|
|
.image-menu .button {
|
|
aspect-ratio: 1;
|
|
width: auto;
|
|
height: 100%;
|
|
flex: 1 1 0%;
|
|
border-bottom: 1px solid var(--f7-segmented-raised-divider-color);
|
|
}
|
|
|
|
.button > img {
|
|
width:auto;
|
|
height: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import { f7 } from 'framework7-vue'
|
|
|
|
export default {
|
|
props: {
|
|
f7route: Object,
|
|
},
|
|
data () {
|
|
return {
|
|
regions: ['Thorax','Abdomen/Pelvis','Limbs','Head and Neck'],
|
|
resultData: {},
|
|
selectedChip: -1,
|
|
activeRegion: 4,
|
|
imageRegion: '',
|
|
imageLoaded: false,
|
|
imageView: '../assets/icons/image.svg',
|
|
reader: new FileReader(),
|
|
detectorName: ''
|
|
}
|
|
},
|
|
created () {
|
|
switch (this.f7route.params.region) {
|
|
case 'thorax':
|
|
this.activeRegion = 0
|
|
this.imageRegion = '../assets/regions/thorax.svg'
|
|
this.detectorName = 'thorax'
|
|
break;
|
|
case 'abdomen':
|
|
this.activeRegion = 1
|
|
this.imageRegion = '../assets/regions/abdpel.svg'
|
|
this.detectorName = 'combined'
|
|
break;
|
|
case 'limbs':
|
|
this.activeRegion = 2
|
|
this.imageRegion = '../assets/regions/limb.svg'
|
|
this.detectorName = 'defaultNew'
|
|
break;
|
|
case 'head':
|
|
this.activeRegion = 3
|
|
this.imageRegion = '../assets/regions/headneck.svg'
|
|
break;
|
|
}
|
|
},
|
|
methods: {
|
|
chipColor (confVal) {
|
|
if (confVal >= 90) return 'green'
|
|
if (confVal >= 70) return 'lime'
|
|
return 'yellow'
|
|
},
|
|
setData () {
|
|
var self = this
|
|
var loadServerSettings = localStorage.getItem('serverSettings')
|
|
if (loadServerSettings) var serverSettings = JSON.parse(loadServerSettings)
|
|
if (serverSettings && serverSettings.use) {
|
|
var modelURL = `http://${serverSettings.address}:${serverSettings.port}/detect`
|
|
var xhr = new XMLHttpRequest()
|
|
xhr.open("POST", modelURL)
|
|
xhr.setRequestHeader('Content-Type', 'application/json')
|
|
xhr.onload = function () {
|
|
if (this.status !== 200) {
|
|
//this.response.text().then(function(message){alert(message)})
|
|
console.log(this.response)
|
|
return;
|
|
}
|
|
self.resultData = JSON.parse(this.response)
|
|
}
|
|
|
|
var doodsData = {
|
|
"detector_name": this.detectorName,
|
|
"detect": {
|
|
"*":50
|
|
}
|
|
}
|
|
doodsData.data = this.reader.result.split(',')[1]
|
|
|
|
xhr.send(JSON.stringify(doodsData))
|
|
} else {
|
|
f7.dialog.alert('Using built-in model')
|
|
}
|
|
},
|
|
selectImage () {
|
|
var loadResult = this.$refs.image_chooser.click()
|
|
},
|
|
selectChip ( iChip ) {
|
|
var imgWidth
|
|
var imgHeight
|
|
|
|
this.selectedChip = iChip
|
|
const box = this.$refs.structure_box
|
|
const img = this.$refs.image_src
|
|
var imgAspect = img.naturalWidth / img.naturalHeight
|
|
var rendAspect = img.offsetWidth / img.offsetHeight
|
|
if (imgAspect >= rendAspect) {
|
|
imgWidth = img.offsetWidth
|
|
imgHeight = img.offsetWidth / imgAspect
|
|
} else {
|
|
imgWidth = img.offsetHeight * imgAspect
|
|
imgHeight = img.offsetHeight
|
|
}
|
|
box.style.display = "block"
|
|
box.style.left = `${(img.offsetWidth - imgWidth) / 2 + this.resultData.detections[iChip].left * imgWidth}px`
|
|
box.style.top = `${(img.offsetHeight - imgHeight) / 2 + this.resultData.detections[iChip].top * imgHeight}px`
|
|
box.style.width = `${(this.resultData.detections[iChip].right - this.resultData.detections[iChip].left) * imgWidth}px`
|
|
box.style.height = `${(this.resultData.detections[iChip].bottom - this.resultData.detections[iChip].top) * imgHeight}px`
|
|
},
|
|
deleteChip ( iChip ) {
|
|
f7.dialog.confirm(`${this.resultData.detections[iChip].label} is identified with ${this.resultData.detections[iChip].confidence.toFixed(1)}% confidence. Are you sure you want to delete it?`, () => {
|
|
this.resultData.detections.splice(iChip, 1)
|
|
});
|
|
},
|
|
getImage () {
|
|
const example = this.$refs.image_chooser.files[0];
|
|
this.imageView = URL.createObjectURL(example);
|
|
this.reader.readAsDataURL(example)
|
|
this.imageLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
</script> |