mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-06 03:31:41 +00:00
UI Rebrand (#1)
* Rebrand UI and increase responsiveness * Fix typo in PipelineViews and SettingsViews directory name * Inset FPS indicator in stream preview * Rebrand favicon
This commit is contained in:
committed by
GitHub
parent
3e8f3736af
commit
895edb988a
206
photon-client/src/views/PipelineViews/3D.vue
Normal file
206
photon-client/src/views/PipelineViews/3D.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
align="center"
|
||||
justify="start"
|
||||
dense
|
||||
>
|
||||
<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>
|
||||
<CVslider
|
||||
v-model="value.accuracy"
|
||||
name="Contour simplification"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('accuracy')"
|
||||
@rollback="e=> rollback('accuracy',e)"
|
||||
/>
|
||||
<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
|
||||
:color="snackbar.color"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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
|
||||
},
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
is3D: false,
|
||||
selectedModel: null,
|
||||
FRCtargets: null,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: ""
|
||||
},
|
||||
snack: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
targets: {
|
||||
get() {
|
||||
return this.$store.state.point.targets;
|
||||
}
|
||||
},
|
||||
horizontalFOV: {
|
||||
get() {
|
||||
let index = this.$store.state.cameraSettings.resolution;
|
||||
let FOV = this.$store.state.cameraSettings.fov;
|
||||
let resolution = this.$store.state.resolutionList[index];
|
||||
let diagonalView = FOV * (Math.PI / 180);
|
||||
let diagonalAspect = Math.hypot(resolution.width, resolution.height);
|
||||
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
|
||||
}
|
||||
},
|
||||
allow3D: {
|
||||
get() {
|
||||
let currentRes = this.$store.state.resolutionList[this.$store.state.pipeline.videoModeIndex];
|
||||
for (let res of this.$store.state.cameraSettings.calibration) {
|
||||
if (currentRes.width === res.width && currentRes.height === res.height) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let tmp = [];
|
||||
for (let t in FRCtargetsConfig) {
|
||||
if (FRCtargetsConfig.hasOwnProperty(t)) {
|
||||
tmp.push({name: t, data: FRCtargetsConfig[t]})
|
||||
}
|
||||
}
|
||||
this.FRCtargets = tmp;
|
||||
},
|
||||
methods: {
|
||||
readFile(event) {
|
||||
let file = event.target.files[0];
|
||||
Papa.parse(file, {
|
||||
complete: this.onParse,
|
||||
skipEmptyLines: true
|
||||
});
|
||||
},
|
||||
onParse(result) {
|
||||
if (result.data.length > 0) {
|
||||
|
||||
|
||||
let data = [];
|
||||
for (let item of result.data) {
|
||||
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"
|
||||
};
|
||||
this.snack = true;
|
||||
return;
|
||||
}
|
||||
data.push(tmp);
|
||||
}
|
||||
this.uploadModel(data);
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error: cvs did not contain any data"
|
||||
};
|
||||
this.snack = true;
|
||||
}
|
||||
},
|
||||
uploadPremade() {
|
||||
this.uploadModel(this.selectedModel);
|
||||
},
|
||||
uploadModel(model) {
|
||||
this.axios.post("http://" + this.$address + "/api/vision/pnpModel", model).then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "File uploaded successfully"
|
||||
};
|
||||
this.snack = true;
|
||||
}).catch(() => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred"
|
||||
};
|
||||
this.snack = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.miniMapClass {
|
||||
width: 50% !important;
|
||||
height: 50% !important;
|
||||
}
|
||||
</style>
|
||||
84
photon-client/src/views/PipelineViews/ContoursTab.vue
Normal file
84
photon-client/src/views/PipelineViews/ContoursTab.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<CVrangeSlider
|
||||
v-model="value.area"
|
||||
name="Area"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.1"
|
||||
@input="handleData('area')"
|
||||
@rollback="e=> rollback('area',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="value.ratio"
|
||||
name="Ratio (W/H)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.1"
|
||||
@input="handleData('ratio')"
|
||||
@rollback="e=> rollback('ratio',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="value.extent"
|
||||
name="Extent"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('extent')"
|
||||
@rollback="e=> rollback('extent',e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="value.speckle"
|
||||
name="Speckle Rejection"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('speckle')"
|
||||
@rollback="e=> rollback('speckle',e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.targetGroup"
|
||||
name="Target Group"
|
||||
:list="['Single','Dual']"
|
||||
@input="handleData('targetGroup')"
|
||||
@rollback="e=> rollback('targetGroup',e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.targetIntersection"
|
||||
name="Target Intersection"
|
||||
:list="['None','Up','Down','Left','Right']"
|
||||
:disabled="isDisabled"
|
||||
@input="handleData('targetIntersection')"
|
||||
@rollback="e=> rollback('targetIntersection',e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVrangeSlider from '../../components/common/cv-range-slider'
|
||||
import CVselect from '../../components/common/cv-select'
|
||||
import CVslider from '../../components/common/cv-slider'
|
||||
|
||||
export default {
|
||||
name: 'Contours',
|
||||
components: {
|
||||
CVrangeSlider,
|
||||
CVselect,
|
||||
CVslider
|
||||
},
|
||||
props: ['value'],
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
isDisabled() {
|
||||
return this.value.targetGroup === 0;
|
||||
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="" scoped>
|
||||
|
||||
</style>
|
||||
95
photon-client/src/views/PipelineViews/InputTab.vue
Normal file
95
photon-client/src/views/PipelineViews/InputTab.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div>
|
||||
<CVslider
|
||||
v-model="value.exposure"
|
||||
name="Exposure"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('exposure')"
|
||||
@rollback="e => rollback('exposure', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="value.brightness"
|
||||
name="Brightness"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('brightness')"
|
||||
@rollback="e => rollback('brightness', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="value.gain !== -1"
|
||||
v-model="value.gain"
|
||||
name="Gain"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="handleData('gain')"
|
||||
@rollback="e => rollback('gain', e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.rotationMode"
|
||||
name="Orientation"
|
||||
:list="['Normal','90° CW','180°','90° CCW']"
|
||||
@input="handleData('rotationMode')"
|
||||
@rollback="e => e => rollback('rotationMode',e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.videoModeIndex"
|
||||
name="Resolution"
|
||||
:list="resolutionList"
|
||||
@input="handleData('videoModeIndex')"
|
||||
@rollback="e => rollback('videoModeIndex', e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.streamDivisor"
|
||||
name="Stream Resolution"
|
||||
:list="streamResolutionList"
|
||||
@input="handleData('streamDivisor')"
|
||||
@rollback="e => rollback('streamDivisor', e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVslider from '../../components/common/cv-slider'
|
||||
import CVselect from '../../components/common/cv-select'
|
||||
|
||||
export default {
|
||||
name: 'Input',
|
||||
components: {
|
||||
CVslider,
|
||||
CVselect,
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
resolutionList: {
|
||||
get() {
|
||||
let tmp_list = [];
|
||||
for (let i of this.$store.state.resolutionList) {
|
||||
tmp_list.push(`${i['width']} X ${i['height']} at ${i['fps']} FPS, ${i['pixelFormat']}`)
|
||||
}
|
||||
return tmp_list;
|
||||
}
|
||||
},
|
||||
streamResolutionList: {
|
||||
get() {
|
||||
let cam_res = this.$store.state.resolutionList[this.value.videoModeIndex];
|
||||
let tmp_list = [];
|
||||
tmp_list.push(`${Math.floor(cam_res['width'])} X ${Math.floor(cam_res['height'])}`);
|
||||
for (let x = 2; x <= 6; x += 2) {
|
||||
tmp_list.push(`${Math.floor(cam_res['width'] / x)} X ${Math.floor(cam_res['height'] / x)}`);
|
||||
}
|
||||
return tmp_list;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
126
photon-client/src/views/PipelineViews/OutputTab.vue
Normal file
126
photon-client/src/views/PipelineViews/OutputTab.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<CVselect
|
||||
v-model="value.sortMode"
|
||||
name="Sort Mode"
|
||||
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
|
||||
@input="handleData('sortMode')"
|
||||
@rollback="rollback('sortMode',e)"
|
||||
/>
|
||||
|
||||
<CVselect
|
||||
v-model="value.targetRegion"
|
||||
name="Target Region"
|
||||
:list="['Center','Top','Bottom','Left','Right']"
|
||||
@input="handleData('targetRegion')"
|
||||
@rollback="e=> rollback('targetRegion',e)"
|
||||
/>
|
||||
|
||||
<CVselect
|
||||
v-model="value.targetOrientation"
|
||||
name="Target Orientation"
|
||||
:list="['Portrait', 'Landscape']"
|
||||
@input="handleData('targetOrientation')"
|
||||
@rollback="e=> rollback('targetOrientation',e)"
|
||||
/>
|
||||
|
||||
<CVswitch
|
||||
v-model="value.multiple"
|
||||
name="Output multiple"
|
||||
@input="handleData('multiple')"
|
||||
@rollback="e=> rollback('multiple',e)"
|
||||
/>
|
||||
<span>Calibrate:</span>
|
||||
<v-divider
|
||||
dark
|
||||
color="white"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="value.calibrationMode"
|
||||
name="Calibration Mode"
|
||||
:list="['None','Single point','Dual point']"
|
||||
@input="handleData('calibrationMode')"
|
||||
@rollback="e=> rollback('calibrationMode',e)"
|
||||
/>
|
||||
<component
|
||||
:is="selectedComponent"
|
||||
:raw-point="rawPoint"
|
||||
@update="doUpdate"
|
||||
@snackbar="showSnackbar"
|
||||
/>
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:timeout="3000"
|
||||
top
|
||||
color="error"
|
||||
>
|
||||
<span style="color:#000">{{ snackbarText }}</span>
|
||||
<v-btn
|
||||
color="black"
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVselect from '../../components/common/cv-select'
|
||||
import CVswitch from '../../components/common/cv-switch'
|
||||
import DualCalibration from "../../components/pipeline/OutputTab/DualCalibration";
|
||||
import SingleCalibration from "../../components/pipeline/OutputTab/SingleCalibration";
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Output',
|
||||
components: {
|
||||
CVselect,
|
||||
CVswitch,
|
||||
SingleCalibration,
|
||||
DualCalibration,
|
||||
|
||||
},
|
||||
props: ['value'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
snackbar: false,
|
||||
snackbarText: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedComponent: {
|
||||
get() {
|
||||
switch (this.value.calibrationMode) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return "SingleCalibration";
|
||||
case 2:
|
||||
return "DualCalibration"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
},
|
||||
rawPoint: {
|
||||
get() {
|
||||
return this.$store.state.point.rawPoint;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doUpdate() {
|
||||
this.$emit('update')
|
||||
},
|
||||
showSnackbar(message) {
|
||||
this.snackbarText = message;
|
||||
this.snackbar = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
164
photon-client/src/views/PipelineViews/ThresholdTab.vue
Normal file
164
photon-client/src/views/PipelineViews/ThresholdTab.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div>
|
||||
<CVrangeSlider
|
||||
v-model="value.hue"
|
||||
name="Hue"
|
||||
:min="0"
|
||||
:max="180"
|
||||
@input="handleData('hue')"
|
||||
@rollback="e => rollback('hue',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="value.saturation"
|
||||
name="Saturation"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handleData('saturation')"
|
||||
@rollback="e => rollback('saturation',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="value.value"
|
||||
name="Value"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handleData('value')"
|
||||
@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="value.erode"
|
||||
name="Erode"
|
||||
@input="handleData('erode')"
|
||||
@rollback="e => rollback('erode',e)"
|
||||
/>
|
||||
<CVswitch
|
||||
v-model="value.dilate"
|
||||
name="Dilate"
|
||||
@input="handleData('dilate')"
|
||||
@rollback="e => rollback('dilate',e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVrangeSlider from '../../components/common/cv-range-slider'
|
||||
import CVswitch from '../../components/common/cv-switch'
|
||||
|
||||
export default {
|
||||
name: 'Threshold',
|
||||
components: {
|
||||
CVrangeSlider,
|
||||
CVswitch
|
||||
},
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
currentFunction: undefined,
|
||||
colorPicker: undefined,
|
||||
currentBinaryState: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pipeline: {
|
||||
get() {
|
||||
return this.$store.state.pipeline;
|
||||
}
|
||||
},
|
||||
driverState: {
|
||||
get() {
|
||||
return this.$store.state.driverMode;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("driverMode", val);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
const self = this;
|
||||
this.colorPicker = require('../../plugins/ColorPicker').default;
|
||||
this.$nextTick(() => {
|
||||
self.colorPicker.initColorPicker();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onClick(event) {
|
||||
if (this.currentFunction !== undefined) {
|
||||
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]]]);
|
||||
this.currentFunction = undefined;
|
||||
this.value.hue = [hsvArray[0][0], hsvArray[1][0]];
|
||||
this.value.saturation = [hsvArray[0][1], hsvArray[1][1]];
|
||||
this.value.value = [hsvArray[0][2], hsvArray[1][2]];
|
||||
this.value.isBinary = this.currentBinaryState;
|
||||
let msg = this.$msgPack.encode({
|
||||
'hue': this.value.hue,
|
||||
'saturation': this.value.saturation,
|
||||
'value': this.value.value,
|
||||
'isBinary': this.value.isBinary
|
||||
});
|
||||
this.$socket.send(msg);
|
||||
this.$emit('update');
|
||||
}
|
||||
},
|
||||
setFunction(index) {
|
||||
this.currentBinaryState = this.value.isBinary;
|
||||
if (this.currentBinaryState === true) {
|
||||
this.value.isBinary = false;
|
||||
this.handleData('isBinary')
|
||||
}
|
||||
switch (index) {
|
||||
case 0:
|
||||
this.currentFunction = undefined;
|
||||
break;
|
||||
case 1:
|
||||
this.currentFunction = this.colorPicker.eyeDrop;
|
||||
break;
|
||||
case 2:
|
||||
this.currentFunction = this.colorPicker.expand;
|
||||
break;
|
||||
case 3:
|
||||
this.currentFunction = this.colorPicker.shrink;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="" scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user