Update log viewer, add uncalibrated modal (#108)

Adds a prettier log viewer, with the ability to filter logs. Also warns user and prevents switching into 3d mode if the resolution is uncalibrated.
This commit is contained in:
Matt
2020-09-04 18:18:44 -07:00
committed by GitHub
parent ec9e3dcf79
commit 8a7318f5dd
25 changed files with 1047 additions and 433 deletions

View File

@@ -40,6 +40,7 @@
<v-list-item
link
to="cameras"
ref="camerasTabOpener"
@click="switchToDriverMode()"
>
<v-list-item-icon>
@@ -71,7 +72,6 @@
<v-list-item-title>Documentation</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
v-if="this.$vuetify.breakpoint.mdAndUp"
link
@@ -111,63 +111,43 @@
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-content>
<v-main>
<v-container
fluid
fill-height
>
<v-layout>
<v-flex>
<router-view @save="startTimer" />
<v-snackbar
v-model="saveSnackbar"
:timeout="1000"
top
color="accent"
>
<div style="text-align: center;width: 100%;">
<h4>Saved All changes</h4>
</div>
</v-snackbar>
<div v-if="isLogger">
<keep-alive>
<log-view
class="loggerClass"
:log="log"
/>
</keep-alive>
</div>
<router-view v-on:switch-to-cameras="switchToDriverMode" />
</v-flex>
</v-layout>
</v-container>
</v-content>
</v-main>
<v-dialog
v-model="$store.state.logsOverlay"
width="1500"
dark
>
<logs />
</v-dialog>
</v-app>
</template>
<script>
import logView from '@femessage/log-viewer';
import Logs from "./views/LogsView"
export default {
name: 'App',
components: {
logView
Logs
},
data: () => ({
// Used so that we can switch back to the previously selected pipeline after camera calibration
previouslySelectedIndex: undefined,
timer: undefined,
isLogger: false,
log: "",
}),
computed: {
saveSnackbar: {
get() {
return this.$store.state.saveBar;
},
set(value) {
this.$store.commit("saveBar", value);
}
},
compact: {
get() {
if (this.$store.state.compactMode === undefined) {
@@ -186,8 +166,8 @@
created() {
document.addEventListener("keydown", e => {
switch (e.key) {
case '`' :
this.isLogger = !this.isLogger;
case "`":
this.$store.state.logsOverlay = !this.$store.state.logsOverlay;
break;
case "z":
if (e.ctrlKey && this.$store.getters.canUndo) {
@@ -250,22 +230,12 @@
toggleCompactMode() {
this.compact = !this.compact;
},
saveSettings() {
clearInterval(this.timer);
this.saveSnackbar = true;
this.handleInput("command", "save");
},
startTimer() {
if (this.timer !== undefined) {
clearInterval(this.timer);
}
this.timer = setInterval(this.saveSettings, 4000);
},
logMessage(message, level) {
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)
// eslint-disable-next-line no-unused-vars
logMessage(message, levelInt) {
this.$store.commit('logString', {
['level']: levelInt,
['message']: message
})
},
switchToDriverMode() {
this.previouslySelectedIndex = this.$store.getters.currentPipelineIndex;
@@ -307,16 +277,6 @@
object-fit: contain;
}
.loggerClass {
position: absolute;
bottom: 0;
height: 25% !important;
left: 0;
right: 0;
box-shadow: #282828 0 0 5px 1px;
background-color: #2b2b2b;
}
::-webkit-scrollbar {
width: 0.5em;
border-radius: 5px;
@@ -341,21 +301,23 @@
#title {
color: #ffd843;
}
span {
color: white;
}
</style>
<style>
/* Hack */
/* Hacks */
.v-divider {
border-color: white !important;
}
.v-input {
font-size: 1rem !important;
}
.v-input {
font-size: 1rem !important;
}
/* This is unfortunately the only way to override table background color */
.theme--dark.v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281 !important;
}
</style>
<style lang="scss">

View File

@@ -29,7 +29,6 @@
height: `${this.scale}%`,
cursor: (this.colorPicking ? `url(${require("../../assets/eyedropper.svg")}),` : "") + "default",
};
console.log(ret);
if (this.$vuetify.breakpoint.xl) {
ret["max-height"] = this.maxHeightXl;

View File

@@ -8,6 +8,7 @@
<template v-slot:activator="{ on, attrs }">
<span
style="cursor: text !important;"
class="white--text"
v-bind="attrs"
v-on="on"
>{{ text }}</span>

View File

@@ -5,7 +5,7 @@
align="center"
cols="12"
>
<span class="text--white">Target Location</span>
<span class="white--text">Target Location</span>
<canvas
id="canvasId"
class="mt-2"

View File

@@ -78,6 +78,7 @@
<v-menu
offset-y
auto
v-if="!$store.getters.isDriverMode"
>
<template v-slot:activator="{ on }">
<v-icon

View File

@@ -4,6 +4,7 @@ import Dashboard from "./views/PipelineView";
import Cameras from "./views/CamerasView";
import Settings from "./views/SettingsView";
import Docs from "./views/DocsView";
Vue.use(Router);
export default new Router({

View File

@@ -11,18 +11,14 @@ const set = key => (state, val) => {
export default new Vuex.Store({
modules: {
reflectivePipelineSettings: {
state: {
currentResolutionIndex: 0,
},
},
undoRedo: undoRedo
},
state: {
backendConnected: false,
colorPicking: false,
saveBar: false,
logsOverlay: false,
compactMode: localStorage.getItem("compactMode") === undefined ? undefined : localStorage.getItem("compactMode") === "true", // Compact mode is initially unset on purpose
logMessages: [],
currentCameraIndex: 0,
selectedOutputs: [0, 1], // 0 indicates normal, 1 indicates threshold
cameraSettings: [ // This is a list of objects representing the settings of all cameras
@@ -132,13 +128,17 @@ export default new Vuex.Store({
},
},
mutations: {
saveBar: set('saveBar'),
compactMode: set('compactMode'),
cameraSettings: set('cameraSettings'),
currentCameraIndex: set('currentCameraIndex'),
selectedOutputs: set('selectedOutputs'),
settings: set('settings'),
calibrationData: set('calibrationData'),
logString: (state, newStr) => {
const str = state.logMessages;
str.push(newStr)
Vue.set(state, 'logString', str)
},
solvePNPEnabled: (state, val) => {
state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.solvePNPEnabled = val;
@@ -205,6 +205,11 @@ export default new Vuex.Store({
currentPipelineResults: state => {
return state.pipelineResults;
},
isCalibrated: state => {
let resolution = state.cameraSettings[state.currentCameraIndex].videoFormatList[state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.cameraVideoModeIndex];
return state.cameraSettings[state.currentCameraIndex].calibrations
.some(e => e.width === resolution.width && e.height === resolution.height);
},
cameraList: state => state.cameraSettings.map(it => it.nickname),
currentCameraSettings: state => state.cameraSettings[state.currentCameraIndex],
currentCameraIndex: state => state.currentCameraIndex,

View File

@@ -232,7 +232,7 @@
<v-icon left>
mdi-download
</v-icon>
Download Checkerboard
Download Chessboard
</v-btn>
<a
ref="calibrationFile"
@@ -415,8 +415,8 @@ export default {
return this.filteredVideomodeIndex
},
set(i) {
console.log(`Setting filtered index to ${i}`)
this.filteredVideomodeIndex = i
console.log(`Setting filtered index to ${i}`);
this.filteredVideomodeIndex = i;
this.$store.commit('mutateCalibrationState', {['videoModeIndex']: this.filteredResolutionList[i].index});
}
},
@@ -430,13 +430,13 @@ export default {
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")
})
require('downloadjs')(response.data, "Calibration Board", "image/png");
});
},
sendCameraSettings() {
this.axios.post("http://" + this.$address + "/api/settings/camera", {
@@ -453,7 +453,7 @@ export default {
isCalibrated(resolution) {
return this.$store.getters.currentCameraSettings.calibrations
.some(e => e.width === resolution.width && e.height === resolution.height)
.some(e => e.width === resolution.width && e.height === resolution.height);
},
sendCalibrationMode() {
@@ -464,20 +464,20 @@ export default {
if (this.isCalibrating === true) {
data['takeCalibrationSnapshot'] = true
} else {
const calData = this.calibrationData
calData.isCalibrating = true
data['startPnpCalibration'] = calData
const calData = this.calibrationData;
calData.isCalibrating = true;
data['startPnpCalibration'] = calData;
console.log("starting calibration with index " + calData.videoModeIndex)
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)
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex);
this.snackbar.text = "Calibrating...";
this.snackbar.color = "secondary"
this.snackbar.color = "secondary";
this.snack = true;
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", this.$store.getters.currentCameraIndex)
@@ -525,9 +525,4 @@ export default {
.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>

View File

@@ -0,0 +1,130 @@
<template>
<v-card
dark
class="pt-3"
color="primary"
flat
>
<v-card-title>
View Program Logs
<v-btn
color="secondary"
style="margin-left: auto;"
depressed
@click="download('photonlog.log', rawLogs.map(it => it.message).join('\n'))"
>
<v-icon left>
mdi-download
</v-icon>
Download Log
</v-btn>
</v-card-title>
<div class="pr-6 pl-6">
<v-btn-toggle
v-model="logLevel"
dark
multiple
class="fill mb-4"
>
<v-btn
v-for="(level) in possibleLevelArray"
:key="level"
color="secondary"
class="fill"
>
{{ level }}
</v-btn>
</v-btn-toggle>
<!-- Logs -->
<v-virtual-scroll
:items="logMessageArray"
item-height="50"
height="600"
>
<template v-slot="{ item }">
<div :class="[getColor(item) + '--text', 'log-item']">{{ item.message }}</div>
</template>
</v-virtual-scroll>
</div>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
color="white"
text
@click="$store.state.logsOverlay = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "Logs",
components: {
},
data() {
return {
selectedLevel: [0, 1, 2],
possibleLevelArray: ['ERROR', 'WARN', 'INFO', 'DEBUG'],
colorArray: ['red', 'yellow','green', 'white'],
}
},
computed: {
rawLogs() {
return this.$store.state.logMessages;
},
logMessageArray() {
const logArray = this.$store.state.logMessages;
return logArray.filter(it => this.selectedLevel.includes(it.level));
},
logLevel: {
get() {
return this.selectedLevel
},
set(value) {
this.selectedLevel = value;
}
}
},
methods: {
getColor(message) {
return this.colorArray[message.level];
},
download(filename, text) {
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
}
</script>
<style scoped>
.v-btn-toggle.fill {
width: 100%;
height: 100%;
}
.v-btn-toggle.fill > .v-btn {
width: 25%;
height: 100%;
}
</style>

View File

@@ -82,6 +82,7 @@
align="center"
class="pl-3 pr-3"
>
<!-- -->
<v-col lg="12">
<p style="color: white;">
Processing mode:
@@ -100,6 +101,7 @@
</v-btn>
<v-btn
color="secondary"
@click="on3DClick"
>
<v-icon>mdi-cube-outline</v-icon>
<span>3D</span>
@@ -180,7 +182,7 @@
</v-col>
</v-row>
</v-container>
<!-- snack bar -->
<!-- snack bar and modal -->
<v-snackbar
v-model="snackbar"
:timeout="3000"
@@ -196,6 +198,44 @@
Close
</v-btn>
</v-snackbar>
<v-dialog
v-model="dialog"
width="500"
>
<v-card
color="primary"
dark
>
<v-card-title>
Current resolution not calibrated
</v-card-title>
<v-card-text>
Because the current resolution {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].width }}
x {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].height }}
is not yet calibrated, 3D mode cannot be enabled. Please
<a
href="/#/cameras"
class="white--text"
@click="$emit('switch-to-cameras')"
> visit the Cameras tab</a> to calibrate this resolution. For now, SolvePNP will do nothing.
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
color="white"
text
@click="closeUncalibratedDialog"
>
OK
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
@@ -226,6 +266,8 @@
selectedTabsData: [0, 0, 0, 0],
snackbar: false,
counterData: 0,
dialog: false,
processingModeOverride: false
}
},
computed: {
@@ -293,11 +335,13 @@
},
processingMode: {
get() {
return this.$store.getters.currentPipelineSettings.solvePNPEnabled ? 1 : 0;
return (this.$store.getters.currentPipelineSettings.solvePNPEnabled || this.processingModeOverride) ? 1 : 0;
},
set(value) {
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
if (this.$store.getters.isCalibrated) {
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
}
}
},
driverMode: {
@@ -350,6 +394,11 @@
}
},
methods: {
isCalibrated() {
const resolution = this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex];
return this.$store.getters.currentCameraSettings.calibrations
.some(e => e.width === resolution.width && e.height === resolution.height)
},
onImageClick(event) {
// Only run on the input stream
if (event.target.alt !== "Stream0") return;
@@ -358,6 +407,18 @@
if (ref && ref[0])
ref[0].onClick(event)
},
on3DClick() {
if (!this.$store.getters.isCalibrated) {
this.dialog = true;
this.processingModeOverride = true;
}
},
closeUncalibratedDialog() {
this.dialog = false;
this.processingModeOverride = false;
// this.$store.getters.currentPipelineSettings.solvePNPEnabled = false;
this.handlePipelineUpdate("solvePNPEnabled", false);
}
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<span>Target Manipulation</span>
<span class="white--text">Target Manipulation</span>
<v-divider class="mt-2" />
<CVselect
@@ -31,7 +31,7 @@
@rollback="e=> rollback('outputShowMultipleTargets', e)"
/>
<span>Robot Offset</span>
<span class="white--text">Robot Offset</span>
<v-divider class="mt-2" />
<CVselect
v-model="offsetRobotOffsetMode"

View File

@@ -96,11 +96,6 @@
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
}
},
allow3D: {
get() {
return this.$store.getters.currentCameraSettings.calibrated;
}
}
},
mounted() {
let tmp = [];

View File

@@ -97,9 +97,4 @@
.v-data-table td {
font-family: monospace !important;
}
/** This is unfortunately the only way to override table background color **/
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281;
}
</style>

View File

@@ -67,6 +67,7 @@
v-model="snack"
top
:color="snackbar.color"
timeout="0"
>
<span>{{ snackbar.text }}</span>
</v-snackbar>
@@ -115,15 +116,16 @@ export default {
{headers: {"Content-Type": "multipart/form-data"}}).then(() => {
this.snackbar = {
color: "success",
text: "Settings imported successfully",
text: "Settings imported successfully! Program will now exit...",
};
this.snack = true;
}).catch(() => {
this.snackbar = {
color: "error",
text: "Couldn't import settings",
}
color: "success",
text: "Settings imported successfully! Program will now exit...",
};
this.snack = true;
});
this.snack = true;
},
}
}