UI bug fixes and feature refinements (#59)

* Rework settings page; touch up contour, output, and 3D tabs; font sizing

No stream placeholder; driver mode refined; cameras page

Make settings snackbar work

Lint fix

Fix settings page padding

Actually hide settings fields if unsupported

* Make toggle buttons less confusing; fix driver toggle; form validation

* Make eyedropper work and make input/select styling more consistent

* Fix color picker and tabbing bugs

* Set up camera and settings pages to talk to the backend

* Add auto reconnect

* Add lots of tooltips and improve related thematic consistency

* Only show output stream while color picking

* Unbreak robot offset

* Increase tooltip delay and refactor tooltip label into a component

* Remove toggle button switching behavior

* Fix PnP tab and add a flag to disable FOV configuration

* Move FPS indicator

* Make GPU acceleration status use one value in the store

* Only allow IPv4 static IPs and remove accidentally committed index
This commit is contained in:
Declan Freeman-Gleason
2020-07-31 13:50:50 -07:00
committed by GitHub
parent 0b98dc3c9f
commit 19b57235fe
34 changed files with 1099 additions and 566 deletions

View File

@@ -12,6 +12,7 @@
<CVrangeSlider
v-model="contourRatio"
name="Ratio (W/H)"
tooltip="Min and max ratio between the width and height of a contour's bounding rectangle"
min="0"
max="100"
step="0.1"
@@ -21,6 +22,7 @@
<CVrangeSlider
v-model="contourFullness"
name="Fullness"
tooltip="Min and max ratio between a contour's area and its bounding rectangle"
min="0"
max="100"
@input="handlePipelineData('contourFullness')"
@@ -29,6 +31,7 @@
<CVslider
v-model="contourSpecklePercentage"
name="Speckle Rejection"
tooltip="Rejects contours whose average area is less than the given percentage of the average area of all the other contours"
min="0"
max="100"
:slider-cols="largeBox"
@@ -37,7 +40,8 @@
/>
<CVselect
v-model="contourGroupingMode"
name="Target Group"
name="Target Grouping"
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
:select-cols="largeBox"
:list="['Single','Dual']"
@input="handlePipelineData('targetGroup')"
@@ -46,12 +50,22 @@
<CVselect
v-model="contourIntersection"
name="Target Intersection"
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
:select-cols="largeBox"
:list="['None','Up','Down','Left','Right']"
:disabled="contourGroupingMode === 0"
@input="handlePipelineData('contourIntersection')"
@rollback="e=> rollback('contourIntersection',e)"
/>
<CVselect
v-model="contourSortMode"
name="Target Sort"
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
:select-cols="largeBox"
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
@input="handlePipelineData('contourSortMode')"
@rollback="e => rollback('contourSortMode', e)"
/>
</div>
</template>
@@ -122,6 +136,14 @@
this.$store.commit("mutatePipeline", {"contourGroupingMode": val});
}
},
contourSortMode: {
get() {
return this.$store.getters.currentPipelineSettings.contourSortMode
},
set(val) {
this.$store.commit("mutatePipeline", {"contourSortMode": val});
}
},
contourIntersection: {
get() {
return this.$store.getters.currentPipelineSettings.contourIntersection

View File

@@ -5,6 +5,7 @@
name="Exposure"
min="0"
max="100"
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects brightness"
:slider-cols="largeBox"
@input="handlePipelineData('cameraExposure')"
@rollback="e => rollback('cameraExposure', e)"
@@ -14,6 +15,7 @@
name="Brightness"
min="0"
max="100"
tooltip="Controls camera postprocessing that brightens or darkens the image uniformly"
:slider-cols="largeBox"
@input="handlePipelineData('cameraBrightness')"
@rollback="e => rollback('cameraBrightness', e)"
@@ -24,6 +26,7 @@
name="Gain"
min="0"
max="100"
tooltip="Controls automatic white balance gain, which affects how the camera captures colors in different conditions"
:slider-cols="largeBox"
@input="handlePipelineData('cameraGain')"
@rollback="e => rollback('cameraGain', e)"
@@ -31,6 +34,7 @@
<CVselect
v-model="inputImageRotationMode"
name="Orientation"
tooltip="Rotates the camera stream"
:list="['Normal','90° CW','180°','90° CCW']"
:select-cols="largeBox"
@input="handlePipelineData('inputImageRotationMode')"
@@ -39,6 +43,7 @@
<CVselect
v-model="cameraVideoModeIndex"
name="Resolution"
tooltip="Resolution and FPS the camera should directly capture at"
:list="resolutionList"
:select-cols="largeBox"
@input="handlePipelineData('cameraVideoModeIndex')"
@@ -47,6 +52,7 @@
<CVselect
v-model="streamingFrameDivisor"
name="Stream Resolution"
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
:list="streamResolutionList"
:select-cols="largeBox"
@input="handlePipelineData('streamingFrameDivisor')"

View File

@@ -1,18 +1,12 @@
<template>
<div>
<span>Contour Sorting</span>
<span>Target Manipulation</span>
<v-divider class="mt-2" />
<CVselect
v-model="contourSortMode"
name="Sort Mode"
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
@input="handlePipelineData('contourSortMode')"
@rollback="e => rollback('contourSortMode', e)"
/>
<CVselect
v-model="contourTargetOffsetPointEdge"
name="Target Offset Point"
tooltip="Changes where the 'center' of the target is (used for calculating e.g. pitch and yaw)"
:list="['Center','Top','Bottom','Left','Right']"
@input="handlePipelineData('contourTargetOffsetPointEdge')"
@rollback="e=> rollback('contourTargetOffsetPointEdge', e)"
@@ -21,6 +15,7 @@
<CVselect
v-model="contourTargetOrientation"
name="Target Orientation"
tooltip="Used to determine how to calculate target landmarks (e.g. the top, left, or bottom of the target)"
:list="['Portrait', 'Landscape']"
@input="handlePipelineData('contourTargetOrientation')"
@rollback="e=> rollback('contourTargetOrientation', e)"
@@ -29,7 +24,9 @@
<CVswitch
v-model="outputShowMultipleTargets"
name="Show Multiple Targets"
tooltip="If enabled, up to five targets will be displayed and sent to user code"
class="mb-4"
text-cols="3"
@input="handlePipelineData('outputShowMultipleTargets')"
@rollback="e=> rollback('outputShowMultipleTargets', e)"
@@ -39,6 +36,7 @@
<CVselect
v-model="offsetRobotOffsetMode"
name="Robot Offset Mode"
tooltip="Used to add an arbitrary offset to the location of the targeting crosshair"
:list="['None','Single Point','Dual Point']"
@input="handlePipelineData('offsetRobotOffsetMode')"
@rollback="e=> rollback('offsetRobotOffsetMode',e)"
@@ -93,16 +91,6 @@
}
},
computed: {
contourSortMode: {
get() {
return this.$store.getters.currentPipelineSettings.contourSortMode
},
set(val) {
this.$store.commit("mutatePipeline", {"contourSortMode": val});
}
},
contourTargetOffsetPointEdge: {
get() {
return this.$store.getters.currentPipelineSettings.contourTargetOffsetPointEdge
@@ -138,13 +126,13 @@
selectedComponent: {
get() {
switch (this.value.calibrationMode) {
switch (this.offsetRobotOffsetMode) {
case 0:
return "";
return null;
case 1:
return "Single Point";
return SingleCalibration;
case 2:
return "Dual Point"
return DualCalibration;
}
return ""
}

View File

@@ -1,6 +1,6 @@
<template>
<div>
<!-- Special hidden upload input that gets 'clicked' when the user selects the right dropdown item' -->
<!-- Special hidden upload input that gets 'clicked' when the user selects the right dropdown item -->
<input
ref="file"
type="file"
@@ -21,7 +21,6 @@
item-value="data"
@change="onModelSelect"
/>
<v-divider />
<CVslider
v-model="value.accuracy"
class="pt-2"
@@ -33,7 +32,6 @@
@input="handleData('accuracy')"
@rollback="e => rollback('accuracy', e)"
/>
<v-divider class="pb-2" />
<mini-map
class="miniMapClass"
:targets="targets"
@@ -77,7 +75,7 @@
computed: {
targets: {
get() {
return "FIXME"; // TODO fix
return this.$store.getters.currentPipelineResults.targets;
}
},
horizontalFOV: {
@@ -100,7 +98,7 @@
let tmp = [];
for (let t in FRCtargetsConfig) {
if (FRCtargetsConfig.hasOwnProperty(t)) {
tmp.push({name: t, data: FRCtargetsConfig[t]})
tmp.push({name: t, data: FRCtargetsConfig[t]});
}
}

View File

@@ -13,12 +13,12 @@
dark
>
<template v-slot:default>
<thead style="font-size: 20px;">
<thead style="font-size: 1.25rem;">
<tr>
<th class="text-center">
Target
</th>
<template v-if="!is3D">
<template v-if="!$store.getters.currentPipelineSettings.is3D">
<th class="text-center">
Pitch
</th>
@@ -32,7 +32,7 @@
<th class="text-center">
Area
</th>
<template v-if="is3D">
<template v-if="$store.getters.currentPipelineSettings.is3D">
<th class="text-center">
X
</th>
@@ -51,17 +51,17 @@
:key="index"
>
<td>{{ index }}</td>
<template v-if="!is3D">
<template v-if="!$store.getters.currentPipelineSettings.is3D">
<td>{{ parseFloat(value.pitch).toFixed(2) }}</td>
<td>{{ parseFloat(value.yaw).toFixed(2) }}</td>
<td>{{ parseFloat(value.skew).toFixed(2) }}</td>
</template>
<td>{{ parseFloat(value.area).toFixed(2) }}</td>
<template v-if="is3D">
<template v-if="$store.getters.currentPipelineSettings.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>
<td>{{ parseFloat(value.pose.rotation).toFixed(2) }}&deg;</td>
</template>
</tr>
</tbody>
@@ -74,9 +74,6 @@
<script>
export default {
name: "TargetsTab",
props: {
is3D: Boolean,
}
}
</script>
@@ -97,6 +94,10 @@
font-size: 1rem !important;
}
.v-data-table td {
font-family: monospace !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;

View File

@@ -3,6 +3,7 @@
<CVrangeSlider
v-model="hsvHue"
name="Hue"
tooltip="Describes color"
:min="0"
:max="180"
@input="handlePipelineData('hsvHue')"
@@ -11,6 +12,7 @@
<CVrangeSlider
v-model="hsvSaturation"
name="Saturation"
tooltip="Describes colorfulness; the smaller this value the 'whiter' the color becomes"
:min="0"
:max="255"
@input="handlePipelineData('hsvSaturation')"
@@ -19,53 +21,81 @@
<CVrangeSlider
v-model="hsvValue"
name="Value"
tooltip="Describes lightness; the smaller this value the 'blacker' the color becomes"
:min="0"
:max="255"
@input="handlePipelineData('hsvValue')"
@rollback="e => rollback('value',e)"
/>
<div class="pt-3 white--text">
Color Picker
</div>
<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
justify="center"
class="mt-3 mb-3"
>
<template v-if="!$store.state.colorPicking">
<v-btn
color="accent"
class="ma-2 black--text"
small
@click="setFunction(3)"
>
<v-icon left>
mdi-minus
</v-icon>
Shrink Range
</v-btn>
<v-btn
color="accent"
class="ma-2 black--text"
small
@click="setFunction(1)"
>
<v-icon left>
mdi-plus-minus
</v-icon>
Set To Average
</v-btn>
<v-btn
color="accent"
class="ma-2 black--text"
small
@click="setFunction(2)"
>
<v-icon left>
mdi-plus
</v-icon>
Expand Range
</v-btn>
</template>
<template v-else>
<v-btn
color="accent"
class="ma-2 black--text"
style="width: 30%;"
small
@click="setFunction(0)"
>
Cancel
</v-btn>
</template>
</v-row>
<v-divider />
<v-divider class="mb-3" />
<CVswitch
v-model="erode"
name="Erode"
tooltip="Removes pixels around the edges of white areas in the thresholded image"
@input="handlePipelineData('erode')"
@rollback="e => rollback('erode',e)"
/>
<CVswitch
v-model="dilate"
name="Dilate"
tooltip="Adds pixels around the edges of white areas in the thresholded image"
@input="handlePipelineData('dilate')"
@rollback="e => rollback('dilate',e)"
/>
@@ -143,14 +173,27 @@
methods: {
onClick(event) {
if (this.currentFunction !== undefined) {
this.colorPicker.initColorPicker();
let s = this.$store.getters.currentPipelineSettings;
let hsvArray = this.colorPicker.colorPickerClick(event, this.currentFunction,
[[this.value.hue[0], this.value.saturation[0], this.value.value[0]], [this.value.hue[1], this.value.saturation[1], this.value.value[1]]]);
[
[s.hsvHue[0], s.hsvSaturation[0], s.hsvValue[0]],
[s.hsvHue[1], s.hsvSaturation[1], s.hsvValue[1]]
].map(hsv => hsv.map(it => it || 0)));
// That `map` calls are to make sure that we don't let any undefined/null values slip in
this.currentFunction = undefined;
this.$store.state.colorPicking = false;
s.hsvHue = [hsvArray[0][0], hsvArray[1][0]];
s.hsvSaturation = [hsvArray[0][1], hsvArray[1][1]];
s.hsvValue = [hsvArray[0][2], hsvArray[1][2]];
let msg = this.$msgPack.encode({
"changePipelineSetting": {
'hsvHue': [hsvArray[0][0], hsvArray[1][0]],
'hsvSaturation': [hsvArray[0][1], hsvArray[1][1]],
'hsvValue': [hsvArray[0][2], hsvArray[1][2]],
'hsvHue': s.hsvHue,
'hsvSaturation': s.hsvSaturation,
'hsvValue': s.hsvValue,
'outputShowThresholded': this.showThresholdState,
'cameraIndex': this.$store.state.currentCameraIndex
}
@@ -160,15 +203,11 @@
}
},
setFunction(index) {
this.showThresholdState = this.value.outputShowThresholded;
if (this.showThresholdState === true) {
this.value.outputShowThresholded = false;
this.handlePipelineData('outputShowThresholded')
}
switch (index) {
case 0:
this.currentFunction = undefined;
break;
this.$store.state.colorPicking = false;
return;
case 1:
this.currentFunction = this.colorPicker.eyeDrop;
break;
@@ -179,6 +218,7 @@
this.currentFunction = this.colorPicker.shrink;
break;
}
this.$store.state.colorPicking = true;
}
}
}