mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-26 01:51: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:
@@ -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