UI Redesign (#22)

* Rework UI into a new, responsive layout

* Send two streams (only one is currently downscaled)
This commit is contained in:
Declan Freeman-Gleason
2020-07-13 19:34:31 -07:00
committed by GitHub
parent aed92e7132
commit 8b46ad1cab
29 changed files with 945 additions and 559 deletions

View File

@@ -1,55 +1,58 @@
<template>
<div>
<CVrangeSlider
v-model="contourArea"
name="Area"
:min="0"
:max="100"
:step="0.1"
@input="handlePipelineData('contourArea')"
@rollback="e=> rollback('contourArea',e)"
/>
<CVrangeSlider
v-model="contourRatio"
name="Ratio (W/H)"
:min="0"
:max="100"
:step="0.1"
@input="handlePipelineData('contourRatio')"
@rollback="e=> rollback('contourRatio',e)"
/>
<CVrangeSlider
v-model="contourExtent"
name="Extent"
:min="0"
:max="100"
@input="handlePipelineData('contourExtent')"
@rollback="e=> rollback('contourExtent',e)"
/>
<CVslider
v-model="contourSpecklePercentage"
name="Speckle Rejection"
:min="0"
:max="100"
@input="handlePipelineData('contourSpecklePercentage')"
@rollback="e=> rollback('contourSpecklePercentage',e)"
/>
<CVselect
v-model="contourGroupingMode"
name="Target Group"
:list="['Single','Dual']"
@input="handlePipelineData('targetGroup')"
@rollback="e=> rollback('targetGroup',e)"
/>
<CVselect
v-model="contourIntersection"
name="Target Intersection"
:list="['None','Up','Down','Left','Right']"
:disabled="contourGroupingMode === 0"
@input="handlePipelineData('contourIntersection')"
@rollback="e=> rollback('contourIntersection',e)"
/>
</div>
<div>
<CVrangeSlider
v-model="contourArea"
name="Area"
min="0"
max="100"
step="0.1"
@input="handlePipelineData('contourArea')"
@rollback="e=> rollback('contourArea',e)"
/>
<CVrangeSlider
v-model="contourRatio"
name="Ratio (W/H)"
min="0"
max="100"
step="0.1"
@input="handlePipelineData('contourRatio')"
@rollback="e=> rollback('contourRatio',e)"
/>
<CVrangeSlider
v-model="contourExtent"
name="Extent"
min="0"
max="100"
@input="handlePipelineData('contourExtent')"
@rollback="e=> rollback('contourExtent',e)"
/>
<CVslider
v-model="contourSpecklePercentage"
name="Speckle Rejection"
min="0"
max="100"
:slider-cols="largeBox"
@input="handlePipelineData('contourSpecklePercentage')"
@rollback="e=> rollback('contourSpecklePercentage',e)"
/>
<CVselect
v-model="contourGroupingMode"
name="Target Group"
:select-cols="largeBox"
:list="['Single','Dual']"
@input="handlePipelineData('targetGroup')"
@rollback="e=> rollback('targetGroup',e)"
/>
<CVselect
v-model="contourIntersection"
name="Target Intersection"
:select-cols="largeBox"
:list="['None','Up','Down','Left','Right']"
:disabled="contourGroupingMode === 0"
@input="handlePipelineData('contourIntersection')"
@rollback="e=> rollback('contourIntersection',e)"
/>
</div>
</template>
<script>
@@ -71,6 +74,14 @@
return {}
},
computed: {
largeBox: {
get() {
// Sliders and selectors should be fuller width if we're on screen size medium and
// up and either not in compact mode (because the tab will be 100% screen width),
// or in driver mode (where the card will also be 100% screen width).
return this.$vuetify.breakpoint.mdAndUp && (!this.$store.state.compactMode || this.$store.getters.isDriverMode) ? 10 : 8;
}
},
contourArea: {
get() {
return this.$store.getters.currentPipelineSettings.contourArea

View File

@@ -3,16 +3,18 @@
<CVslider
v-model="cameraExposure"
name="Exposure"
:min="0"
:max="100"
min="0"
max="100"
:slider-cols="largeBox"
@input="handlePipelineData('cameraExposure')"
@rollback="e => rollback('cameraExposure', e)"
/>
<CVslider
v-model="cameraBrightness"
name="Brightness"
:min="0"
:max="100"
min="0"
max="100"
:slider-cols="largeBox"
@input="handlePipelineData('cameraBrightness')"
@rollback="e => rollback('cameraBrightness', e)"
/>
@@ -20,8 +22,9 @@
v-if="cameraGain !== -1"
v-model="cameraGain"
name="Gain"
:min="0"
:max="100"
min="0"
max="100"
:slider-cols="largeBox"
@input="handlePipelineData('cameraGain')"
@rollback="e => rollback('cameraGain', e)"
/>
@@ -29,6 +32,7 @@
v-model="inputImageRotationMode"
name="Orientation"
:list="['Normal','90° CW','180°','90° CCW']"
:select-cols="largeBox"
@input="handlePipelineData('inputImageRotationMode')"
@rollback="e => rollback('inputImageRotationMode',e)"
/>
@@ -36,6 +40,7 @@
v-model="cameraVideoModeIndex"
name="Resolution"
:list="resolutionList"
:select-cols="largeBox"
@input="handlePipelineData('cameraVideoModeIndex')"
@rollback="e => rollback('cameraVideoModeIndex', e)"
/>
@@ -43,6 +48,7 @@
v-model="outputFrameDivisor"
name="Stream Resolution"
:list="streamResolutionList"
:select-cols="largeBox"
@input="handlePipelineData('outputFrameDivisor')"
@rollback="e => rollback('outputFrameDivisor', e)"
/>
@@ -65,6 +71,14 @@
return {}
},
computed: {
largeBox: {
get() {
// Sliders and selectors should be fuller width if we're on screen size medium and
// up and either not in compact mode (because the tab will be 100% screen width),
// or in driver mode (where the card will also be 100% screen width).
return this.$vuetify.breakpoint.mdAndUp && (!this.$store.state.compactMode || this.$store.getters.isDriverMode) ? 10 : 8;
}
},
cameraExposure: {
get() {
return parseInt(this.$store.getters.currentPipelineSettings.cameraExposure);

View File

@@ -1,5 +1,7 @@
<template>
<div>
<span>Contour Sorting</span>
<v-divider class="mt-2" />
<CVselect
v-model="contourSortMode"
name="Sort Mode"
@@ -27,14 +29,13 @@
<CVswitch
v-model="outputShowMultipleTargets"
name="Show Multiple Targets"
class="mb-4"
@input="handlePipelineData('outputShowMultipleTargets')"
@rollback="e=> rollback('outputShowMultipleTargets', e)"
/>
<span>Robot Offset:</span>
<v-divider
dark
color="white"
/>
<span>Robot Offset</span>
<v-divider class="mt-2" />
<CVselect
v-model="offsetRobotOffsetMode"
name="Robot Offset Mode"

View File

@@ -1,71 +1,44 @@
<template>
<div>
<v-row
align="center"
justify="start"
dense
<!-- Special hidden upload input that gets 'clicked' when the user selects the right dropdown item' -->
<input
ref="file"
type="file"
accept=".csv"
style="display: none;"
@change="readFile"
>
<v-col :cols="6">
<CVswitch
v-model="value.is3D"
:disabled="allow3D"
name="Enable 3D"
@input="handleData('is3D')"
@rollback="e=> rollback('is3D',e)"
/>
</v-col>
<v-col>
<input
ref="file"
type="file"
style="display: none"
accept=".csv"
@change="readFile"
>
<v-btn
small
@click="$refs.file.click()"
>
<v-icon>mdi-upload</v-icon>
upload model
</v-btn>
</v-col>
</v-row>
<v-select
v-model="selectedModel"
dark
color="accent"
item-color="secondary"
label="Select a target model"
:items="FRCtargets"
item-text="name"
item-value="data"
@change="onModelSelect"
/>
<v-divider />
<CVslider
v-model="value.accuracy"
name="Contour simplification"
:min="0"
:max="100"
class="pt-2"
slider-cols="12"
name="Contour simplification amount"
:disabled="selectedModel === null"
min="0"
max="100"
@input="handleData('accuracy')"
@rollback="e=> rollback('accuracy',e)"
@rollback="e => rollback('accuracy', e)"
/>
<v-divider class="pb-2" />
<mini-map
class="miniMapClass"
:targets="targets"
:horizontal-f-o-v="horizontalFOV"
/>
<v-row>
<v-col>
<mini-map
class="miniMapClass"
:targets="targets"
:horizontal-f-o-v="horizontalFOV"
/>
</v-col>
<v-col>
<v-select
v-model="selectedModel"
:items="FRCtargets"
item-text="name"
item-value="data"
dark
color="#ffd843"
item-color="green"
/>
<v-btn
v-if="selectedModel !== null"
small
@click="uploadPremade"
>
Upload Premade
</v-btn>
</v-col>
</v-row>
<v-snackbar
v-model="snack"
top
@@ -79,14 +52,12 @@
<script>
import Papa from 'papaparse';
import miniMap from '../../components/pipeline/3D/MiniMap';
import CVswitch from '../../components/common/cv-switch';
import CVslider from '../../components/common/cv-slider'
import FRCtargetsConfig from '../../assets/FRCtargets'
export default {
name: "SolvePNP",
components: {
CVswitch,
CVslider,
miniMap
},
@@ -94,7 +65,6 @@
props: ['value'],
data() {
return {
is3D: false,
selectedModel: null,
FRCtargets: null,
snackbar: {
@@ -107,13 +77,13 @@
computed: {
targets: {
get() {
return 330; // TODO fix
return "FIXME"; // TODO fix
}
},
horizontalFOV: {
get() {
let index = this.$store.state.cameraSettings.resolution;
let FOV = this.$store.state.cameraSettings.fov;
let index = this.$store.getters.currentPipelineSettings.cameraVideoModeIndex;
let FOV = this.$store.getters.currentCameraSettings.fov;
let resolution = this.$store.getters.videoFormatList[index];
let diagonalView = FOV * (Math.PI / 180);
let diagonalAspect = Math.hypot(resolution.width, resolution.height);
@@ -122,14 +92,7 @@
},
allow3D: {
get() {
let index = this.$store.state.cameraSettings.resolution;
let currentRes = this.$store.getters.videoFormatList[index];
for (let res of this.$store.state.cameraSettings.calibrated) {
if (currentRes.width === res.width && currentRes.height === res.height) {
return false;
}
}
return true;
return this.$store.getters.currentCameraSettings.calibrated;
}
}
},
@@ -140,6 +103,11 @@
tmp.push({name: t, data: FRCtargetsConfig[t]})
}
}
// Special dropdown item for uploading your own model
// data is what gets put in selectedMode, so we add a special field
tmp.push({name: "Custom model", data: {isCustom: true}});
this.FRCtargets = tmp;
},
methods: {
@@ -150,21 +118,30 @@
skipEmptyLines: true
});
},
onModelSelect() {
if (this.selectedModel.isCustom) {
this.$refs.file.click();
} else {
this.uploadPremade();
}
},
onParse(result) {
if (result.data.length > 0) {
let data = [];
for (let item of result.data) {
for (let i = 0; i < result.data.length; i++) {
let item = result.data[i];
let tmp = [];
tmp.push(Number(item[0]));
tmp.push(Number(item[1]));
if (isNaN(tmp[0]) || isNaN(tmp[1])) {
this.snackbar = {
color: "error",
text: "Error: cvs did parse correctly"
text: `Error: custom target CSV contained a non-numeric value on line ${i + 1}`
};
this.snack = true;
this.selectedModel = null;
return;
}
data.push(tmp);
@@ -173,28 +150,32 @@
} else {
this.snackbar = {
color: "error",
text: "Error: cvs did not contain any data"
text: "Error: custom target CSV was empty"
};
this.snack = true;
this.selectedModel = null;
}
},
uploadPremade() {
this.uploadModel(this.selectedModel);
this.uploadModel(this.selectedModel, true);
},
uploadModel(model) {
uploadModel(model, premade = false) {
this.axios.post("http://" + this.$address + "/api/vision/pnpModel", model).then(() => {
this.snackbar = {
color: "success",
text: "File uploaded successfully"
text: premade ? "Target model changed successfully" : "Custom target model uploaded and selected successfully"
};
this.snack = true;
}).catch(() => {
this.snackbar = {
color: "error",
text: "An error occurred"
text: "An error occurred selecting a target model"
};
this.snack = true;
})
this.selectedModel = null;
});
}
}
}
@@ -202,7 +183,10 @@
<style scoped>
.miniMapClass {
width: 50% !important;
height: 50% !important;
width: 400px !important;
height: 100% !important;
margin-left: auto;
margin-right: auto;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div>
<v-row
align="start"
class="pb-4"
style="height: 300px;"
>
<!-- Simple table height must be set here and in the CSS for the fixed-header to work -->
<v-simple-table
fixed-header
height="100%"
dense
dark
>
<template v-slot:default>
<thead style="font-size: 20px;">
<tr>
<th class="text-center">
Target
</th>
<template v-if="!is3D">
<th class="text-center">
Pitch
</th>
<th class="text-center">
Yaw
</th>
</template>
<th class="text-center">
Area
</th>
<template v-if="is3D">
<th class="text-center">
X
</th>
<th class="text-center">
Y
</th>
<th class="text-center">
Angle
</th>
</template>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.getters.currentPipelineResults.targets"
:key="index"
>
<td>{{ index }}</td>
<template v-if="!is3D">
<td>{{ parseFloat(value.pitch).toFixed(2) }}</td>
<td>{{ parseFloat(value.yaw).toFixed(2) }}</td>
</template>
<td>{{ parseFloat(value.area).toFixed(2) }}</td>
<template v-if="is3D">
<!-- TODO: Make sure that units are correct -->
<td>{{ parseFloat(value.pose.x).toFixed(2) }}&nbsp;m</td>
<td>{{ parseFloat(value.pose.y).toFixed(2) }}&nbsp;m</td>
<td>{{ parseFloat(value.pose.rot).toFixed(2) }}&deg;</td>
</template>
</tr>
</tbody>
</template>
</v-simple-table>
</v-row>
</div>
</template>
<script>
export default {
name: "TargetsTab",
props: {
is3D: Boolean,
}
}
</script>
<style scoped>
.v-data-table {
text-align: center;
background-color: transparent !important;
width: 100%;
height: 100%;
overflow-y: auto;
}
.v-data-table th {
background-color: #006492 !important;
}
.v-data-table th,td {
font-size: 1rem !important;
}
/** This is unfortunately the only way to override table background color **/
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281;
}
</style>

View File

@@ -1,76 +1,75 @@
<template>
<div>
<CVrangeSlider
v-model="hsvHue"
name="Hue"
:min="0"
:max="180"
@input="handlePipelineData('hsvHue')"
@rollback="e => rollback('hue',e)"
/>
<CVrangeSlider
v-model="hsvSaturation"
name="Saturation"
:min="0"
:max="255"
@input="handlePipelineData('hsvSaturation')"
@rollback="e => rollback('saturation',e)"
/>
<CVrangeSlider
v-model="hsvValue"
name="Value"
:min="0"
:max="255"
@input="handlePipelineData('hsvValue')"
@rollback="e => rollback('value',e)"
/>
<v-divider
color="black"
style="margin-top: 5px"
/>
<v-row justify="center">
<v-btn
style="margin: 20px;"
color="#ffd843"
small
@click="setFunction(1)"
>
<v-icon>colorize</v-icon>
Eye drop
</v-btn>
<v-btn
style="margin: 20px;"
color="#ffd843"
small
@click="setFunction(2)"
>
<v-icon>add</v-icon>
Expand Selection
</v-btn>
<v-btn
style="margin: 20px;"
color="#ffd843"
small
@click="setFunction(3)"
>
<v-icon>remove</v-icon>
Shrink Selection
</v-btn>
</v-row>
<v-divider color="black"/>
<CVswitch
v-model="erode"
name="Erode"
@input="handlePipelineData('erode')"
@rollback="e => rollback('erode',e)"
/>
<CVswitch
v-model="dilate"
name="Dilate"
@input="handlePipelineData('dilate')"
@rollback="e => rollback('dilate',e)"
/>
</div>
<div>
<CVrangeSlider
v-model="hsvHue"
name="Hue"
:min="0"
:max="180"
@input="handlePipelineData('hsvHue')"
@rollback="e => rollback('hue',e)"
/>
<CVrangeSlider
v-model="hsvSaturation"
name="Saturation"
:min="0"
:max="255"
@input="handlePipelineData('hsvSaturation')"
@rollback="e => rollback('saturation',e)"
/>
<CVrangeSlider
v-model="hsvValue"
name="Value"
:min="0"
:max="255"
@input="handlePipelineData('hsvValue')"
@rollback="e => rollback('value',e)"
/>
<v-divider
class="mt-3"
/>
<v-row justify="center">
<v-btn
color="accent"
class="ma-5 black--text"
small
@click="setFunction(1)"
>
<v-icon>colorize</v-icon>
Eye drop
</v-btn>
<v-btn
color="accent"
class="ma-5 black--text"
small
@click="setFunction(2)"
>
<v-icon>add</v-icon>
Expand Selection
</v-btn>
<v-btn
color="accent"
class="ma-5 black--text"
small
@click="setFunction(3)"
>
<v-icon>remove</v-icon>
Shrink Selection
</v-btn>
</v-row>
<v-divider />
<CVswitch
v-model="erode"
name="Erode"
@input="handlePipelineData('erode')"
@rollback="e => rollback('erode',e)"
/>
<CVswitch
v-model="dilate"
name="Dilate"
@input="handlePipelineData('dilate')"
@rollback="e => rollback('dilate',e)"
/>
</div>
</template>
<script>
@@ -184,8 +183,4 @@
}
}
</script>
<style lang="" scoped>
</style>
</script>