Ui rework (#96)

* updated libs, folder rework

* started store modules added data handle mixin

* more store rework

* name refractor and component split

* bug fixes and code cleanup
This commit is contained in:
Ori agranat
2020-05-25 22:46:44 +03:00
committed by GitHub
parent 47c2f8cab0
commit 9141efa2ed
35 changed files with 1316 additions and 1192 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "chameleon-client",
"version": "0.1.0",
"version": "3.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -17,19 +17,19 @@
"vue-axios": "^2.1.5",
"vue-native-websocket": "^2.0.14",
"vue-router": "^3.1.6",
"vuetify": "^2.2.17",
"vuex": "^3.1.3"
"vuetify": "^2.2.26",
"vuex": "^3.3.0"
},
"devDependencies": {
"@mdi/font": "^4.9.95",
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-eslint": "^4.2.3",
"@vue/cli-service": "^4.2.3",
"@vue/cli-plugin-eslint": "^4.3.1",
"@vue/cli-service": "^4.3.1",
"babel-eslint": "^10.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"papaparse": "^5.1.1",
"sass": "^1.26.3",
"papaparse": "^5.2.0",
"sass": "^1.26.5",
"sass-loader": "^7.1.0",
"vue-cli-plugin-vuetify": "^0.6.3",
"vue-template-compiler": "^2.6.11",

View File

@@ -37,7 +37,7 @@
if (this.$store.state.hasOwnProperty(key)) {
this.$store.commit(key, value);
} else if (this.$store.state.pipeline.hasOwnProperty(key)) {
this.$store.commit('setPipeValues', {[key]: value});
this.$store.commit('mutatePipeline', {'key': key, 'value': value});
} else {
switch (key) {
default: {

View File

@@ -1,140 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg3713"
width="219"
height="230"
viewBox="0 0 219 230"
sodipodi:docname="robotIcon.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata3719">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3717">
<linearGradient
id="linearGradient937"
inkscape:collect="always">
<stop
id="stop933"
offset="0"
style="stop-color:#228a42;stop-opacity:1" />
<stop
id="stop935"
offset="1"
style="stop-color:#5cb34a;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient931"
inkscape:collect="always">
<stop
id="stop927"
offset="0"
style="stop-color:#228a42;stop-opacity:1" />
<stop
id="stop929"
offset="1"
style="stop-color:#5cb34a;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient925">
<stop
style="stop-color:#228a42;stop-opacity:1"
offset="0"
id="stop921" />
<stop
style="stop-color:#5cb34a;stop-opacity:1"
offset="1"
id="stop923" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient925"
id="linearGradient4820"
x1="108.25"
y1="186.25"
x2="109.5"
y2="21"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient937"
id="linearGradient4822"
x1="108.25"
y1="186.25"
x2="109.5"
y2="21"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient931"
id="linearGradient4824"
x1="108.25"
y1="186.25"
x2="109.5"
y2="21"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview3715"
showgrid="false"
inkscape:zoom="4"
inkscape:cx="110.62282"
inkscape:cy="130.60249"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg3713"
showguides="false"
inkscape:snap-page="false"
inkscape:snap-others="true"
inkscape:snap-object-midpoints="true" />
<path
style="opacity:1;fill:url(#linearGradient4824);fill-opacity:1;stroke:#000000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 167,156.45722 -41,-2 -14,-17 V 55.457215 Z"
id="path3725-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#linearGradient4822);stroke:#020000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1;opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 76.866,158.246 13,-0.496 19.567,11.61661 19.567,-11.39151 13,0.2709 -32.5675,19.39302 z"
id="path3742"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:url(#linearGradient4820);stroke:#020000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1;opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 51.864904,156.45722 41,-2 13.999996,-17 V 55.457209 Z"
id="path3725-6-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,28 @@
<template>
<img id="CameraStream" v-bind:style="styleObject" :src="address" @click="e => $emit('click', e)"
crossorigin="Anonymous" alt=""/>
</template>
<script>
export default {
name: "cv-image",
data: () => {
return {}
},
props: ['address', 'scale'],
computed: {
styleObject: {
get() {
return {
width: `${this.scale}%`,
height: `${this.scale}%`,
display: 'block',
margin: 'auto'
}
}
}
}
}
</script>

View File

@@ -0,0 +1,263 @@
<template>
<div>
<v-row align="center">
<v-col :cols="3" class="">
<div style="padding-left:30px">
<CVselect v-if="isCameraNameEdit === false" name="Camera" v-model="currentCameraIndex"
:list="$store.getters.cameraList"
@input="handleInput('currentCamera',currentCameraIndex)"/>
<CVinput v-else name="Camera" v-model="newCameraName" @Enter="saveCameraNameChange"
:errorMessage="checkCameraName"/>
</div>
</v-col>
<v-col :cols="1">
<CVicon color="#c5c5c5" v-if="isCameraNameEdit === false" :hover="true" text="edit"
@click="toCameraNameChange" tooltip="Edit camera name"/>
<div v-else>
<CVicon color="#c5c5c5" style="display: inline-block;" :hover="true" text="save"
@click="saveCameraNameChange" tooltip="Save Camera Name"/>
<CVicon color="error" style="display: inline-block;" :hover="true" text="close"
@click="discardCameraNameChange" tooltip="Discard Changes"/>
</div>
</v-col>
<v-col :cols="3" class="">
<CVselect name="Pipeline"
:list="['Driver Mode'].concat($store.getters.pipelineList)"
v-model="currentPipelineIndex"
@input="handleInput('currentPipeline',currentPipelineIndex - 1)"/>
</v-col>
<v-col :cols="1" class="" md="3" v-if="currentPipelineIndex !== 0">
<v-menu offset-y dark auto>
<template v-slot:activator="{ on }">
<v-icon color="white" v-on="on">menu</v-icon>
</template>
<v-list dense>
<v-list-item @click="toPipelineNameChange">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="edit" tooltip="Edit pipeline name"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="toCreatePipeline">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="add" tooltip="Add new pipeline"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="deleteCurrentPipeline">
<v-list-item-title>
<CVicon color="red darken-2" :right="true" text="delete"
tooltip="Delete pipeline"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="openDuplicateDialog">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="mdi-content-copy"
tooltip="Duplicate pipeline"/>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<v-btn style="position: absolute; top:5px;right: 0;" tile color="#4baf62"
@click="handleInput('command','save')">
<v-icon>save</v-icon>
Save
</v-btn>
</v-row>
<!--pipeline duplicate dialog-->
<v-dialog dark v-model="duplicateDialog" width="500" height="357">
<v-card dark>
<v-card-title class="headline" primary-title>Duplicate Pipeline</v-card-title>
<v-card-text>
<CVselect name="Pipeline" :list="$store.getters.pipelineList" v-model="pipelineDuplicate.pipeline"/>
<v-checkbox v-if="$store.getters.cameraList.length > 1" dark :label="'To another camera'"
v-model="anotherCamera"/>
<CVselect v-if="anotherCamera === true" name="Camera" v-model="pipelineDuplicate.camera"
:list="$store.getters.cameraList"/>
</v-card-text>
<v-divider>
</v-divider>
<v-card-actions>
<v-spacer/>
<v-btn color="#4baf62" @click="duplicatePipeline">Duplicate</v-btn>
<v-btn color="error" @click="closeDuplicateDialog">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!--pipeline naming dialog-->
<v-dialog dark v-model="namingDialog" width="500" height="357">
<v-card dark>
<v-card-title class="headline" primary-title>Pipeline Name</v-card-title>
<v-card-text>
<CVinput name="Pipeline" :error-message="checkPipelineName" v-model="newPipelineName"
@Enter="savePipelineNameChange"/>
</v-card-text>
<v-divider>
</v-divider>
<v-card-actions>
<v-spacer/>
<v-btn color="#4baf62" @click="savePipelineNameChange" :disabled="checkPipelineName !==''">Save
</v-btn>
<v-btn color="error" @click="discardPipelineNameChange">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import CVicon from '../common/cv-icon'
import CVselect from '../common/cv-select'
import CVinput from '../common/cv-input'
export default {
name: "CameraAndPipelineSelect",
components: {
CVicon,
CVselect,
CVinput
},
data: () => {
return {
re: RegExp("^[A-Za-z0-9 \\-)(]*[A-Za-z0-9][A-Za-z0-9 \\-)(.]*$"),
isCameraNameEdit: false,
newCameraName: "",
cameraNameError: "",
isPipelineNameEdit: false,
namingDialog: false,
newPipelineName: "",
duplicateDialog: false,
anotherCamera: false,
pipelineDuplicate: {
pipeline: undefined,
camera: -1
},
}
},
methods: {
toCameraNameChange() {
this.newCameraName = this.$store.getters.cameraList[this.currentCameraIndex];
this.isCameraNameEdit = true;
},
saveCameraNameChange() {
if (this.checkCameraName === "") {
this.handleInput("changeCameraName", this.newCameraName);
this.discardCameraNameChange();
}
},
discardCameraNameChange() {
this.isCameraNameEdit = false;
this.newCameraName = "";
},
toPipelineNameChange() {
this.newPipelineName = this.$store.getters.pipelineList[this.currentPipelineIndex - 1];
this.isPipelineNameEdit = true;
this.namingDialog = true;
},
toCreatePipeline() {
this.newPipelineName = "New Pipeline";
this.isPipelineNameEdit = false;
this.namingDialog = true;
},
openDuplicateDialog() {
this.pipelineDuplicate = {
pipeline: this.currentPipelineIndex - 1,
camera: -1
};
this.duplicateDialog = true;
},
deleteCurrentPipeline() {
if (this.$store.getters.pipelineList.length > 1) {
this.handleInput('command', 'deleteCurrentPipeline');
} else {
this.snackbar = true;
}
},
savePipelineNameChange() {
if (this.checkPipelineName === "") {
if (this.isPipelineNameEdit) {
this.handleInput("changePipelineName", this.newPipelineName);
} else {
this.handleInput("addNewPipeline", this.newPipelineName);
}
this.discardPipelineNameChange();
}
},
duplicatePipeline() {
if (!this.anotherCamera) {
this.pipelineDuplicate.camera = -1
}
// this.handleInput("duplicatePipeline", this.pipelineDuplicate);
this.axios.post("http://" + this.$address + "/api/vision/duplicate", this.pipelineDuplicate);
this.closeDuplicateDialog();
},
closeDuplicateDialog() {
this.duplicateDialog = false;
this.pipelineDuplicate = {
pipeline: undefined,
camera: -1
}
},
discardPipelineNameChange() {
this.namingDialog = false;
this.isPipelineNameEdit = false;
this.newPipelineName = "";
},
},
computed: {
checkCameraName() {
if (this.newCameraName !== this.$store.getters.cameraList[this.currentCameraIndex]) {
if (this.re.test(this.newCameraName)) {
for (let cam in this.cameraList) {
if (this.cameraList.hasOwnProperty(cam)) {
if (this.newCameraName === this.cameraList[cam]) {
return "Camera by that name already Exists"
}
}
}
} else {
return "Camera name can only contain letters, numbers and spaces"
}
}
return ""
},
checkPipelineName() {
if (this.newPipelineName !== this.$store.getters.pipelineList[this.currentPipelineIndex - 1] || this.isPipelineNameEdit === false) {
if (this.re.test(this.newPipelineName)) {
for (let pipe in this.$store.getters.pipelineList) {
if (this.$store.getters.pipelineList.hasOwnProperty(pipe)) {
if (this.newPipelineName === this.$store.getters.pipelineList[pipe]) {
return "A pipeline with this name already exists"
}
}
}
} else {
return "Pipeline name can only contain letters, numbers, and spaces"
}
}
return ""
},
currentCameraIndex: {
get() {
return this.$store.getters.currentCameraIndex;
},
set(value) {
this.$store.commit('currentPipelineIndex', value - 1);
}
},
currentPipelineIndex: {
get() {
return this.$store.getters.currentPipelineIndex + 1;
},
set(value) {
this.$store.commit('currentPipelineIndex', value - 1);
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,9 +1,8 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import store from './store/index'
import vuetify from './plugins/vuetify';
import VueNativeSock from 'vue-native-websocket';
import msgPack from 'msgpack5';
import axios from 'axios';
import VueAxios from "vue-axios";
@@ -16,24 +15,22 @@ if (process.env.NODE_ENV === "production") {
Vue.prototype.$address = location.hostname + ":5800";
}
const url = 'ws://' + Vue.prototype.$address + '/websocket';
var ws = new WebSocket(url);
const wsURL = 'ws://' + Vue.prototype.$address + '/websocket';
const ws = new WebSocket(wsURL);
ws.binaryType = "arraybuffer";
Vue.use(VueNativeSock, url, {
import VueNativeSock from 'vue-native-websocket';
Vue.use(VueNativeSock, wsURL, {
WebSocket: ws
});
Vue.use(VueAxios, axios);
Vue.prototype.$msgPack = msgPack(true);
Vue.mixin({
methods: {
handleInput(key, value) {
let msg = this.$msgPack.encode({[key]: value});
this.$socket.send(msg);
}
}
});
import {dataHandleMixin} from './mixins/global/dataHandleMixin'
Vue.mixin(dataHandleMixin);
new Vue({
router,
store,

View File

@@ -0,0 +1,13 @@
export const dataHandleMixin = {
methods:{
handleInput(key, value) {
let msg = this.$msgPack.encode({[key]: value});
this.$socket.send(msg);
},
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
},
}
};

View File

@@ -1,29 +1,23 @@
import Vue from 'vue'
import Router from 'vue-router'
import Camera from "./views/PipelineView";
import Settings from "./views/SettingsView";
Vue.use(Router);
function lazyLoad(view) {
return () => import(`@/views/${view}.vue`)
}
export default new Router({
// mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
redirect: '/vision'
},
{
path: '/vision',
name: 'Vision',
component: lazyLoad('Camera')
},
{
path: '/settings',
name: 'Settings',
component: lazyLoad('Settings')
},
]
routes: [{
path: '/',
redirect: '/vision'
}, {
path: '/vision',
name: 'Vision',
component: Camera
}, {
path: '/settings',
name: 'Settings',
component: Settings
}]
})

View File

@@ -1,94 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const set = key => (state, val) => {
Vue.set(state, key, val);
};
export default new Vuex.Store({
state: {
settings: {
teamNumber: 1577,
connectionType: 0,
ip: "",
gateway: "",
netmask: "",
hostname: "chameleon-vision"
},
pipeline: {
exposure: 0,
brightness: 0,
rotationMode: 0,
hue: [0, 15],
saturation: [0, 15],
value: [0, 25],
erode: false,
dilate: false,
area: [0, 12],
ratio: [0, 12],
extent: [0, 12],
speckle: 5,
targetGrouping: 0,
targetIntersection: 0,
sortMode: 0,
multiple: false,
isBinary: 0,
calibrationMode: 0,
videoModeIndex: 0,
streamDivisor: 0,
is3D: false,
targetRegion: 0,
targetOrientation: 1
},
cameraSettings: {
calibration: [],
fov: 0,
resolution: 0,
streamDivisor: 0,
tilt: 0
},
resolutionList: [],
port: 1181,
currentCameraIndex: 0,
currentPipelineIndex: 0,
cameraList: [],
pipelineList: [],
point: {},
saveBar: false
},
mutations: {
settings: set('settings'),
pipeline: set('pipeline'),
cameraSettings: set('cameraSettings'),
resolutionList: set('resolutionList'),
port: set('port'),
currentCameraIndex: set('currentCameraIndex'),
currentPipelineIndex: set('currentPipelineIndex'),
cameraList: set('cameraList'),
pipelineList: set('pipelineList'),
point: set('point'),
setPipeValues(state, obj) {
for (let i in obj) {
Vue.set(state.pipeline, i, obj[i]);
}
},
driverMode: set('driverMode'),
saveBar: set("saveBar")
},
actions: {
settings: state => state.settings,
pipeline: state => state.pipeline,
cameraSettings: state => state.cameraSettings,
resolutionList: state => state.resolutionList,
port: state => state.port,
currentCameraIndex: state => state.currentCameraIndex,
currentPipelineIndex: state => state.currentPipelineIndex,
cameraList: state => state.cameraList,
pipelineList: state => state.pipelineList,
point: state => state.point,
driverMode: state => state.driverMode,
saveBar: state => state.saveBar
}
})

View File

@@ -0,0 +1,64 @@
import Vue from 'vue'
import Vuex from 'vuex'
import pipeline from "./modules/pipeline";
import generalSettings from "./modules/generalSettings";
import cameraSettings from "./modules/cameraSettings";
Vue.use(Vuex);
const set = key => (state, val) => {
Vue.set(state, key, val);
};
export default new Vuex.Store({
modules: {
pipeline: pipeline,
settings: generalSettings,
cameraSettings: cameraSettings
},
state: {
resolutionList: [],
port: 1181,
currentCameraIndex: 0,
currentPipelineIndex: 0,
cameraList: [],
pipelineList: [],
point: {},
saveBar: false
},
mutations: {
settings: set('settings'),
pipeline: set('pipeline'),
cameraSettings: set('cameraSettings'),
resolutionList: set('resolutionList'),
port: set('port'),
currentCameraIndex: set('currentCameraIndex'),
currentPipelineIndex: set('currentPipelineIndex'),
cameraList: set('cameraList'),
pipelineList: set('pipelineList'),
point: set('point'),
driverMode: set('driverMode'),
saveBar: set("saveBar")
},
getters: {
streamAddress: state => {
return "http://" + location.hostname + ":" + state.port + "/stream.mjpg";
},
targets: state => {
return state.point['targets']
},
cameraList: state => {
return state.cameraList
},
pipelineList: state => {
return state.pipelineList
},
currentCameraIndex: state => {
return state.currentCameraIndex
},
currentPipelineIndex: state => {
return state.currentPipelineIndex
}
}
})

View File

@@ -0,0 +1,14 @@
export default {
state: {
calibration: [],
fov: 0,
resolution: 0,
streamDivisor: 0,
tilt: 0
},
getters: {
cameraSettings: state => {
return state
}
}
}

View File

@@ -0,0 +1,10 @@
export default {
state:{
teamNumber: 1577,
connectionType: 0,
ip: "",
gateway: "",
netmask: "",
hostname: "chameleon-vision"
}
}

View File

@@ -0,0 +1,45 @@
export default {
state: {
exposure: 0,
brightness: 0,
gain: 0,
rotationMode: 0,
hue: [0, 15],
saturation: [0, 15],
value: [0, 25],
erode: false,
dilate: false,
area: [0, 12],
ratio: [0, 12],
extent: [0, 12],
speckle: 5,
targetGrouping: 0,
targetIntersection: 0,
sortMode: 0,
multiple: false,
isBinary: 0,
calibrationMode: 0,
videoModeIndex: 0,
streamDivisor: 0,
is3D: false,
targetRegion: 0,
targetOrientation: 1
},
mutations: {
isBinary: (state, value) => {
console.log(value)
state.isBinary = value
},
mutatePipeline: (state, {key, value}) => {
// console.log(`key:${key}, value: ${value}`)
this.set(state, key, value)
}
},
actions: {},
getters: {
pipeline: state => {
return state
}
}
};

View File

@@ -1,414 +0,0 @@
<template>
<div>
<div>
<v-row align="center">
<v-col :cols="3" class="colsClass">
<div style="padding-left:30px">
<CVselect v-if="isCameraNameEdit === false" name="Camera" v-model="currentCameraIndex"
:list="cameraList" @input="handleInput('currentCamera',currentCameraIndex)"/>
<CVinput v-else name="Camera" v-model="newCameraName" @Enter="saveCameraNameChange"
:errorMessage="checkCameraName"/>
</div>
</v-col>
<v-col :cols="1">
<CVicon color="#c5c5c5" v-if="isCameraNameEdit === false" hover text="edit"
@click="toCameraNameChange" tooltip="Edit camera name"/>
<div v-else>
<CVicon color="#c5c5c5" style="display: inline-block;" hover text="save"
@click="saveCameraNameChange" tooltip="Save Camera Name"/>
<CVicon color="error" style="display: inline-block;" hover text="close"
@click="discardCameraNameChange" tooltip="Discard Changes"/>
</div>
</v-col>
<v-col :cols="3" class="colsClass">
<CVselect name="Pipeline"
:list="['Driver Mode'].concat(pipelineList)"
v-model="currentPipelineIndex"
@input="handleInput('currentPipeline',currentPipelineIndex - 1)"/>
</v-col>
<v-col :cols="1" class="colsClass" md="3" v-if="currentPipelineIndex !== 0">
<v-menu offset-y dark auto>
<template v-slot:activator="{ on }">
<v-icon color="white" v-on="on">menu</v-icon>
</template>
<v-list dense>
<v-list-item @click="toPipelineNameChange">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="edit" tooltip="Edit pipeline name"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="toCreatePipeline">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="add" tooltip="Add new pipeline"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="deleteCurrentPipeline">
<v-list-item-title>
<CVicon color="red darken-2" :right="true" text="delete"
tooltip="Delete pipeline"/>
</v-list-item-title>
</v-list-item>
<v-list-item @click="openDuplicateDialog">
<v-list-item-title>
<CVicon color="#c5c5c5" :right="true" text="mdi-content-copy"
tooltip="Duplicate pipeline"/>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<v-btn style="position: absolute; top:5px;right: 0;" tile color="#4baf62"
@click="handleInput('command','save')">
<v-icon>save</v-icon>
Save
</v-btn>
</v-row>
</div>
<v-row>
<!-- vision tabs -->
<v-col cols="6" class="colsClass">
<v-tabs fixed-tabs background-color="#212121" dark height="48" slider-color="#4baf62"
v-model="selectedTab" v-if="currentPipelineIndex !== 0">
<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>
<div style="padding-left:30px">
<keep-alive>
<!-- vision component -->
<component v-model="pipeline" :is="selectedComponent" ref="component" @update="$emit('save')"/>
</keep-alive>
</div>
</v-col>
<v-col cols="6" class="colsClass">
<div>
<!-- camera image tabs -->
<v-tabs background-color="#212121" dark height="48" slider-color="#4baf62" centered
style="padding-bottom:10px" v-model="isBinaryNumber"
@change="handleInput('isBinary',pipeline.isBinary)" v-if="currentPipelineIndex !== 0">
<v-tab>Normal</v-tab>
<v-tab>Threshold</v-tab>
</v-tabs>
<div v-else style="height: 58px"></div>
<!-- camera image stream -->
<div class="videoClass">
<v-row align="center">
<img id="CameraStream" style="display: block;margin: auto; width: 70%;height: 70%;"
v-if="cameraList.length > 0"
:src="streamAddress" @click="onImageClick"
crossorigin="Anonymous"/>
<span style="display: block;margin: auto; width: 70%;height: 70%;" v-else>No Cameras Are connected</span>
</v-row>
<v-row justify="end">
<span style="margin-right: 45px">FPS:{{parseFloat(fps).toFixed(2)}}</span>
</v-row>
<v-row align="center">
<v-simple-table
style="text-align: center;background-color: transparent; display: block;margin: auto"
dense dark>
<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 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>
</v-row>
</div>
</div>
</v-col>
</v-row>
<!-- pipeline duplicate dialog -->
<v-dialog dark v-model="duplicateDialog" width="500" height="357">
<v-card dark>
<v-card-title class="headline" primary-title>Duplicate Pipeline</v-card-title>
<v-card-text>
<CVselect name="Pipeline" :list="pipelineList" v-model="pipelineDuplicate.pipeline"/>
<v-checkbox v-if="cameraList.length > 1" dark :label="'To another camera'" v-model="anotherCamera"/>
<CVselect v-if="anotherCamera === true" name="Camera" v-model="pipelineDuplicate.camera"
:list="cameraList"/>
</v-card-text>
<v-divider>
</v-divider>
<v-card-actions>
<v-spacer/>
<v-btn color="#4baf62" @click="duplicatePipeline">Duplicate</v-btn>
<v-btn color="error" @click="closeDuplicateDialog">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!--pipeline naming dialog-->
<v-dialog dark v-model="namingDialog" width="500" height="357">
<v-card dark>
<v-card-title class="headline" primary-title>Pipeline Name</v-card-title>
<v-card-text>
<CVinput name="Pipeline" :error-message="checkPipelineName" v-model="newPipelineName"
@Enter="savePipelineNameChange"/>
</v-card-text>
<v-divider>
</v-divider>
<v-card-actions>
<v-spacer/>
<v-btn color="#4baf62" @click="savePipelineNameChange" :disabled="checkPipelineName !==''">Save
</v-btn>
<v-btn color="error" @click="discardPipelineNameChange">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- snack bar -->
<v-snackbar :timeout="3000" v-model="snackbar" top color="error">
<span style="color:#000">Can not remove the only pipeline!</span>
<v-btn color="black" text @click="snackbar = false">Close</v-btn>
</v-snackbar>
</div>
</template>
<script>
import InputTab from './CameraViewes/InputTab'
import ThresholdTab from './CameraViewes/ThresholdTab'
import ContoursTab from './CameraViewes/ContoursTab'
import OutputTab from './CameraViewes/OutputTab'
import pnpTab from './CameraViewes/3D'
import CVselect from '../components/cv-select'
import CVicon from '../components/cv-icon'
import CVinput from '../components/cv-input'
export default {
name: 'CameraTab',
components: {
InputTab,
ThresholdTab,
ContoursTab,
OutputTab,
pnpTab,
CVselect,
CVicon,
CVinput
},
methods: {
onImageClick(event) {
if (this.selectedTab === 1) {
this.$refs.component.onClick(event);
}
},
toCameraNameChange() {
this.newCameraName = this.cameraList[this.currentCameraIndex];
this.isCameraNameEdit = true;
},
saveCameraNameChange() {
if (this.checkCameraName === "") {
this.handleInput("changeCameraName", this.newCameraName);
this.discardCameraNameChange();
}
},
discardCameraNameChange() {
this.isCameraNameEdit = false;
this.newCameraName = "";
},
toPipelineNameChange() {
this.newPipelineName = this.pipelineList[this.currentPipelineIndex - 1];
this.isPipelineNameEdit = true;
this.namingDialog = true;
},
toCreatePipeline() {
this.newPipelineName = "New Pipeline";
this.isPipelineNameEdit = false;
this.namingDialog = true;
},
savePipelineNameChange() {
if (this.checkPipelineName === "") {
if (this.isPipelineNameEdit) {
this.handleInput("changePipelineName", this.newPipelineName);
} else {
this.handleInput("addNewPipeline", this.newPipelineName);
}
this.discardPipelineNameChange();
}
},
discardPipelineNameChange() {
this.namingDialog = false;
this.isPipelineNameEdit = false;
this.newPipelineName = "";
},
duplicatePipeline() {
if (!this.anotherCamera) {
this.pipelineDuplicate.camera = -1
}
// this.handleInput("duplicatePipeline", this.pipelineDuplicate);
this.axios.post("http://" + this.$address + "/api/vision/duplicate", this.pipelineDuplicate);
this.closeDuplicateDialog();
},
openDuplicateDialog() {
this.pipelineDuplicate = {
pipeline: this.currentPipelineIndex - 1,
camera: -1
};
this.duplicateDialog = true;
},
closeDuplicateDialog() {
this.duplicateDialog = false;
this.pipelineDuplicate = {
pipeline: undefined,
camera: -1
}
},
deleteCurrentPipeline() {
if (this.pipelineList.length > 1) {
this.handleInput('command', 'deleteCurrentPipeline');
} else {
this.snackbar = true;
}
}
},
data() {
return {
re: RegExp("^[A-Za-z0-9 \\-)(]*[A-Za-z0-9][A-Za-z0-9 \\-)(.]*$"),
selectedTab: 0,
// camera edit variables
isCameraNameEdit: false,
newCameraName: "",
cameraNameError: "",
// pipeline edit variables
isPipelineNameEdit: false,
namingDialog: false,
newPipelineName: "",
duplicateDialog: false,
anotherCamera: false,
pipelineDuplicate: {
pipeline: undefined,
camera: -1
},
snackbar: false,
}
},
computed: {
checkCameraName() {
if (this.newCameraName !== this.cameraList[this.currentCameraIndex]) {
if (this.re.test(this.newCameraName)) {
for (let cam in this.cameraList) {
if (this.cameraList.hasOwnProperty(cam)) {
if (this.newCameraName === this.cameraList[cam]) {
return "Camera by that name already Exists"
}
}
}
} else {
return "Camera name can only contain letters, numbers and spaces"
}
}
return ""
},
checkPipelineName() {
if (this.newPipelineName !== this.pipelineList[this.currentPipelineIndex - 1] || this.isPipelineNameEdit === false) {
if (this.re.test(this.newPipelineName)) {
for (let pipe in this.pipelineList) {
if (this.pipelineList.hasOwnProperty(pipe)) {
if (this.newPipelineName === this.pipelineList[pipe]) {
return "A pipeline with this name already exists"
}
}
}
} else {
return "Pipeline name can only contain letters, numbers, and spaces"
}
}
return ""
},
isBinaryNumber: {
get() {
return this.pipeline.isBinary ? 1 : 0
},
set(value) {
this.pipeline.isBinary = !!value;
}
},
selectedComponent: {
get() {
return this.currentPipelineIndex === 0 ? "InputTab" : ["InputTab", "ThresholdTab", "ContoursTab", "OutputTab", "pnpTab"][this.selectedTab];
}
},
targets: {
get: function () {
return this.$store.state.point.targets;
}
},
fps: {
get() {
return this.$store.state.point.fps;
}
},
currentCameraIndex: {
get() {
return this.$store.state.currentCameraIndex;
},
set(value) {
this.$store.commit('currentCameraIndex', value);
}
},
currentPipelineIndex: {
get() {
return this.$store.state.currentPipelineIndex + 1;
},
set(value) {
this.$store.commit('currentPipelineIndex', value - 1);
}
},
cameraList: {
get() {
return this.$store.state.cameraList;
}
},
pipelineList: {
get() {
return this.$store.state.pipelineList;
}
},
pipeline: {
get() {
return this.$store.state.pipeline;
}
},
streamAddress: {
get() {
return "http://" + location.hostname + ":" + this.$store.state.port + "/stream.mjpg";
}
},
}
}
</script>
<style scoped>
.colsClass {
padding: 0 !important;
}
.videoClass {
text-align: center;
}
th {
width: 80px;
text-align: center;
}
</style>

View File

@@ -0,0 +1,150 @@
<template>
<div>
<camera-and-pipeline-select/>
<v-row>
<!-- vision tabs -->
<v-col cols="6" class="colsClass">
<v-tabs fixed-tabs background-color="#212121" dark height="48" slider-color="#4baf62"
v-model="selectedTab" v-if="($store.getters.currentPipelineIndex + 1) !== 0">
<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>
<div style="padding-left:30px">
<keep-alive>
<!-- vision component -->
<component v-model="$store.getters.pipeline" :is="selectedComponent" ref="component"
@update="$emit('save')"/>
</keep-alive>
</div>
</v-col>
<v-col cols="6" class="colsClass">
<div>
<!-- camera image tabs -->
<v-tabs background-color="#212121" dark height="48" slider-color="#4baf62" centered
style="padding-bottom:10px" v-model="isBinaryNumber"
@change="handleInput('isBinary',$store.getters.pipeline.isBinary)"
v-if="($store.getters.currentPipelineIndex + 1) !== 0">
<v-tab>Normal</v-tab>
<v-tab>Threshold</v-tab>
</v-tabs>
<div v-else style="height: 58px"></div>
<!-- camera image stream -->
<div class="videoClass">
<v-row align="center">
<cvImage :address="$store.getters.streamAddress" :scale="75" @click="onImageClick"/>
</v-row>
<v-row justify="end">
<span style="margin-right: 45px">FPS:{{parseFloat(fps).toFixed(2)}}</span>
</v-row>
<v-row align="center">
<v-simple-table
style="text-align: center;background-color: transparent; display: block;margin: auto"
dense dark>
<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.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>
</v-row>
</div>
</div>
</v-col>
</v-row>
<!-- snack bar -->
<v-snackbar :timeout="3000" v-model="snackbar" top color="error">
<span style="color:#000">Can not remove the only pipeline!</span>
<v-btn color="black" text @click="snackbar = false">Close</v-btn>
</v-snackbar>
</div>
</template>
<script>
import CameraAndPipelineSelect from "../components/pipeline/CameraAndPipelineSelect";
import cvImage from '../components/common/cv-image'
import InputTab from './PipelineViewes/InputTab'
import ThresholdTab from './PipelineViewes/ThresholdTab'
import ContoursTab from './PipelineViewes/ContoursTab'
import OutputTab from './PipelineViewes/OutputTab'
import pnpTab from './PipelineViewes/3D'
export default {
name: 'CameraTab',
components: {
CameraAndPipelineSelect,
cvImage,
InputTab,
ThresholdTab,
ContoursTab,
OutputTab,
pnpTab,
},
methods: {
onImageClick(event) {
if (this.selectedTab === 1) {
this.$refs.component.onClick(event);
}
},
},
data() {
return {
selectedTab: 0,
snackbar: false,
}
},
computed: {
isBinaryNumber: {
get() {
return this.$store.getters.pipeline.isBinary ? 1 : 0
},
set(value) {
this.$store.commit('isBinary', !!value);
}
},
selectedComponent: {
get() {
return (this.$store.getters.currentPipelineIndex + 1) === 0 ? "InputTab" : ["InputTab", "ThresholdTab", "ContoursTab", "OutputTab", "pnpTab"][this.selectedTab];
}
},
fps: {
get() {
return this.$store.state.point.fps;
}
}
}
}
</script>
<style scoped>
.colsClass {
padding: 0 !important;
}
.videoClass {
text-align: center;
}
th {
width: 80px;
text-align: center;
}
</style>

View File

@@ -32,9 +32,9 @@
<script>
import Papa from 'papaparse';
import miniMap from '../../components/3D/MiniMap';
import CVswitch from '../../components/cv-switch';
import CVslider from '../../components/cv-slider'
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 {
@@ -58,10 +58,6 @@
}
},
methods: {
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
},
readFile(event) {
let file = event.target.files[0];
Papa.parse(file, {

View File

@@ -17,9 +17,9 @@
</template>
<script>
import CVrangeSlider from '../../components/cv-range-slider'
import CVselect from '../../components/cv-select'
import CVslider from '../../components/cv-slider'
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',
@@ -30,10 +30,6 @@
CVslider
},
methods: {
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
}
},
data() {

View File

@@ -14,8 +14,8 @@
</template>
<script>
import CVslider from '../../components/cv-slider'
import CVselect from '../../components/cv-select'
import CVslider from '../../components/common/cv-slider'
import CVselect from '../../components/common/cv-select'
export default {
name: 'Input',
@@ -25,10 +25,6 @@
CVselect,
},
methods: {
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
}
},
data() {
return {

View File

@@ -21,10 +21,10 @@
</template>
<script>
import CVselect from '../../components/cv-select'
import CVswitch from '../../components/cv-switch'
import DualCalibration from "../../components/OutputTab/DualCalibration";
import SingleCalibration from "../../components/OutputTab/SingleCalibration";
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 {
@@ -38,10 +38,6 @@
},
methods: {
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
},
doUpdate() {
this.$emit('update')
},

View File

@@ -24,8 +24,8 @@
</template>
<script>
import CVrangeSlider from '../../components/cv-range-slider'
import CVswitch from '../../components/cv-switch'
import CVrangeSlider from '../../components/common/cv-range-slider'
import CVswitch from '../../components/common/cv-switch'
export default {
name: 'Threshold',
@@ -96,11 +96,7 @@
this.currentFunction = this.colorPicker.shrink;
break;
}
},
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
},
}
},
mounted: function () {
const self = this;

View File

@@ -13,7 +13,7 @@
</v-col>
<v-col class="colsClass" v-show="selectedTab === 1">
<div class="videoClass">
<img :src="streamAddress" alt="Camera Stream">
<cvImage :address="$store.getters.streamAddress" :scale="75"/>
</div>
</v-col>
</v-row>
@@ -23,11 +23,13 @@
<script>
import General from './SettingsViewes/General'
import Cameras from './SettingsViewes/Cameras'
import cvImage from '../components/common/cv-image'
export default {
name: 'SettingsTab',
components: {
cvImage,
General,
Cameras,
},
@@ -43,11 +45,6 @@
return this.tabList[this.selectedTab];
}
},
streamAddress: {
get: function () {
return "http://" + location.hostname + ":" + this.$store.state.port + "/stream.mjpg";
}
},
}
}
</script>
@@ -60,7 +57,6 @@
.videoClass img {
padding-top: 10px;
height: auto !important;
width: 75%;
vertical-align: middle;
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div>
<CVselect name="Camera" :list="cameraList" v-model="currentCameraIndex"
<CVselect name="Camera" :list="$store.getters.cameraList" v-model="currentCameraIndex"
@input="handleInput('currentCamera',currentCameraIndex)"/>
<CVnumberinput name="Diagonal FOV" v-model="cameraSettings.fov"/>
<br>
@@ -50,14 +50,16 @@
</v-row>
<div v-if="isCalibrating">
<v-checkbox v-model="isAdvanced" label="Advanced Menu" dark/>
<div v-if="isAdvanced">
<CVslider name="Exposure" v-model="pipeline.exposure" :min="0" :max="100"
@input="handleData('exposure')"/>
<CVslider name="Brightness" v-model="pipeline.brightness" :min="0" :max="100"
@input="handleData('brightness')"/>
<CVslider name="Gain" v-if="pipeline.gain !== -1" v-model="pipeline.gain" :min="0" :max="100"
@input="handleData('gain')"/>
<CVselect name="FPS" v-model="pipeline.videoModeIndex" :list="stringFpsList" @input="changeFps"/>
<div v-if="isAdvanced" >
<CVslider name="Exposure" v-model="$store.getters.pipeline.exposure" :min="0" :max="100"
@input="e=> handleInput('exposure', e)"/>
<CVslider name="Brightness" v-model="$store.getters.pipeline.brightness" :min="0" :max="100"
@input="e=> handleInput('brightness', e)"/>
<CVslider name="Gain" v-if="$store.getters.pipeline.gain !== -1"
v-model="$store.getters.pipeline.gain" :min="0" :max="100"
@input="e=> handleInput('gain', e)"/>
<CVselect name="FPS" v-model="$store.getters.pipeline.videoModeIndex" :list="stringFpsList"
@input="changeFps"/>
</div>
</div>
</div>
@@ -68,9 +70,9 @@
</template>
<script>
import CVselect from '../../components/cv-select'
import CVnumberinput from '../../components/cv-number-input'
import CVslider from '../../components/cv-slider'
import CVselect from '../../components/common/cv-select'
import CVnumberinput from '../../components/common/cv-number-input'
import CVslider from '../../components/common/cv-slider'
export default {
name: 'CameraSettings',
@@ -103,16 +105,13 @@
}
},
methods: {
handleData(val) {
this.handleInput(val, this.pipeline[val]);
},
downloadBoard() {
this.axios.get("http://" + this.$address + require('../../assets/chessboard.png'), {responseType: 'blob'}).then((response) => {
require('downloadjs')(response.data, "Calibration Board", "image/png")
})
},
changeFps() {
this.handleInput('videoModeIndex', this.filteredFpsList[this.pipeline['videoModeIndex']]['actualIndex']);
this.handleInput('videoModeIndex', this.filteredFpsList[this.$store.getters.pipeline['videoModeIndex']]['actualIndex']);
},
sendCameraSettings() {
const self = this;
@@ -211,14 +210,6 @@
this.$store.commit('currentCameraIndex', value);
}
},
cameraList: {
get() {
return this.$store.state.cameraList;
},
set(value) {
this.$store.commit('cameraList', value);
}
},
filteredResolutionList: {
get() {
let tmp_list = [];
@@ -268,16 +259,11 @@
},
cameraSettings: {
get() {
return this.$store.state.cameraSettings;
return this.$store.getters.cameraSettings;
},
set(value) {
this.$store.commit('cameraSettings', value);
}
},
pipeline: {
get() {
return this.$store.state.pipeline;
}
}
}
}

View File

@@ -41,9 +41,9 @@
</template>
<script>
import CVnumberinput from '../../components/cv-number-input'
import CVradio from '../../components/cv-radio'
import CVinput from '../../components/cv-input'
import CVnumberinput from '../../components/common/cv-number-input'
import CVradio from '../../components/common/cv-radio'
import CVinput from '../../components/common/cv-input'
export default {
name: 'General',