Allow images and detection upload for future data (#56)

Closes: #48

This PR should allow the user to upload an image that has been taken with the camera and the associated detection information to a file server.  These file can then be used to add data to ALVINN's models.

Current upload destination is set to a file drop folder in Georgi Lab Nextcloud, but eventually (maybe) should be changed to some dedicated ALVINN backend.

Also, prediction data is still not formatted in any specific way.  It is just a JSON array of (top, left, bottom, right, label) objects.

Reviewed-on: Georgi_Lab/ALVINN_f7#56
This commit is contained in:
2023-12-19 09:52:18 -07:00
parent 75c54d6740
commit 619211e827
3 changed files with 118 additions and 9 deletions

View File

@@ -24,20 +24,20 @@
@delete="deleteChip(result.resultIndex)"
:style="chipGradient(result.confidence)"
/>
<span v-if="showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted }).length == 0" style="height: var(--f7-chip-height); font-size: calc(var(--f7-chip-height) - 4px); font-weight: bolder; margin: 2px;">No results.</span>
<span v-if="numResults == 0" style="height: var(--f7-chip-height); font-size: calc(var(--f7-chip-height) - 4px); font-weight: bolder; margin: 2px;">No results.</span>
</div>
<f7-segmented class="image-menu" raised>
<f7-button popover-open="#region-popover">
<RegionIcon :region="activeRegion" />
</f7-button>
<f7-button popover-open="#capture-popover">
<SvgIcon icon="image" fill-color="var(--avn-theme-color)"/>
<SvgIcon icon="image"/>
</f7-button>
<f7-button @click="setData" :class="(imageLoaded) ? '' : 'disabled'">
<SvgIcon icon="visibility" fill-color="var(--avn-theme-color)"/>
<SvgIcon icon="visibility"/>
</f7-button>
<f7-button class="disabled" @click="setData">
<SvgIcon icon="videocam" fill-color="var(--avn-theme-color)"/>
<f7-button :class="(numResults && uploadDirty && viewedAll) ? '' : 'disabled'" @click="submitData">
<SvgIcon :icon="(uploadUid) ? 'cloud_done' : 'cloud_upload'"/>
</f7-button>
</f7-segmented>
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" capture="environment" style="display: none;"/>
@@ -56,7 +56,6 @@
</f7-list>
</f7-accordion-content>
</f7-list-item>
<f7-list-item title="Cordova">{{ isCordova }}</f7-list-item>
<f7-list-item title="Turn on debugging">
<f7-toggle v-model:checked="debugOn" style="margin-right: 16px;" />
</f7-list-item>
@@ -84,6 +83,9 @@
<f7-popover id="capture-popover">
<f7-segmented raised style="flex-wrap: wrap; flex-direction: column;">
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" class="disabled" @click="videoStream">
<SvgIcon icon="videocam"/>
</f7-button>
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('camera')">
<SvgIcon icon="photo_camera" />
</f7-button>
@@ -221,7 +223,10 @@
import RegionIcon from '../components/region-icon.vue'
import SvgIcon from '../components/svg-icon.vue'
import submitMixin from './submit-mixin'
export default {
mixins: [submitMixin],
props: {
f7route: Object,
},
@@ -245,7 +250,9 @@
serverSettings: {},
debugOn: false,
debugText: ['Variables loaded'],
isCordova: !!window.cordova
isCordova: !!window.cordova,
uploadUid: null,
uploadDirty: false
}
},
created () {
@@ -301,6 +308,7 @@
},
showResults () {
var filteredResults = this.resultData.detections
if (!filteredResults) return []
var allSelect = this.detectorLabels.every( s => { return s.detect } )
var selectedLabels = this.detectorLabels
.filter( l => { return l.detect })
@@ -311,6 +319,14 @@
filteredResults[i].isSearched = allSelect || selectedLabels.includes(d.label)
})
return filteredResults
},
numResults () {
return this.showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted }).length
},
viewedAll () {
return this.resultData.detections
.filter( s => { return s.confidence >= this.detectorLevel})
.every( s => { return s.beenViewed })
}
},
methods: {
@@ -334,6 +350,7 @@
return;
}
self.resultData = JSON.parse(xhr.response)
self.uploadDirty = true
}
var doodsData = {
@@ -399,11 +416,13 @@
box.style.top = `${(img.offsetHeight - imgHeight) / 2 + this.resultData.detections[iChip].top * imgHeight}px`
box.style.width = `${(Math.min(this.resultData.detections[iChip].right, 1) - Math.max(this.resultData.detections[iChip].left, 0)) * imgWidth}px`
box.style.height = `${(Math.min(this.resultData.detections[iChip].bottom, 1) - Math.max(this.resultData.detections[iChip].top, 0)) * imgHeight}px`
this.resultData.detections[iChip].beenViewed = true
},
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)
this.resetView()
this.uploadDirty = true
});
},
getImage (searchImage) {
@@ -419,7 +438,7 @@
}
resolve()
})
loadImage.then((imageData) => {
loadImage.then(() => {
this.imageLoaded = true
this.resultData = {}
this.resetView()
@@ -432,6 +451,17 @@
this.selectedChip = -1
const box = this.$refs.structure_box
box.style.display = 'none'
},
videoStream() {
//TODO
return null
},
async submitData () {
var uploadData = this.showResults
.filter( d => { return d.aboveThreshold && d.isSearched && !d.isDeleted })
.map( r => { return {"top": r.top, "left": r.left, "bottom": r.bottom, "right": r.right, "label": r.label}})
this.uploadUid = await this.uploadData(this.imageView.split(',')[1],uploadData,this.uploadUid)
if (this.uploadUid) { this.uploadDirty = false }
}
}
}