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,6 +1,16 @@
<template>
<div style="overflow:hidden;height:100%;width:100%" height="100%" width="100%">
<iframe src="docs/index.html" frameborder="0" style="overflow:hidden;height:100%;width:100%" height="100%" width="100%"></iframe>
<div
style="overflow:hidden;height:100%;width:100%"
height="100%"
width="100%"
>
<iframe
src="docs/index.html"
frameborder="0"
style="overflow:hidden;height:100%;width:100%"
height="100%"
width="100%"
/>
</div>
</template>

View File

@@ -1,128 +1,173 @@
<template>
<div>
<camera-and-pipeline-select />
<v-row no-gutters>
<!-- vision tabs -->
<v-col
cols="12"
sm="12"
md="7"
xl="6"
class="colsClass pr-8"
<v-container
class="pa-3"
fluid
>
<v-row
no-gutters
align="center"
justify="center"
>
<v-tabs
v-if="($store.getters.currentPipelineIndex + 1) !== 0"
v-model="selectedTab"
fixed-tabs
background-color="#232c37"
dark
height="48"
slider-color="#ffd843"
<v-col
cols="12"
:class="['pb-3 ', $store.getters.isDriverMode ? '' : 'pr-lg-3']"
:lg="$store.getters.isDriverMode ? 12 : 8"
align-self="stretch"
>
<v-tab>Input</v-tab>
<v-tab>Threshold</v-tab>
<v-tab>Contours</v-tab>
<v-tab>Output</v-tab>
<v-tab>3D</v-tab>
</v-tabs>
<div
v-else
style="height: 48px"
/>
<div style="padding-left:30px">
<keep-alive>
<!-- vision component -->
<component
:is="selectedComponent"
ref="component"
v-model="$store.getters.pipeline"
@update="$emit('save')"
/>
</keep-alive>
</div>
</v-col>
<v-col
cols="12"
sm="12"
md="5"
xl="6"
class="colsClass"
>
<div>
<!-- camera image tabs -->
<v-tabs
v-if="($store.getters.currentPipelineIndex + 1) !== 0"
v-model="outputShowThresholded"
background-color="#232c37"
<v-card
color="primary"
height="100%"
dark
height="48"
slider-color="#ffd843"
centered
style="padding-bottom:10px"
@change="handleTruthyPipelineData('outputShowThresholded')"
>
<v-tab>Normal</v-tab>
<v-tab>Threshold</v-tab>
</v-tabs>
<div
v-else
style="height: 58px"
/>
<!-- camera image stream -->
<div class="videoClass">
<v-row align="center">
<div style="position: relative; width: 100%; height: 100%;">
<cvImage
:address="$store.getters.streamAddress"
:scale="75"
@click="onImageClick"
/>
<span style=" position: absolute; top: 0.2%; left: 13%;">FPS: {{ parseFloat(fps).toFixed(2) }}</span>
<span style=" position: absolute; top: 0.2%; right: 13%;">Latency: {{ parseFloat(latency).toFixed(2) }}ms</span>
</div>
</v-row>
<v-row align="center">
<v-simple-table
style="text-align: center;background-color: transparent; display: block;margin: auto"
dense
dark
<v-card-title
class="pb-0 mb-0 pl-4 pt-1"
style="height: 10%;"
>
Cameras
</v-card-title>
<v-row
align="center"
style="height: 90%;"
>
<v-col
v-for="idx in (selectedOutputs instanceof Array ? selectedOutputs : [selectedOutputs])"
:key="idx"
cols="12"
:md="selectedOutputs.length === 1 ? 12 : Math.floor(12 / selectedOutputs.length)"
>
<template v-slot:default>
<thead>
<tr>
<th class="text-center">
Target
</th>
<th class="text-center">
Pitch
</th>
<th class="text-center">
Yaw
</th>
<th class="text-center">
Area
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.getters.currentPipelineResults.targets"
:key="index"
>
<td>{{ index }}</td>
<td>{{ parseFloat(value['pitch']).toFixed(2) }}</td>
<td>{{ parseFloat(value['yaw']).toFixed(2) }}</td>
<td>{{ parseFloat(value['area']).toFixed(2) }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
<div style="position: relative; width: 100%; height: 100%;">
<cvImage
:address="$store.getters.streamAddress[idx]"
scale="100"
max-height="300px"
max-height-md="320px"
max-height-xl="450px"
:alt="'Stream' + idx"
@click="onImageClick"
/>
<span style="position: absolute; top: 2%; left: 2%; font-size: 28px; -webkit-text-stroke: 1px black;">{{ parseFloat(fps).toFixed(2) }}</span>
</div>
</v-col>
</v-row>
</div>
</div>
</v-col>
</v-row>
</v-card>
</v-col>
<v-col
cols="12"
class="pb-3"
:lg="$store.getters.isDriverMode ? 12 : 4"
align-self="stretch"
>
<v-card
color="primary"
>
<camera-and-pipeline-select />
</v-card>
<v-card
v-if="!$store.getters.isDriverMode"
class="mt-3"
color="primary"
>
<v-row
align="center"
class="pl-3 pr-3"
>
<v-col lg="12">
<p style="color: white;">
Processing mode:
</p>
<v-btn-toggle
v-model="processingMode"
mandatory
dark
class="fill"
>
<v-btn color="secondary">
<v-icon>mdi-cube-outline</v-icon>
<span>3D</span>
</v-btn>
<v-btn color="secondary">
<v-icon>mdi-crop-square</v-icon>
<span>2D</span>
</v-btn>
</v-btn-toggle>
</v-col>
<v-col lg="12">
<p style="color: white;">
Stream display:
</p>
<v-btn-toggle
v-model="selectedOutputs"
:multiple="$vuetify.breakpoint.mdAndUp"
mandatory
dark
class="fill"
>
<v-btn
color="secondary"
class="fill"
>
<v-icon>mdi-palette</v-icon>
<span>Normal</span>
</v-btn>
<v-btn
color="secondary"
class="fill"
>
<v-icon>mdi-compare</v-icon>
<span>Threshold</span>
</v-btn>
</v-btn-toggle>
</v-col>
</v-row>
</v-card>
</v-col>
</v-row>
<v-row no-gutters>
<v-col
v-for="(tabs, idx) in tabGroups"
:key="idx"
:cols="Math.floor(12 / tabGroups.length)"
:class="idx != tabGroups.length - 1 ? 'pr-3' : ''"
align-self="stretch"
>
<v-card
color="primary"
height="100%"
class="pr-4 pl-4"
>
<v-tabs
v-if="!$store.getters.isDriverMode"
v-model="selectedTabs[idx]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
>
<v-tab
v-for="(tab, i) in tabs.filter(it => it.name !== '3D' || is3D)"
:key="i"
>
{{ tab.name }}
</v-tab>
</v-tabs>
<div class="pl-4 pr-4 pt-2">
<keep-alive>
<!-- vision component -->
<component
:is="(tabs[selectedTabs[idx]] || tabs[0]).component"
ref="component"
v-model="$store.getters.pipeline"
:is3d="is3D"
@update="$emit('save')"
/>
</keep-alive>
</div>
</v-card>
</v-col>
</v-row>
</v-container>
<!-- snack bar -->
<v-snackbar
v-model="snackbar"
@@ -144,12 +189,13 @@
<script>
import CameraAndPipelineSelect from "../components/pipeline/CameraAndPipelineSelect";
import cvImage from '../components/common/cv-image'
import InputTab from './PipelineViews/InputTab'
import ThresholdTab from './PipelineViews/ThresholdTab'
import ContoursTab from './PipelineViews/ContoursTab'
import OutputTab from './PipelineViews/OutputTab'
import pnpTab from './PipelineViews/3D'
import cvImage from '../components/common/cv-image';
import InputTab from './PipelineViews/InputTab';
import ThresholdTab from './PipelineViews/ThresholdTab';
import ContoursTab from './PipelineViews/ContoursTab';
import OutputTab from './PipelineViews/OutputTab';
import TargetsTab from "./PipelineViews/TargetsTab";
import PnPTab from './PipelineViews/PnPTab';
export default {
name: 'CameraTab',
@@ -160,26 +206,107 @@
ThresholdTab,
ContoursTab,
OutputTab,
pnpTab,
TargetsTab,
PnPTab,
},
data() {
return {
selectedTab: 0,
selectedTabs: [0, 0, 0, 0],
snackbar: false,
is3D: false,
}
},
computed: {
outputShowThresholded: {
tabGroups: {
get() {
return this.$store.getters.currentPipelineSettings.outputShowThresholded ? 1 : 0;
},
set(value) {
this.$store.commit('mutatePipeline', {'outputShowThresholded': !!value});
let tabs = {
input: {
name: "Input",
component: "InputTab",
},
threshold: {
name: "Threshold",
component: "ThresholdTab",
},
contours: {
name: "Contours",
component: "ContoursTab",
},
output: {
name: "Output",
component: "OutputTab",
},
targets: {
name: "Target Info",
component: "TargetsTab",
},
pnp: {
name: "3D",
component: "PnPTab",
}
};
// 2D array of tab names and component names; each sub-array is a separate tab group
let ret = [];
if (this.$vuetify.breakpoint.smAndDown || !this.$store.state.compactMode || this.$store.getters.isDriverMode) {
// One big tab group with all the tabs
ret[0] = Object.values(tabs);
} else if (this.$vuetify.breakpoint.mdAndDown) {
// Two tab groups, one with "input, threshold, contours, output" and the other with "target info, 3D"
ret[0] = [tabs.input, tabs.threshold, tabs.contours, tabs.output];
ret[1] = [tabs.targets, tabs.pnp];
} else if (this.$vuetify.breakpoint.lgAndDown) {
// Three tab groups, one with "input", one with "threshold, contours, output", and the other with "target info, 3D"
ret[0] = [tabs.input];
ret[1] = [tabs.threshold, tabs.contours, tabs.output];
ret[2] = [tabs.targets, tabs.pnp];
} else if (this.$vuetify.breakpoint.xl) {
// Three tab groups, one with "input", one with "threshold, contours", and the other with "output, target info, 3D"
ret[0] = [tabs.input];
ret[1] = [tabs.threshold];
ret[2] = [tabs.contours, tabs.output]
ret[3] = [tabs.targets, tabs.pnp];
}
return ret;
}
},
selectedComponent: {
processingMode: {
get() {
return (this.$store.getters.currentPipelineIndex + 1) === 0 ? "InputTab" : ["InputTab", "ThresholdTab", "ContoursTab", "OutputTab", "pnpTab"][this.selectedTab];
return this.is3D ? 0 : 1;
},
set(value) {
this.is3D = value === 0;
}
},
selectedOutputs: {
// All this logic exists to deal with the reality that the output select buttons sometimes need an array and sometimes need a number (depending on whether or not they're exclusive)
get() {
// We switch the selector to single-select only on sm-and-down size devices, so we have to return a Number instead of an Array in that state
let ret = 0;
if (!this.$store.getters.isDriverMode) {
ret = this.$store.state.selectedOutputs || [0];
}
if (this.$vuetify.breakpoint.mdAndUp) {
return ret;
} else {
return ret[0] || 0;
}
},
set(value) {
let valToCommit = [0];
if (value instanceof Array) {
// Value is already an array, we don't need to do anything
value.sort(); // Sort for visual consistency
valToCommit = value;
} else if (value) {
// Value is assumed to be a number, so we wrap it into an array
valToCommit = [value];
}
this.$store.commit("selectedOutputs", valToCommit);
// TODO: Currently the backend just sends both streams regardless of the selected outputs value, so we don't need to send anything
// this.handlePipelineUpdate('selectedOutputs', valToCommit);
}
},
fps: {
@@ -204,6 +331,16 @@
</script>
<style scoped>
.v-btn-toggle.fill {
width: 100%;
height: 100%;
}
.v-btn-toggle.fill > .v-btn {
width: 50%;
height: 100%;
}
.colsClass {
padding: 0 !important;
}
@@ -216,5 +353,4 @@
width: 80px;
text-align: center;
}
</style>

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>