mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-03 03:01:40 +00:00
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:
committed by
GitHub
parent
0b98dc3c9f
commit
19b57235fe
@@ -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
|
||||
|
||||
@@ -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')"
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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]});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) }} m</td>
|
||||
<td>{{ parseFloat(value.pose.y).toFixed(2) }} m</td>
|
||||
<td>{{ parseFloat(value.pose.rot).toFixed(2) }}°</td>
|
||||
<td>{{ parseFloat(value.pose.rotation).toFixed(2) }}°</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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user