mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-25 01:41:40 +00:00
3d, camera calibration, backend settings hookup (#80)
* Implement new UI backend stuff * Kinda partially add resolution accuracy list * camera calibration go brrrrrrrr * ayyyy calibration works * Maybe fix grouping * Reorganize camera view * Fix settings not getting sent * Make pretty (#4) * Reorganize camera view * Apply some cosmetic layout changes to the cameras page * Fix pipeline rollback bug when starting on non-dashboard pages Co-authored-by: Matt <matthew.morley.ca@gmail.com> * Fix naming mismatch * Mostly make stuff work * rename robot-relative pose to camera-relative pose * SolvePNP memes, fix isFovConfigurable * Change config path to photonvision_config * netmask go poof, fix zip download? * Update index.js * Fix multi cam stuff? * Use LinearFilter instead * Fix multicam * aaa * start adding restart device and restart program, fix square size bug * Add some debug stuff * oop * Start fixing tests * Fix tests * Make target box proportinal * run spotless * Make crosshair h o t p i n k * Address review comments * Address review 2 electric booaloo * Possibly implement vendor FOV? * Make centroid crosshair gren * actually use FOV * Fix tests * actually fix tests Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
This commit is contained in:
@@ -104,7 +104,9 @@
|
||||
</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ $store.state.backendConnected ? "Connected" : "Trying to connect..." }}</v-list-item-title>
|
||||
<v-list-item-title>
|
||||
{{ $store.state.backendConnected ? "Connected" : "Trying to connect..." }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -152,7 +154,7 @@
|
||||
},
|
||||
data: () => ({
|
||||
// Used so that we can switch back to the previously selected pipeline after camera calibration
|
||||
previouslySelectedIndex: null,
|
||||
previouslySelectedIndex: undefined,
|
||||
timer: undefined,
|
||||
isLogger: false,
|
||||
log: "",
|
||||
@@ -169,9 +171,9 @@
|
||||
compact: {
|
||||
get() {
|
||||
if (this.$store.state.compactMode === undefined) {
|
||||
return this.$vuetify.breakpoint.smAndDown;
|
||||
return this.$vuetify.breakpoint.smAndDown;
|
||||
} else {
|
||||
return this.$store.state.compactMode || this.$vuetify.breakpoint.smAndDown;
|
||||
return this.$store.state.compactMode || this.$vuetify.breakpoint.smAndDown;
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
@@ -179,7 +181,7 @@
|
||||
this.$store.commit("compactMode", value);
|
||||
localStorage.setItem("compactMode", value);
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
document.addEventListener("keydown", e => {
|
||||
@@ -210,16 +212,16 @@
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error: ' + data.data + " , " + error);
|
||||
console.error('error: ' + JSON.stringify(data.data) + " , " + error);
|
||||
}
|
||||
};
|
||||
this.$options.sockets.onopen = () => {
|
||||
this.$store.state.backendConnected = true;
|
||||
this.$store.state.backendConnected = true;
|
||||
};
|
||||
|
||||
let closed = () => {
|
||||
this.$store.state.backendConnected = false;
|
||||
}
|
||||
this.$store.state.backendConnected = false;
|
||||
};
|
||||
this.$options.sockets.onclose = closed;
|
||||
this.$options.sockets.onerror = closed;
|
||||
|
||||
@@ -228,14 +230,15 @@
|
||||
methods: {
|
||||
handleMessage(key, value) {
|
||||
if (key === "logMessage") {
|
||||
console.log("[FROM BACKEND]" + value);
|
||||
this.logMessage(value, 0);
|
||||
this.logMessage(value["logMessage"], value["logLevel"]);
|
||||
} else if (key === "updatePipelineResult") {
|
||||
this.$store.commit('mutatePipelineResults', value)
|
||||
} else if (this.$store.state.hasOwnProperty(key)) {
|
||||
this.$store.commit(key, value);
|
||||
} else if (this.$store.getters.currentPipelineSettings.hasOwnProperty(key)) {
|
||||
this.$store.commit('mutatePipeline', {[key]: value});
|
||||
} else if (this.$store.state.settings.hasOwnProperty(key)) {
|
||||
this.$store.commit('mutateSettings', {[key]: value});
|
||||
} else {
|
||||
switch (key) {
|
||||
default: {
|
||||
@@ -259,9 +262,10 @@
|
||||
this.timer = setInterval(this.saveSettings, 4000);
|
||||
},
|
||||
logMessage(message, level) {
|
||||
const colors = ["\u001b[31m", "\u001b[32m", "\u001b[33m", "\u001b[34m"]
|
||||
const colors = ["\u001B[30m", "\u001B[31m", "\u001B[33m", "\u001B[32m", "\u001B[37m", "\u001B[36m"]
|
||||
const reset = "\u001b[0m"
|
||||
this.log += `${colors[level]}${message}${reset}\n`
|
||||
console.log(message)
|
||||
},
|
||||
switchToDriverMode() {
|
||||
this.previouslySelectedIndex = this.$store.getters.currentPipelineIndex;
|
||||
@@ -269,7 +273,7 @@
|
||||
},
|
||||
rollbackPipelineIndex() {
|
||||
if (this.previouslySelectedIndex !== null) {
|
||||
this.handleInputWithIndex('currentPipeline', this.previouslySelectedIndex)
|
||||
this.handleInputWithIndex('currentPipeline', this.previouslySelectedIndex || 0);
|
||||
}
|
||||
this.previouslySelectedIndex = null;
|
||||
}
|
||||
@@ -278,7 +282,7 @@
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
@import "./scss/variables.scss"
|
||||
@import "./scss/variables.scss"
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@@ -288,12 +292,12 @@
|
||||
|
||||
@keyframes pulse-animation {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 20px rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
box-shadow: 0 0 0 20px rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,17 +353,17 @@
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
.v-input {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
.v-input {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~vuetify/src/styles/settings/_variables';
|
||||
@import '~vuetify/src/styles/settings/_variables';
|
||||
|
||||
@media #{map-get($display-breakpoints, 'md-and-down')} {
|
||||
html {
|
||||
font-size: 14px !important;
|
||||
@media #{map-get($display-breakpoints, 'md-and-down')} {
|
||||
html {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,7 @@
|
||||
type="number"
|
||||
style="width: 70px"
|
||||
:step="step"
|
||||
:disabled="disabled"
|
||||
:rules="rules"
|
||||
/>
|
||||
</v-col>
|
||||
@@ -37,7 +38,7 @@
|
||||
TooltippedLabel,
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['name', 'value', 'step', 'labelCols', 'rules', 'tooltip'],
|
||||
props: ['name', 'value', 'step', 'labelCols', 'rules', 'tooltip', 'disabled'],
|
||||
computed: {
|
||||
localValue: {
|
||||
get() {
|
||||
|
||||
@@ -148,22 +148,10 @@
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<CVselect
|
||||
v-model="pipelineDuplicate.pipeline"
|
||||
v-model="pipeIndexToDuplicate"
|
||||
name="Pipeline"
|
||||
:list="$store.getters.pipelineList"
|
||||
/>
|
||||
<v-checkbox
|
||||
v-if="$store.getters.cameraList.length > 1"
|
||||
v-model="anotherCamera"
|
||||
dark
|
||||
:label="'To another camera'"
|
||||
/>
|
||||
<CVselect
|
||||
v-if="anotherCamera === true"
|
||||
v-model="pipelineDuplicate.camera"
|
||||
name="Camera"
|
||||
:list="$store.getters.cameraList"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
@@ -249,11 +237,7 @@
|
||||
namingDialog: false,
|
||||
newPipelineName: "",
|
||||
duplicateDialog: false,
|
||||
anotherCamera: false,
|
||||
pipelineDuplicate: {
|
||||
pipeline: undefined,
|
||||
camera: -1
|
||||
},
|
||||
pipeIndexToDuplicate: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -299,10 +283,10 @@
|
||||
},
|
||||
currentPipelineIndex: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineIndex + this.$store.getters.isDriverMode ? 1 : 0;
|
||||
return this.$store.getters.currentPipelineIndex + (this.$store.getters.isDriverMode ? 1 : 0);
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('currentPipelineIndex', value - this.$store.getters.isDriverMode ? 1 : 0);
|
||||
this.$store.commit('currentPipelineIndex', value - (this.$store.getters.isDriverMode ? 1 : 0));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -332,10 +316,7 @@
|
||||
this.namingDialog = true;
|
||||
},
|
||||
openDuplicateDialog() {
|
||||
this.pipelineDuplicate = {
|
||||
pipeline: this.currentPipelineIndex - 1,
|
||||
camera: -1
|
||||
};
|
||||
this.pipeIndexToDuplicate = this.currentPipelineIndex - 1;
|
||||
this.duplicateDialog = true;
|
||||
},
|
||||
deleteCurrentPipeline() {
|
||||
@@ -356,19 +337,17 @@
|
||||
}
|
||||
},
|
||||
duplicatePipeline() {
|
||||
if (!this.anotherCamera) {
|
||||
this.pipelineDuplicate.camera = -1
|
||||
}
|
||||
// this.handleInput("duplicatePipeline", this.pipelineDuplicate);
|
||||
this.axios.post("http://" + this.$address + "/api/vision/duplicate", this.pipelineDuplicate);
|
||||
// if (!this.anotherCamera) {
|
||||
// this.pipelineDuplicate.camera = -1
|
||||
// }
|
||||
this.handleInputWithIndex("duplicatePipeline", this.pipeIndexToDuplicate);
|
||||
// this.axios.post("http://" + this.$address + "/api/vision/duplicate", this.pipeIndexToDuplicate);
|
||||
|
||||
this.closeDuplicateDialog();
|
||||
},
|
||||
closeDuplicateDialog() {
|
||||
this.duplicateDialog = false;
|
||||
this.pipelineDuplicate = {
|
||||
pipeline: undefined,
|
||||
camera: -1
|
||||
}
|
||||
this.pipeIndexToDuplicate = undefined;
|
||||
},
|
||||
discardPipelineNameChange() {
|
||||
this.namingDialog = false;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import networkSettings from "./modules/networkSettings"
|
||||
import undoRedo from "./modules/undoRedo";
|
||||
|
||||
Vue.use(Vuex);
|
||||
@@ -17,7 +16,6 @@ export default new Vuex.Store({
|
||||
currentResolutionIndex: 0,
|
||||
},
|
||||
},
|
||||
networkSettings: networkSettings,
|
||||
undoRedo: undoRedo
|
||||
},
|
||||
state: {
|
||||
@@ -43,6 +41,7 @@ export default new Vuex.Store({
|
||||
"pixelFormat": "BGR"
|
||||
}
|
||||
],
|
||||
calibrations: [ ],
|
||||
fov: 70.0,
|
||||
isFovConfigurable: true,
|
||||
calibrated: false,
|
||||
@@ -76,14 +75,14 @@ export default new Vuex.Store({
|
||||
solvePNPEnabled: false,
|
||||
targetRegion: 0,
|
||||
contourTargetOrientation: 1,
|
||||
is3D: false,
|
||||
|
||||
cornerDetectionAccuracyPercentage: 10,
|
||||
|
||||
// Settings that apply to shape
|
||||
}
|
||||
}
|
||||
],
|
||||
pipelineResults: [
|
||||
{
|
||||
pipelineResults: {
|
||||
fps: 0,
|
||||
latency: 0,
|
||||
targets: [{
|
||||
@@ -95,8 +94,7 @@ export default new Vuex.Store({
|
||||
// 3D only
|
||||
pose: {x: 0, y: 0, rotation: 0},
|
||||
}]
|
||||
}
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
general: {
|
||||
version: "Unknown",
|
||||
@@ -106,7 +104,7 @@ export default new Vuex.Store({
|
||||
hardwareModel: "Unknown",
|
||||
hardwarePlatform: "Unknown",
|
||||
},
|
||||
networking: {
|
||||
networkSettings: {
|
||||
teamNumber: 0,
|
||||
|
||||
supported: true,
|
||||
@@ -120,19 +118,29 @@ export default new Vuex.Store({
|
||||
supported: true,
|
||||
brightness: 0.0,
|
||||
},
|
||||
}
|
||||
},
|
||||
calibrationData: {
|
||||
count: 0,
|
||||
videoModeIndex: 0,
|
||||
minCount: 25,
|
||||
hasEnough: false,
|
||||
squareSizeIn: 1.0,
|
||||
patternWidth: 7,
|
||||
patternHeight: 7,
|
||||
boardType: 0, // Chessboard, dotboard
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
saveBar: set('saveBar'),
|
||||
compactMode: set('compactMode'),
|
||||
cameraSettings: set('cameraSettings'),
|
||||
currentCameraIndex: set('currentCameraIndex'),
|
||||
pipelineResults: set('pipelineResults'),
|
||||
networkSettings: set('networkSettings'),
|
||||
selectedOutputs: set('selectedOutputs'),
|
||||
settings: set('settings'),
|
||||
calibrationData: set('calibrationData'),
|
||||
|
||||
is3D: (state, val) => {
|
||||
state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.is3D = val;
|
||||
solvePNPEnabled: (state, val) => {
|
||||
state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.solvePNPEnabled = val;
|
||||
},
|
||||
|
||||
currentPipelineIndex: (state, val) => {
|
||||
@@ -152,27 +160,50 @@ export default new Vuex.Store({
|
||||
}
|
||||
},
|
||||
|
||||
mutateSettings: (state, payload) => {
|
||||
for (let key in payload) {
|
||||
if (!payload.hasOwnProperty(key)) continue;
|
||||
const value = payload[key];
|
||||
const settings = state.settings;
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
Vue.set(settings, key, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mutatePipelineResults(state, payload) {
|
||||
// Key: index, value: result
|
||||
let newResultArray = [];
|
||||
for (let key in payload) {
|
||||
if (!payload.hasOwnProperty(key)) continue;
|
||||
const index = parseInt(key);
|
||||
newResultArray[index] = payload[key];
|
||||
if(index === state.currentCameraIndex) {
|
||||
Vue.set(state, 'pipelineResults', payload[key])
|
||||
}
|
||||
}
|
||||
|
||||
Vue.set(state, 'pipelineResults', newResultArray)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
mutateCalibrationState: (state, payload) => {
|
||||
for (let key in payload) {
|
||||
if (!payload.hasOwnProperty(key)) continue;
|
||||
const value = payload[key];
|
||||
const calibration = state.calibrationData;
|
||||
if (calibration.hasOwnProperty(key)) {
|
||||
calibration[key] = value
|
||||
}
|
||||
Vue.set(state, 'calibrationData', calibration)
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
isDriverMode: state => state.cameraSettings[state.currentCameraIndex].currentPipelineIndex === -1,
|
||||
pipelineSettings: state => state.pipelineSettings,
|
||||
streamAddress: state =>
|
||||
["http://" + location.hostname + ":" + state.cameraSettings[state.currentCameraIndex].inputStreamPort + "/stream.mjpg",
|
||||
"http://" + location.hostname + ":" + state.cameraSettings[state.currentCameraIndex].outputStreamPort + "/stream.mjpg"],
|
||||
targets: state => state.pipelineResults.length,
|
||||
currentPipelineResults: state =>
|
||||
state.pipelineResults[state.cameraSettings[state.currentCameraIndex].currentPipelineIndex],
|
||||
currentPipelineResults: state => {
|
||||
return state.pipelineResults;
|
||||
},
|
||||
cameraList: state => state.cameraSettings.map(it => it.nickname),
|
||||
currentCameraSettings: state => state.cameraSettings[state.currentCameraIndex],
|
||||
currentCameraIndex: state => state.currentCameraIndex,
|
||||
@@ -182,6 +213,6 @@ export default new Vuex.Store({
|
||||
return Object.values(state.cameraSettings[state.currentCameraIndex].videoFormatList); // convert to a list
|
||||
},
|
||||
pipelineList: state => state.cameraSettings[state.currentCameraIndex].pipelineNicknames,
|
||||
currentCameraFPS: state => state.pipelineResults[state.currentCameraIndex].fps
|
||||
calibrationList: state => state.cameraSettings[state.currentCameraIndex].calibrations,
|
||||
}
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
export default {
|
||||
state: {
|
||||
netmask: "",
|
||||
ip: "",
|
||||
teamNumber: "",
|
||||
connectionType: "",
|
||||
gateway: ""
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {},
|
||||
getters: {
|
||||
pipeline: state => {
|
||||
return state
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
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],
|
||||
fullness: [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) => {
|
||||
state.isBinary = value
|
||||
},
|
||||
mutatePipeline: (state, {key, value}) => {
|
||||
Vue.set(state, key, value)
|
||||
}
|
||||
|
||||
},
|
||||
actions: {},
|
||||
getters: {
|
||||
pipeline: state => {
|
||||
return state
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
cols="12"
|
||||
md="7"
|
||||
>
|
||||
<!-- Camera card -->
|
||||
<v-card
|
||||
class="mb-3 pr-6 pb-3"
|
||||
color="primary"
|
||||
@@ -23,14 +24,14 @@
|
||||
@input="handleInput('currentCamera',currentCameraIndex)"
|
||||
/>
|
||||
<CVnumberinput
|
||||
v-if="cameraSettings.isFovConfigurable"
|
||||
v-model="cameraSettings.fov"
|
||||
tooltip="Field of view (in degrees) of the camera measured across the diagonal of the frame"
|
||||
:tooltip="cameraSettings.isFovConfigurable ? 'Field of view (in degrees) of the camera measured across the diagonal of the frame' : 'This setting is managed by a vendor'"
|
||||
name="Diagonal FOV"
|
||||
:disabled="!cameraSettings.isFovConfigurable"
|
||||
/>
|
||||
<br>
|
||||
<CVnumberinput
|
||||
v-model="cameraSettings.tilt"
|
||||
v-model="cameraSettings.tiltDegrees"
|
||||
name="Camera pitch"
|
||||
tooltip="How many degrees above the horizontal the physical camera is tilted"
|
||||
:step="0.01"
|
||||
@@ -49,52 +50,175 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- Calibration card -->
|
||||
<v-card
|
||||
class="pr-6 pb-3"
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card-title>Camera Calibration</v-card-title>
|
||||
|
||||
<div class="ml-5">
|
||||
<v-row>
|
||||
<v-col cols="8">
|
||||
<!-- Calibration input -->
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<CVselect
|
||||
v-model="resolutionIndex"
|
||||
v-model="selectedFilteredResIndex"
|
||||
name="Resolution"
|
||||
select-cols="7"
|
||||
:list="stringResolutionList"
|
||||
:disabled="isCalibrating"
|
||||
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="boardType"
|
||||
name="Board Type"
|
||||
select-cols="7"
|
||||
:list="['Chessboard', 'Dot Grid']"
|
||||
:disabled="isCalibrating"
|
||||
tooltip="Calibration board pattern to use"
|
||||
/>
|
||||
<CVnumberinput
|
||||
v-model="squareSizeIn"
|
||||
name="Pattern Spacing (in)"
|
||||
label-cols="5"
|
||||
tooltip="Spacing between pattern features in inches"
|
||||
:disabled="isCalibrating"
|
||||
/>
|
||||
<CVnumberinput
|
||||
v-model="boardWidth"
|
||||
name="Board width"
|
||||
label-cols="5"
|
||||
tooltip="Width of the board in dots or corners; with the standard chessboard, this is usually 7"
|
||||
:disabled="isCalibrating"
|
||||
/>
|
||||
<CVnumberinput
|
||||
v-model="boardHeight"
|
||||
name="Board height"
|
||||
label-cols="5"
|
||||
tooltip="Height of the board in dots or corners; with the standard chessboard, this is usually 7"
|
||||
:disabled="isCalibrating"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Calibrated table -->
|
||||
<v-col
|
||||
cols="4"
|
||||
align-self="center"
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<CVnumberinput
|
||||
v-model="squareSize"
|
||||
name="Square Size (in)"
|
||||
tooltip="Length of one side of the checkerboard's square in inches"
|
||||
label-cols="unset"
|
||||
<v-row
|
||||
align="start"
|
||||
class="pb-4"
|
||||
>
|
||||
<v-simple-table
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
>
|
||||
<thead style="font-size: 1.25rem;">
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<tooltipped-label text="Resolution" />
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<tooltipped-label
|
||||
tooltip="Average reprojection error of the calibration, in pixels"
|
||||
text="Mean Error"
|
||||
/>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<tooltipped-label
|
||||
tooltip="Standard deviation of the mean error, in pixels"
|
||||
text="Standard Deviation"
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(value, index) in filteredResolutionList"
|
||||
:key="index"
|
||||
>
|
||||
<td> {{ value.width }} X {{ value.height }} </td>
|
||||
<td>
|
||||
{{ isCalibrated(value) ? value.mean.toFixed(2) + "px" : "—" }}
|
||||
</td>
|
||||
<td> {{ isCalibrated(value) ? value.standardDeviation.toFixed(2) + "px" : "—" }} </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
</v-row>
|
||||
<v-row justify="center">
|
||||
<v-chip
|
||||
v-show="isCalibrating"
|
||||
label
|
||||
:color="snapshotAmount < 25 ? 'grey' : 'secondary'"
|
||||
>
|
||||
Snapshots: {{ snapshotAmount }} of at least {{ minSnapshots }}
|
||||
</v-chip>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="isCalibrating">
|
||||
<v-col
|
||||
cols="12"
|
||||
class="pt-0"
|
||||
>
|
||||
<CVslider
|
||||
v-model="$store.getters.currentPipelineSettings.cameraExposure"
|
||||
name="Exposure"
|
||||
:min="0"
|
||||
:max="100"
|
||||
slider-cols="8"
|
||||
@input="e => handlePipelineUpdate('cameraExposure', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="this.$store.getters.currentPipelineSettings.cameraBrightness"
|
||||
name="Brightness"
|
||||
:min="0"
|
||||
:max="100"
|
||||
slider-cols="8"
|
||||
@input="e => handlePipelineUpdate('cameraBrightness', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="$store.getters.currentPipelineSettings.cameraGain !== -1"
|
||||
v-model="$store.getters.currentPipelineSettings.cameraGain"
|
||||
name="Gain"
|
||||
:min="0"
|
||||
:max="100"
|
||||
slider-cols="8"
|
||||
@input="e => handlePipelineUpdate('cameraGain', e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-col align-self="center">
|
||||
<v-btn
|
||||
small
|
||||
color="secondary"
|
||||
:disabled="checkResolution"
|
||||
style="width: 100%;"
|
||||
:disabled="disallowCalibration"
|
||||
@click="sendCalibrationMode"
|
||||
>
|
||||
{{ calibrationModeButton.text }}
|
||||
{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-col align-self="center">
|
||||
<v-btn
|
||||
small
|
||||
color="red"
|
||||
:color="hasEnough ? 'accent' : 'red'"
|
||||
:class="hasEnough ? 'black--text' : 'white---text'"
|
||||
style="width: 100%;"
|
||||
:disabled="checkCancellation"
|
||||
@click="sendCalibrationFinish"
|
||||
>
|
||||
{{ cancellationModeButton.text }}
|
||||
{{ hasEnough ? "End Calibration" : "Cancel Calibration" }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
@@ -102,6 +226,7 @@
|
||||
color="accent"
|
||||
small
|
||||
outlined
|
||||
style="width: 100%;"
|
||||
@click="downloadBoard"
|
||||
>
|
||||
<v-icon left>
|
||||
@@ -113,52 +238,10 @@
|
||||
ref="calibrationFile"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="require('../assets/chessboard.png')"
|
||||
download="Calibration Board.png"
|
||||
download="chessboard.png"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="isCalibrating">
|
||||
<v-col>
|
||||
<span>Snapshot Amount: {{ snapshotAmount }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div v-if="isCalibrating">
|
||||
<v-checkbox
|
||||
v-model="isAdvanced"
|
||||
label="Advanced Menu"
|
||||
dark
|
||||
/>
|
||||
<div v-if="isAdvanced">
|
||||
<CVslider
|
||||
v-model="$store.getters.pipeline.exposure"
|
||||
name="Exposure"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="e=> handleInput('exposure', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="$store.getters.pipeline.brightness"
|
||||
name="Brightness"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="e=> handleInput('brightness', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="$store.getters.pipeline.gain !== -1"
|
||||
v-model="$store.getters.pipeline.gain"
|
||||
name="Gain"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="e=> handleInput('gain', e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="$store.getters.pipeline.videoModeIndex"
|
||||
name="FPS"
|
||||
:list="stringFpsList"
|
||||
@input="changeFps"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -186,207 +269,265 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVselect from '../components/common/cv-select';
|
||||
import CVnumberinput from '../components/common/cv-number-input';
|
||||
import CVslider from '../components/common/cv-slider';
|
||||
import CVimage from "../components/common/cv-image";
|
||||
import CVselect from '../components/common/cv-select';
|
||||
import CVnumberinput from '../components/common/cv-number-input';
|
||||
import CVslider from '../components/common/cv-slider';
|
||||
import CVimage from "../components/common/cv-image";
|
||||
import TooltippedLabel from "../components/common/cv-tooltipped-label";
|
||||
|
||||
export default {
|
||||
name: 'Cameras',
|
||||
components: {
|
||||
CVselect,
|
||||
CVnumberinput,
|
||||
CVslider,
|
||||
CVimage
|
||||
export default {
|
||||
name: 'Cameras',
|
||||
components: {
|
||||
TooltippedLabel,
|
||||
CVselect,
|
||||
CVnumberinput,
|
||||
CVslider,
|
||||
CVimage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: ""
|
||||
},
|
||||
snack: false,
|
||||
filteredVideomodeIndex: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disallowCalibration() {
|
||||
return !(this.calibrationData.boardType === 0 || this.calibrationData.boardType === 1);
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCalibrating: false,
|
||||
resolutionIndex: undefined,
|
||||
calibrationModeButton: {
|
||||
text: "Start Calibration",
|
||||
color: "green"
|
||||
},
|
||||
cancellationModeButton: {
|
||||
text: "Cancel Calibration",
|
||||
color: "red"
|
||||
},
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: ""
|
||||
},
|
||||
squareSize: 1.0,
|
||||
snapshotAmount: 0,
|
||||
hasEnough: false,
|
||||
snack: false,
|
||||
isAdvanced: false
|
||||
checkCancellation() {
|
||||
if (this.isCalibrating) {
|
||||
return false
|
||||
} else if (this.disallowCalibration) {
|
||||
return true;
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checkResolution() {
|
||||
return this.resolutionIndex === undefined;
|
||||
currentCameraIndex: {
|
||||
get() {
|
||||
return this.$store.state.currentCameraIndex;
|
||||
},
|
||||
checkCancellation() {
|
||||
if (this.isCalibrating) {
|
||||
return false
|
||||
} else if (this.checkResolution) {
|
||||
return true;
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
currentCameraIndex: {
|
||||
get() {
|
||||
return this.$store.state.currentCameraIndex;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('currentCameraIndex', value);
|
||||
}
|
||||
},
|
||||
filteredResolutionList: {
|
||||
get() {
|
||||
let tmp_list = [];
|
||||
for (let i in this.$store.state.resolutionList) {
|
||||
if (this.$store.state.resolutionList.hasOwnProperty(i)) {
|
||||
let res = JSON.parse(JSON.stringify(this.$store.state.resolutionList[i]));
|
||||
if (!tmp_list.some(e => e.width === res.width && e.height === res.height)) {
|
||||
res['actualIndex'] = parseInt(i);
|
||||
tmp_list.push(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmp_list;
|
||||
}
|
||||
},
|
||||
filteredFpsList() {
|
||||
let selectedRes = this.$store.state.resolutionList[this.resolutionIndex];
|
||||
let tmpList = [];
|
||||
for (let i in this.$store.state.resolutionList) {
|
||||
if (this.$store.state.resolutionList.hasOwnProperty(i)) {
|
||||
let res = JSON.parse(JSON.stringify(this.$store.state.resolutionList[i]));
|
||||
if (!tmpList.some(e => e['fps'] === res['fps'])) {
|
||||
if (res.width === selectedRes.width && res.height === selectedRes.height) {
|
||||
res['actualIndex'] = parseInt(i);
|
||||
tmpList.push(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmpList;
|
||||
},
|
||||
stringFpsList() {
|
||||
let tmp = [];
|
||||
for (let i of this.filteredFpsList) {
|
||||
tmp.push(i['fps']);
|
||||
}
|
||||
return tmp;
|
||||
},
|
||||
stringResolutionList: {
|
||||
get() {
|
||||
let tmp = [];
|
||||
for (let i of this.filteredResolutionList) {
|
||||
tmp.push(`${i['width']} X ${i['height']}`)
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
},
|
||||
cameraSettings: {
|
||||
get() {
|
||||
return this.$store.getters.currentCameraSettings;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('cameraSettings', value);
|
||||
}
|
||||
set(value) {
|
||||
this.$store.commit('currentCameraIndex', value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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.$store.getters.pipeline['videoModeIndex']]['actualIndex']);
|
||||
},
|
||||
sendCameraSettings() {
|
||||
const self = this;
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", this.cameraSettings).then(
|
||||
function (response) {
|
||||
if (response.status === 200) {
|
||||
self.$store.state.saveBar = true;
|
||||
|
||||
// Makes sure there's only one entry per resolution
|
||||
filteredResolutionList: {
|
||||
get() {
|
||||
let list = this.$store.getters.videoFormatList;
|
||||
let filtered = [];
|
||||
list.forEach((it, i) => {
|
||||
if (!filtered.some(e => e.width === it.width && e.height === it.height)) {
|
||||
it['index'] = i;
|
||||
const calib = this.getCalibrationCoeffs(it);
|
||||
if(calib != null) {
|
||||
it['standardDeviation'] = calib.standardDeviation;
|
||||
it['mean'] = calib.perViewErrors.reduce((a, b) => a + b) / calib.perViewErrors.length;
|
||||
}
|
||||
filtered.push(it);
|
||||
}
|
||||
)
|
||||
},
|
||||
sendCalibrationMode() {
|
||||
const self = this;
|
||||
let data = {};
|
||||
let connection_string = "/api/settings/";
|
||||
if (self.isCalibrating === true) {
|
||||
connection_string += "snapshot"
|
||||
} else {
|
||||
connection_string += "startCalibration";
|
||||
data['resolution'] = this.filteredResolutionList[this.resolutionIndex].actualIndex;
|
||||
data['squareSize'] = this.squareSize;
|
||||
self.hasEnough = false;
|
||||
}
|
||||
this.axios.post("http://" + this.$address + connection_string, data).then(
|
||||
function (response) {
|
||||
if (response.status === 200) {
|
||||
if (self.isCalibrating) {
|
||||
self.snapshotAmount = response.data['snapshotCount'];
|
||||
self.hasEnough = response.data['hasEnough'];
|
||||
if (self.hasEnough === true) {
|
||||
self.cancellationModeButton.text = "Finish Calibration";
|
||||
self.cancellationModeButton.color = "green";
|
||||
}
|
||||
} else {
|
||||
self.calibrationModeButton.text = "Take Snapshot";
|
||||
self.isCalibrating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
sendCalibrationFinish() {
|
||||
const self = this;
|
||||
let connection_string = "/api/settings/endCalibration";
|
||||
let data = {};
|
||||
data['squareSize'] = this.squareSize;
|
||||
self.axios.post("http://" + this.$address + connection_string, data).then((response) => {
|
||||
if (response.status === 200) {
|
||||
self.snackbar = {
|
||||
color: "success",
|
||||
text: "calibration successful. \n" +
|
||||
"accuracy: " + response.data['accuracy'].toFixed(5)
|
||||
};
|
||||
self.snack = true;
|
||||
}
|
||||
self.isCalibrating = false;
|
||||
self.hasEnough = false;
|
||||
self.snapshotAmount = 0;
|
||||
self.calibrationModeButton.text = "Start Calibration";
|
||||
self.cancellationModeButton.text = "Cancel Calibration";
|
||||
self.cancellationModeButton.color = "red";
|
||||
}
|
||||
).catch(() => {
|
||||
self.snackbar = {
|
||||
color: "error",
|
||||
text: "calibration failed"
|
||||
};
|
||||
self.snack = true;
|
||||
self.isCalibrating = false;
|
||||
self.hasEnough = false;
|
||||
self.snapshotAmount = 0;
|
||||
self.calibrationModeButton.text = "Start Calibration";
|
||||
self.cancellationModeButton.text = "Cancel Calibration";
|
||||
self.cancellationModeButton.color = "red";
|
||||
});
|
||||
filtered.sort((a, b) => (b.width + b.height) - (a.width + a.height));
|
||||
return filtered
|
||||
}
|
||||
},
|
||||
|
||||
stringResolutionList: {
|
||||
get() {
|
||||
return this.filteredResolutionList.map(res => `${res['width']} X ${res['height']}`);
|
||||
}
|
||||
},
|
||||
|
||||
cameraSettings: {
|
||||
get() {
|
||||
return this.$store.getters.currentCameraSettings;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('cameraSettings', value);
|
||||
}
|
||||
},
|
||||
|
||||
boardType: {
|
||||
get() {
|
||||
return this.calibrationData.boardType
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('mutateCalibrationState', {['boardType']: value});
|
||||
}
|
||||
},
|
||||
snapshotAmount: {
|
||||
get() {
|
||||
return this.calibrationData.count
|
||||
}
|
||||
},
|
||||
minSnapshots: {
|
||||
get() {
|
||||
return this.calibrationData.minCount
|
||||
}
|
||||
},
|
||||
hasEnough: {
|
||||
get() {
|
||||
return this.calibrationData.hasEnough
|
||||
}
|
||||
},
|
||||
boardWidth: {
|
||||
get() {
|
||||
return this.calibrationData.patternWidth
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('mutateCalibrationState', {['patternWidth']: value})
|
||||
}
|
||||
},
|
||||
boardHeight: {
|
||||
get() {
|
||||
return this.calibrationData.patternHeight
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('mutateCalibrationState', {['patternHeight']: value})
|
||||
}
|
||||
},
|
||||
squareSizeIn: {
|
||||
get() {
|
||||
return this.calibrationData.squareSizeIn
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('mutateCalibrationState', {['squareSizeIn']: value})
|
||||
}
|
||||
},
|
||||
calibrationData: {
|
||||
get() {
|
||||
return this.$store.state.calibrationData
|
||||
}
|
||||
},
|
||||
isCalibrating: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineIndex === -2;
|
||||
}
|
||||
},
|
||||
|
||||
selectedFilteredResIndex: {
|
||||
get() {
|
||||
return this.filteredVideomodeIndex
|
||||
},
|
||||
set(i) {
|
||||
console.log(`Setting filtered index to ${i}`)
|
||||
this.filteredVideomodeIndex = i
|
||||
this.$store.commit('mutateCalibrationState', {['videoModeIndex']: this.filteredResolutionList[i].index});
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
getCalibrationCoeffs(resolution) {
|
||||
const calList = this.$store.getters.calibrationList;
|
||||
let ret = null;
|
||||
calList.forEach(cal => {
|
||||
if(cal.width === resolution.width && cal.height === resolution.height) {
|
||||
ret = cal
|
||||
}
|
||||
})
|
||||
return ret;
|
||||
},
|
||||
downloadBoard() {
|
||||
this.axios.get("http://" + this.$address + require('../assets/chessboard.png'), {responseType: 'blob'}).then((response) => {
|
||||
require('downloadjs')(response.data, "Calibration Board", "image/png")
|
||||
})
|
||||
},
|
||||
sendCameraSettings() {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", {
|
||||
"settings": this.cameraSettings,
|
||||
"index": this.$store.state.currentCameraIndex
|
||||
}).then(
|
||||
function (response) {
|
||||
if (response.status === 200) {
|
||||
this.$store.state.saveBar = true;
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
isCalibrated(resolution) {
|
||||
return this.$store.getters.currentCameraSettings.calibrations
|
||||
.some(e => e.width === resolution.width && e.height === resolution.height)
|
||||
},
|
||||
|
||||
sendCalibrationMode() {
|
||||
let data = {
|
||||
['cameraIndex']: this.$store.state.currentCameraIndex
|
||||
};
|
||||
|
||||
if (this.isCalibrating === true) {
|
||||
data['takeCalibrationSnapshot'] = true
|
||||
} else {
|
||||
const calData = this.calibrationData
|
||||
calData.isCalibrating = true
|
||||
data['startPnpCalibration'] = calData
|
||||
|
||||
console.log("starting calibration with index " + calData.videoModeIndex)
|
||||
}
|
||||
|
||||
this.$socket.send(this.$msgPack.encode(data));
|
||||
},
|
||||
sendCalibrationFinish() {
|
||||
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex)
|
||||
|
||||
this.snackbar.text = "Calibrating...";
|
||||
this.snackbar.color = "secondary"
|
||||
this.snack = true;
|
||||
|
||||
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", this.$store.getters.currentCameraIndex)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Calibration successful! \n" +
|
||||
"Standard deviation: " + response.data.toFixed(5)
|
||||
};
|
||||
this.snack = true;
|
||||
}
|
||||
else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Calibration Failed!"
|
||||
};
|
||||
this.snack = true;
|
||||
}
|
||||
}
|
||||
).catch(() => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Calibration Failed!"
|
||||
};
|
||||
this.snack = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="" scoped>
|
||||
<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>
|
||||
@@ -25,12 +25,7 @@
|
||||
class="pb-0 mb-0 pl-4 pt-1"
|
||||
style="height: 15%; min-height: 50px;"
|
||||
>
|
||||
<div>
|
||||
Cameras <span
|
||||
class="pl-2 caption grey--text text--lighten-2"
|
||||
style="line-height: 220%; display: inline-block; vertical-align: bottom;"
|
||||
>{{ parseFloat(fps).toFixed(2) }} FPS</span>
|
||||
</div>
|
||||
Cameras
|
||||
<v-switch
|
||||
v-model="driverMode"
|
||||
label="Driver Mode"
|
||||
@@ -62,7 +57,6 @@
|
||||
:color-picking="$store.state.colorPicking && idx == 0"
|
||||
@click="onImageClick"
|
||||
/>
|
||||
<!-- <span class="fps-indicator">{{ parseFloat(fps).toFixed(2) }}</span>-->
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -166,7 +160,7 @@
|
||||
slider-color="accent"
|
||||
>
|
||||
<v-tab
|
||||
v-for="(tab, i) in tabs.filter(it => it.name !== '3D' || $store.getters.currentPipelineSettings.is3D)"
|
||||
v-for="(tab, i) in tabs.filter(it => it.name !== '3D' || $store.getters.currentPipelineSettings.solvePNPEnabled)"
|
||||
:key="i"
|
||||
>
|
||||
{{ tab.name }}
|
||||
@@ -299,11 +293,11 @@
|
||||
},
|
||||
processingMode: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.is3D ? 1 : 0;
|
||||
return this.$store.getters.currentPipelineSettings.solvePNPEnabled ? 1 : 0;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.getters.currentPipelineSettings.is3D = value === 1;
|
||||
this.handlePipelineUpdate("is3D", value === 1);
|
||||
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
|
||||
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
|
||||
}
|
||||
},
|
||||
driverMode: {
|
||||
@@ -349,11 +343,6 @@
|
||||
// this.handlePipelineUpdate('selectedOutputs', valToCommit);
|
||||
}
|
||||
},
|
||||
fps: {
|
||||
get() {
|
||||
return this.$store.getters.currentCameraFPS;
|
||||
}
|
||||
},
|
||||
latency: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineResults.latency;
|
||||
@@ -384,14 +373,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fps-indicator {
|
||||
position: absolute;
|
||||
top: 2%;
|
||||
left: 2%;
|
||||
font-size: 1.75rem;
|
||||
text-shadow: 1px 1px 5px rgba(1, 1, 1, 0.65);
|
||||
}
|
||||
|
||||
th {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
|
||||
@@ -22,15 +22,15 @@
|
||||
@change="onModelSelect"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="value.accuracy"
|
||||
v-model="cornerDetectionAccuracyPercentage"
|
||||
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)"
|
||||
@input="handlePipelineData('cornerDetectionAccuracyPercentage')"
|
||||
@rollback="e => rollback('cornerDetectionAccuracyPercentage', e)"
|
||||
/>
|
||||
<mini-map
|
||||
class="miniMapClass"
|
||||
@@ -59,20 +59,25 @@
|
||||
CVslider,
|
||||
miniMap
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
selectedModel: null,
|
||||
FRCtargets: null,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
color: "Success",
|
||||
text: ""
|
||||
},
|
||||
snack: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cornerDetectionAccuracyPercentage: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.cornerDetectionAccuracyPercentage
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"cornerDetectionAccuracyPercentage": val});
|
||||
}
|
||||
},
|
||||
targets: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineResults.targets;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<th class="text-center">
|
||||
Target
|
||||
</th>
|
||||
<template v-if="!$store.getters.currentPipelineSettings.is3D">
|
||||
<template v-if="!$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
Pitch
|
||||
</th>
|
||||
@@ -32,7 +32,7 @@
|
||||
<th class="text-center">
|
||||
Area
|
||||
</th>
|
||||
<template v-if="$store.getters.currentPipelineSettings.is3D">
|
||||
<template v-if="$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
X
|
||||
</th>
|
||||
@@ -51,17 +51,17 @@
|
||||
:key="index"
|
||||
>
|
||||
<td>{{ index }}</td>
|
||||
<template v-if="!$store.getters.currentPipelineSettings.is3D">
|
||||
<template v-if="!$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<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="$store.getters.currentPipelineSettings.is3D">
|
||||
<template v-if="$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<!-- 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.rotation).toFixed(2) }}°</td>
|
||||
<td>{{ parseFloat(value.pose.rot).toFixed(2) }}°</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
import cvImage from '../components/common/cv-image'
|
||||
import General from "./SettingsViews/General";
|
||||
|
||||
|
||||
export default {
|
||||
name: 'SettingsTab',
|
||||
components: {
|
||||
@@ -89,23 +88,22 @@
|
||||
},
|
||||
methods: {
|
||||
sendGeneralSettings() {
|
||||
const self = this;
|
||||
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings).then(
|
||||
function (response) {
|
||||
if (response.status === 200) {
|
||||
self.snackbar = {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Settings updated successfully"
|
||||
};
|
||||
self.snack = true;
|
||||
this.snack = true;
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
self.snackbar = {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: (error.response || {data: "Couldn't save settings"}).data
|
||||
};
|
||||
self.snack = true;
|
||||
this.snack = true;
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
lg="3"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@@ -25,7 +25,7 @@
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
lg="3"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@@ -38,11 +38,24 @@
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="4"
|
||||
lg="3"
|
||||
>
|
||||
<v-btn
|
||||
color="red"
|
||||
@click="restartDevice"
|
||||
@click="axios.post('http://' + this.$address + '/api/restartProgram')"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-restart
|
||||
</v-icon> Restart Photon
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="3"
|
||||
>
|
||||
<v-btn
|
||||
color="red"
|
||||
@click="axios.post('http://' + this.$address + '/api/restartDevice')"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-restart
|
||||
@@ -71,56 +84,53 @@
|
||||
<a
|
||||
ref="exportSettings"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
href="/api/settings/export"
|
||||
:href="'http://' + this.$address + '/api/settings/photonvision_config.zip'"
|
||||
download="photonvision-settings.zip"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'General',
|
||||
data() {
|
||||
return {
|
||||
export default {
|
||||
name: 'General',
|
||||
data() {
|
||||
return {
|
||||
snack: false,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: ""
|
||||
color: "success",
|
||||
text: ""
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.state.settings.general;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readImportedSettings(event) {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readImportedSettings(event) {
|
||||
let formData = new FormData();
|
||||
formData.append("zipData", event.target.files[0]);
|
||||
this.axios.post("http://" + this.$address + "/api/settings/import", formData,
|
||||
{headers: {"Content-Type": "multipart/form-data"}}).then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Settings imported successfully",
|
||||
};
|
||||
{headers: {"Content-Type": "multipart/form-data"}}).then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Settings imported successfully",
|
||||
};
|
||||
}).catch(() => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Couldn't import settings",
|
||||
}
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Couldn't import settings",
|
||||
}
|
||||
});
|
||||
this.snack = true;
|
||||
},
|
||||
restartDevice() {
|
||||
this.axios.post("http://" + this.$address + "/api/restart");
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.v-btn {
|
||||
width: 100%;
|
||||
}
|
||||
.v-btn {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@
|
||||
name="Team Number"
|
||||
:rules="[v => (v > 0) || 'Team number must be greater than zero', v => (v < 10000) || 'Team number must have fewer than five digits']"
|
||||
/>
|
||||
<template v-if="$store.state.settings.networking.supported">
|
||||
<template v-if="$store.state.settings.networkSettings.supported">
|
||||
<CVradio
|
||||
v-model="settings.connectionType"
|
||||
:list="['DHCP','Static']"
|
||||
@@ -17,12 +17,6 @@
|
||||
:rules="[v => isIPv4(v) || 'Invalid IPv4 address']"
|
||||
name="IP"
|
||||
/>
|
||||
<CVinput
|
||||
v-model="settings.netmask"
|
||||
:input-cols="inputCols"
|
||||
:rules="[v => isSubnetMask(v) || 'Invalid subnet mask']"
|
||||
name="Subnet Mask"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<CVinput
|
||||
@@ -70,7 +64,7 @@
|
||||
return this.settings.connectionType === 0;
|
||||
},
|
||||
settings() {
|
||||
return this.$store.state.settings.networking;
|
||||
return this.$store.state.settings.networkSettings;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
Reference in New Issue
Block a user