[PhotonClient] Update dependencies to latest, update assets, and styling fixes (#767)

Currently, there is a difficult-to-reproduce bug where the backend reports that camera calibration was successful in logs via the logger but then throws an exception causing the backend to return a 500 error code with no request body which causes the frontend to interpret this as a failed calibration attempt. This ultimately leads to the entire instance of photonvision crashing and requiring the entire pi to be restarted. It is believed this issue resides inside the ConfigManager's saving action following the calibration update but is not confirmed.
This commit is contained in:
Sriman Achanta
2023-06-09 13:09:41 -04:00
committed by GitHub
parent f63283e187
commit 7b8fb3385b
46 changed files with 7764 additions and 19050 deletions

View File

@@ -5,7 +5,7 @@
Follow [this](https://nodejs.org/en/) link.
## Project setup
Run this one time, this command downloades the packages the UI uses and it might take a short while
Run this one time, this command downloads the packages the UI uses, and it might take a short while
```
npm install

File diff suppressed because it is too large Load Diff

View File

@@ -8,36 +8,30 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@femessage/log-viewer": "^1.4.2",
"@fontsource/prompt": "^4.5.10",
"@vue/eslint-config-prettier": "^6.0.0",
"axios": "^0.19.2",
"core-js": "^2.6.11",
"downloadjs": "^1.4.7",
"jspdf": "^2.4.0",
"material-design-icons-iconfont": "^5.0.1",
"msgpack5": "^4.2.1",
"three-full": "^28.0.2",
"vue": "^2.6.12",
"vue-axios": "^2.1.5",
"vue-native-websocket": "git+https://git@github.com/PhotonVision/vue-native-websocket.git#5189f29",
"vue-router": "^3.4.3",
"vuetify": "^2.3.10",
"vuex": "^3.5.1"
"axios": "^1.4.0",
"core-js": "^3.30.2",
"jspdf": "^2.5.1",
"msgpack5": "^6.0.2",
"three": "^0.153.0",
"vue": "^2.7.14",
"vue-axios": "^3.5.2",
"vue-router": "^3.6.5",
"vuetify": "^2.6.15",
"vuex": "^3.6.2"
},
"devDependencies": {
"@mdi/font": "^4.9.95",
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-eslint": "^4.5.4",
"@vue/cli-service": "^4.5.4",
"@mdi/font": "^7.2.96",
"@vue/cli-plugin-eslint": "^4.5.19",
"@vue/cli-service": "^4.5.19",
"babel-eslint": "^10.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"papaparse": "^5.3.0",
"sass": "^1.26.10",
"sass-loader": "^7.1.0",
"papaparse": "^5.4.1",
"sass": "^1.62.1",
"sass-loader": "^10.4.1",
"vue-cli-plugin-vuetify": "^0.6.3",
"vue-template-compiler": "^2.6.12",
"vuetify-loader": "^1.6.0"
"vue-template-compiler": "^2.7.14",
"vuetify-loader": "^1.9.2"
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 508 507" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-1279,0)">
<g id="PhotonVision-Icon-BG" transform="matrix(0.264062,0,0,0.469444,1279.5,0)">
<rect x="0" y="0" width="1920" height="1080" style="fill:none;"/>
<clipPath id="_clip1">
<rect x="0" y="0" width="1920" height="1080"/>
</clipPath>
<g clip-path="url(#_clip1)">
<g transform="matrix(4.27015,0,0,2.40196,-20444.8,-3235.56)">
<circle cx="5012.55" cy="1571.77" r="224.918" style="fill:rgb(0,100,146);"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-10313.5)">
<path d="M3055.09,3977.51C3050.3,3984.25 3045,3990.56 3039.21,3996.35C2987.91,4047.65 2917.1,4038.77 2881.16,3976.54C2845.23,3914.3 2857.71,3822.13 2909.01,3770.83C2960.31,3719.53 3031.13,3728.41 3067.06,3790.64C3069.85,3795.48 3072.35,3800.49 3074.56,3805.67L3039.78,3811.64C3012.82,3769.64 2962.9,3764.58 2926.45,3801.04C2888.89,3838.59 2879.76,3906.07 2906.07,3951.63C2932.37,3997.19 2984.22,4003.69 3021.77,3966.14L3021.89,3966.01L3055.09,3977.51ZM3085.02,3841.47C3090.86,3875.56 3086.6,3912.35 3073.22,3944.57L3043.91,3934.42C3056.74,3907.59 3060.53,3875.54 3054.13,3846.78L3085.02,3841.47Z" style="fill:white;"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-3827.86)">
<path d="M2906.78,1571.77L3111.02,1642.48L3116.61,1626.34L3147.2,1664.74L3099.42,1675.99L3105,1659.86L2910.03,1592.35C2908.25,1585.69 2907.18,1578.77 2906.78,1571.77ZM2917.45,1517.07L3114.77,1483.17L3111.88,1466.34L3157.2,1485.21L3120.78,1518.13L3117.88,1501.3L2910.22,1536.97C2911.99,1530.09 2914.41,1523.4 2917.45,1517.07Z" style="fill:rgb(255,216,67);"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -5,8 +5,9 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<title>PhotonVision</title>
<!--suppress HtmlUnknownTarget -->
<link rel="icon" href="<%= BASE_URL %>favicon.svg" type="image/png">
<title>PhotonVision Client</title>
</head>
<body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(255, 216, 68); display: block;" width="600px" height="412px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="75" cy="50" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.9166666666666666s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.9166666666666666s"></animate>
</circle><circle cx="71.65063509461098" cy="62.5" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.8333333333333334s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.8333333333333334s"></animate>
</circle><circle cx="62.5" cy="71.65063509461096" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.75s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.75s"></animate>
</circle><circle cx="50" cy="75" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.6666666666666666s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.6666666666666666s"></animate>
</circle><circle cx="37.50000000000001" cy="71.65063509461098" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.5833333333333334s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.5833333333333334s"></animate>
</circle><circle cx="28.34936490538903" cy="62.5" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.5s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.5s"></animate>
</circle><circle cx="25" cy="50" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.4166666666666667s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.4166666666666667s"></animate>
</circle><circle cx="28.34936490538903" cy="37.50000000000001" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.3333333333333333s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.3333333333333333s"></animate>
</circle><circle cx="37.499999999999986" cy="28.349364905389038" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.25s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.25s"></animate>
</circle><circle cx="49.99999999999999" cy="25" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.16666666666666666s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.16666666666666666s"></animate>
</circle><circle cx="62.5" cy="28.349364905389034" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="-0.08333333333333333s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="-0.08333333333333333s"></animate>
</circle><circle cx="71.65063509461096" cy="37.499999999999986" fill="#89b99a" r="5">
<animate attributeName="r" values="2;2;4;2;2" times="0;0.1;0.2;0.3;1" dur="1s" repeatCount="indefinite" begin="0s"></animate>
<animate attributeName="fill" values="#89b99a;#89b99a;#43a7ce;#89b99a;#89b99a" repeatCount="indefinite" times="0;0.1;0.2;0.3;1" dur="1s" begin="0s"></animate>
</circle>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -9,15 +9,9 @@
margin: 0;
padding: 0;
}
.imgbox {
display: grid;
.img-container {
height: 100%;
width: 100%;
}
.center-fit {
width: 90vw;
margin: auto;
width: 40vw;
}
</style>
@@ -25,18 +19,15 @@
<body>
<hr>
<div class="imgbox">
<img id="streamImg" class="center-fit" src=''>
<div class="img-container">
<img id="streamImg" src='' alt="">
</div>
<hr>
<form id="frm1">
Host <input type="text" id="host" value="photonvision.local"><br>
Port <input type="text" id="port" value="1181"><br>
</form>
<button>Start Stream</button>
<script type="module">
class WebsocketVideoStream{
@@ -82,19 +73,19 @@
}
dispNoStream() {
this.image.src = "loading.gif";
this.image.src = "loading.svg";
}
animationLoop(){
// Update time metrics
var now = window.performance.now();
var timeInState = now - this.dsm_restart_start_time;
const now = window.performance.now();
const timeInState = now - this.dsm_restart_start_time;
// Save previous state
this.dsm_prev_state = this.dsm_cur_state;
// Evaluate state transitions
if(this.serverConnectionActive == false){
if(!this.serverConnectionActive){
//Any state - if the server connection goes false, always transition to disconnected
this.dsm_cur_state = this.DSM_DISCONNECTED;
} else {
@@ -160,35 +151,35 @@
//take current-state or state-transition actions
if(this.dsm_cur_state != this.dsm_prev_state){
if(this.dsm_cur_state !== this.dsm_prev_state){
//Any state transition
console.log("State Change: " + this.dsm_prev_state + " -> " + this.dsm_cur_state);
}
if(this.dsm_cur_state == this.DSM_SHOWING){
if(this.dsm_cur_state === this.DSM_SHOWING){
// Currently in SHOWING
this.dispImageData();
}
if(this.dsm_cur_state != this.DSM_SHOWING && this.dsm_prev_state == this.DSM_SHOWING ){
if(this.dsm_cur_state !== this.DSM_SHOWING && this.dsm_prev_state === this.DSM_SHOWING ){
//Any transition out of showing - no stream
this.dispNoStream();
}
if(this.dsm_cur_state == this.DSM_RESTART_UNSUBSCRIBE){
if(this.dsm_cur_state === this.DSM_RESTART_UNSUBSCRIBE){
// Currently in UNSUBSCRIBE, do the unsubscribe actions
this.stopStream();
this.dsm_restart_start_time = now;
}
if(this.dsm_cur_state == this.DSM_SUBSCRIBE){
if(this.dsm_cur_state === this.DSM_SUBSCRIBE){
// Currently in SUBSCRIBE, do the subscribe actions
this.startStream();
this.dsm_restart_start_time = now;
}
if(this.dsm_cur_state == this.DSM_WAIT_FOR_VALID_PORT){
// Currently waiting for a vaild port to be requested
if(this.dsm_cur_state === this.DSM_WAIT_FOR_VALID_PORT){
// Currently waiting for a valid port to be requested
if(this.newStreamPortReq != null){
this.streamPort = this.newStreamPortReq;
this.newStreamPortReq = null;
@@ -243,7 +234,7 @@
ws_onMessage(e){
if(typeof e.data === 'string'){
//string data from host
//TODO - anything to recieve info here? Maybe "avaialble streams?"
//TODO - anything to receive info here? Maybe "available streams?"
} else {
if(e.data.size > 0){
//binary data - a frame
@@ -275,11 +266,11 @@
}
var stream = null;
let stream = null;
function streamStartRequest() {
var host = document.getElementById("host").value + ":5800";
var port = document.getElementById("port").value;
const host = document.getElementById("host").value + ":5800";
const port = document.getElementById("port").value;
if(stream == null){
stream = new WebsocketVideoStream("streamImg",port,host);
} else {
@@ -296,15 +287,15 @@
const urlParams = new URLSearchParams(queryString);
const port_in = urlParams.get('port')
const host_in = urlParams.get('host')
if(port_in != ""){
if(port_in !== ""){
document.getElementById("port").value = port_in;
}
if(host_in != ""){
if(host_in !== ""){
document.getElementById("host").value = host_in;
}
if(port_in != "" && host_in != ""){
if(port_in !== "" && host_in !== ""){
streamStartRequest(); //we got valid inputs, auto-start the stream
}
@@ -312,6 +303,4 @@
</script>
</body>
</html>

View File

@@ -1,17 +1,40 @@
<template>
<v-app>
<!-- Although most of the app runs with the "light" theme, the navigation drawer needs to have white text and icons so it uses the dark theme-->
<v-navigation-drawer dark app permanent :mini-variant="compact" color="primary">
<v-navigation-drawer
dark
app
permanent
:mini-variant="compact"
color="primary"
>
<v-list>
<!-- List item for the heading; note that there are some tricks in setting padding and image width make things look right -->
<v-list-item :class="compact ? 'pr-0 pl-0' : ''">
<v-list-item
:class="compact ? 'pr-0 pl-0' : ''"
style="display: flex; justify-content: center"
>
<v-list-item-icon class="mr-0">
<img v-if="!compact" class="logo" src="./assets/logoLarge.png">
<img v-else class="logo" src="./assets/logoSmall.png">
<img
v-if="!compact"
class="logo"
src="@/assets/logos/logoLarge.svg"
alt="large logo"
>
<img
v-else
class="logo"
src="@/assets/logos/logoSmall.svg"
alt="small logo"
>
</v-list-item-icon>
</v-list-item>
<v-list-item link to="dashboard" @click="rollbackPipelineIndex()">
<v-list-item
link
to="dashboard"
@click="rollbackPipelineIndex()"
>
<v-list-item-icon>
<v-icon>mdi-view-dashboard</v-icon>
</v-list-item-icon>
@@ -19,7 +42,12 @@
<v-list-item-title>Dashboard</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item ref="camerasTabOpener" link to="cameras" @click="switchToDriverMode()">
<v-list-item
ref="camerasTabOpener"
link
to="cameras"
@click="switchToDriverMode()"
>
<v-list-item-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
@@ -27,15 +55,22 @@
<v-list-item-title>Cameras</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="settings" @click="switchToSettingsTab()">
<v-list-item
link
to="settings"
@click="switchToSettingsTab()"
>
<v-list-item-icon>
<v-icon>mdi-settings</v-icon>
<v-icon>mdi-cog</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Settings</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="docs">
<v-list-item
link
to="docs"
>
<v-list-item-icon>
<v-icon>mdi-bookshelf</v-icon>
</v-list-item-icon>
@@ -43,7 +78,11 @@
<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 @click.stop="toggleCompactMode">
<v-list-item
v-if="this.$vuetify.breakpoint.mdAndUp"
link
@click.stop="toggleCompactMode"
>
<v-list-item-icon>
<v-icon v-if="compact">
mdi-chevron-right
@@ -63,41 +102,60 @@
<v-icon v-if="$store.state.settings.networkSettings.runNTServer">
mdi-server
</v-icon>
<img v-else-if="$store.state.ntConnectionInfo.connected" src="@/assets/robot.svg" alt="">
<img v-else class="pulse" style="border-radius: 100%" src="@/assets/robot-off.svg" alt="">
<v-icon v-else-if="$store.state.ntConnectionInfo.connected">
mdi-robot
</v-icon>
<v-icon
v-else
style="border-radius: 100%"
>
mdi-robot-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-if="$store.state.settings.networkSettings.runNTServer" class="text-wrap">
NetworkTables server running for {{ $store.state.ntConnectionInfo.clients ?
$store.state.ntConnectionInfo.clients : 'zero'
}} clients!
<v-list-item-title
v-if="$store.state.settings.networkSettings.runNTServer"
class="text-wrap"
>
NetworkTables server running for <span class="accent--text">{{ $store.state.ntConnectionInfo.clients }}</span> clients
</v-list-item-title>
<v-list-item-title v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected"
class="text-wrap">
Robot connected! {{ $store.state.ntConnectionInfo.address }}
<v-list-item-title
v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected"
class="text-wrap"
style="flex-direction: column; display: flex"
>
NetworkTables Server Connected!
<span
class="accent--text"
>
{{ $store.state.ntConnectionInfo.address }}
</span>
</v-list-item-title>
<v-list-item-title v-else class="text-wrap">
Not connected to robot!
<v-list-item-title
v-else
class="text-wrap"
style="flex-direction: column; display: flex"
>
Not connected to NetworkTables Server!
</v-list-item-title>
<router-link v-if="!$store.state.settings.networkSettings.runNTServer" to="settings" class="accent--text"
@click="switchToSettingsTab">
NT server is {{ $store.state.settings.networkSettings.ntServerAddress }}
</router-link>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="$store.state.backendConnected">
mdi-wifi
mdi-server-network
</v-icon>
<v-icon v-else class="pulse" style="border-radius: 100%;">
mdi-wifi-off
<v-icon
v-else
style="border-radius: 100%;"
>
mdi-server-network-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap">
{{ $store.state.backendConnected ? "Backend Connected" : "Trying to connect..." }}
{{ $store.state.backendConnected ? "Backend Connected" : "Trying to connect to Backend" }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
@@ -105,7 +163,10 @@
</v-list>
</v-navigation-drawer>
<v-main>
<v-container fluid fill-height>
<v-container
fluid
fill-height
>
<v-layout>
<v-flex>
<router-view @switch-to-cameras="switchToDriverMode" />
@@ -114,18 +175,35 @@
</v-container>
</v-main>
<v-dialog v-model="$store.state.logsOverlay" width="1500" dark>
<v-dialog
v-model="$store.state.logsOverlay"
width="1500"
dark
>
<logs />
</v-dialog>
<v-dialog v-model="needsTeamNumberSet" width="500" dark persistent>
<v-card dark color="primary" flat>
<v-card-title>No team number set!</v-card-title>
<v-dialog
v-model="needsTeamNumberSet"
width="500"
dark
persistent
>
<v-card
dark
color="primary"
flat
>
<v-card-title>NetworkTables Server Address Not Set</v-card-title>
<v-card-text>
PhotonVision cannot connect to your robot! Please
<router-link to="settings" class="accent--text" @click="switchToSettingsTab">
visit the settings tab
PhotonVision cannot connect to the NetworkTables Server. Please visit the
<router-link
to="settings"
class="accent--text"
@click="switchToSettingsTab"
>
networking settings tab
</router-link>
and set your team number.
and set the NetworkTables Server Address.
</v-card-text>
</v-card>
</v-dialog>
@@ -284,7 +362,7 @@ export default {
@keyframes pulse-animation {
0% {
box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
background-color: rgba(0, 0, 0, 0.2);
}
@@ -310,7 +388,6 @@ export default {
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background-color: #ffd843;
border-radius: 10px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg865"
sodipodi:docname="eyedropper.svg"
inkscape:version="0.92.4 (unknown)">
<metadata
id="metadata871">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs869" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1040"
id="namedview867"
showgrid="false"
inkscape:zoom="35.541667"
inkscape:cx="12.112544"
inkscape:cy="10.171169"
inkscape:window-x="0"
inkscape:window-y="1458"
inkscape:window-maximized="0"
inkscape:current-layer="svg865" />
<g
id="g905"
inkscape:export-xdpi="77.2733"
inkscape:export-ydpi="77.2733">
<path
inkscape:connector-curvature="0"
id="path863"
d="m 12.28,19.4725 -2.13,-2.13 1.42,-1.41 -7.71,-7.71 -1.86,-4.6 1.5,-1.5 4.6,1.86 7.71,7.71 1.41,-1.42 2.13,2.13 -7.07,7.07 m 8.72,-2.59 c 1.17,1.17 1.17,3.07 0,4.24 -1.17,1.17 -3.07,1.17 -4.24,0 l -1.92,-1.92 4.24,-4.24 1.92,1.92 m -14.03,-11.2 -2.47,-1.06 1.06,2.47 7.44,7.43 1.4,-1.4 z" />
<path
inkscape:export-ydpi="161.91951"
inkscape:export-xdpi="161.91951"
d="m 3.5996094,2.6132812 -1.109375,1.109375 1.7246094,4.2636719 7.6503902,7.6503909 a 0.41783994,0.41783994 0 0 1 0,0.591797 l -1.123046,1.115234 1.537109,1.539062 6.480469,-6.480468 -1.53711,-1.53711 -1.115234,1.123047 a 0.41783994,0.41783994 0 0 1 -0.591797,0 L 7.8652344,4.3378906 Z m 0.9101562,1.5917969 a 0.41783994,0.41783994 0 0 1 0.1542969,0.033203 L 7.1347656,5.296875 a 0.41783994,0.41783994 0 0 1 0.1308594,0.089844 l 7.429687,7.4414062 a 0.41783994,0.41783994 0 0 1 0,0.589844 l -1.40039,1.40039 a 0.41783994,0.41783994 0 0 1 -0.589844,0 L 5.265625,7.3867188 A 0.41783994,0.41783994 0 0 1 5.1757812,7.2558594 L 4.1152344,4.7871094 A 0.41783994,0.41783994 0 0 1 4.5097656,4.2050781 Z m 14.5703124,11.3476559 -3.65039,3.650391 1.625,1.625 c 1.010062,1.010062 2.640328,1.010062 3.65039,0 1.010062,-1.010062 1.010062,-2.640327 0,-3.650391 z"
id="path889"
style="fill:#ffffff;stroke-width:21.16535378;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:original="M 3.5 2.1230469 L 2 3.6230469 L 3.859375 8.2226562 L 11.570312 15.931641 L 10.150391 17.341797 L 12.279297 19.472656 L 19.349609 12.402344 L 17.220703 10.273438 L 15.810547 11.693359 L 8.0996094 3.9824219 L 3.5 2.1230469 z M 4.5 4.6230469 L 6.9707031 5.6816406 L 14.400391 13.123047 L 13 14.523438 L 5.5605469 7.0917969 L 4.5 4.6230469 z M 19.080078 14.962891 L 14.839844 19.203125 L 16.759766 21.123047 C 17.929766 22.293047 19.83 22.293047 21 21.123047 C 22.17 19.953047 22.17 18.052813 21 16.882812 L 19.080078 14.962891 z "
inkscape:radius="-0.41779816"
sodipodi:type="inkscape:offset" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>eyedropper</title>
<path
d="M19.35,11.72L17.22,13.85L15.81,12.43L8.1,20.14L3.5,22L2,20.5L3.86,15.9L11.57,8.19L10.15,6.78L12.28,4.65L19.35,11.72M16.76,3C17.93,1.83 19.83,1.83 21,3C22.17,4.17 22.17,6.07 21,7.24L19.08,9.16L14.84,4.92L16.76,3M5.56,17.03L4.5,19.5L6.97,18.44L14.4,11L13,9.6L5.56,17.03Z"
stroke="black"
stroke-width="0.5"
fill="#FFFFFF"
transform="rotate(90, 12, 12)"
/>
</svg>

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1920 680" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-1986,-1146)">
<g id="PhotonVision-Header-noBG" transform="matrix(1,0,0,0.62963,-0.666667,1146)">
<rect x="1986.67" y="0" width="1920" height="1080" style="fill:none;"/>
<g transform="matrix(1.87463,0,0,2.97735,-2372.42,-4617.27)">
<g transform="matrix(136.163,0,0,136.163,2412.48,1719.32)">
<path d="M0.061,0L0.061,-0.7L0.362,-0.7C0.409,-0.7 0.452,-0.689 0.491,-0.668C0.53,-0.647 0.561,-0.617 0.584,-0.58C0.607,-0.542 0.619,-0.498 0.619,-0.449C0.619,-0.4 0.607,-0.356 0.584,-0.318C0.561,-0.279 0.53,-0.249 0.491,-0.228C0.452,-0.207 0.409,-0.196 0.362,-0.196L0.229,-0.196L0.229,0L0.061,0ZM0.229,-0.337L0.343,-0.337C0.362,-0.337 0.38,-0.341 0.397,-0.35C0.413,-0.359 0.426,-0.371 0.436,-0.388C0.446,-0.405 0.451,-0.425 0.451,-0.448C0.451,-0.471 0.446,-0.491 0.436,-0.508C0.426,-0.525 0.413,-0.538 0.397,-0.547C0.38,-0.556 0.362,-0.56 0.343,-0.56L0.229,-0.56L0.229,-0.337Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2500.17,1719.32)">
<path d="M0.061,0L0.061,-0.7L0.229,-0.7L0.229,-0.425L0.502,-0.425L0.502,-0.7L0.67,-0.7L0.67,0L0.502,0L0.502,-0.285L0.229,-0.285L0.229,0L0.061,0Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2599.7,1719.32)">
<path d="M0.4,0.013C0.344,0.013 0.293,0.004 0.248,-0.015C0.203,-0.034 0.164,-0.06 0.131,-0.093C0.098,-0.126 0.073,-0.165 0.056,-0.209C0.038,-0.253 0.029,-0.3 0.029,-0.351C0.029,-0.402 0.038,-0.449 0.056,-0.493C0.073,-0.536 0.098,-0.575 0.131,-0.608C0.164,-0.641 0.203,-0.667 0.248,-0.686C0.293,-0.704 0.344,-0.713 0.4,-0.713C0.455,-0.713 0.506,-0.704 0.552,-0.686C0.597,-0.667 0.636,-0.641 0.669,-0.608C0.701,-0.575 0.726,-0.536 0.744,-0.493C0.762,-0.449 0.771,-0.402 0.771,-0.351C0.771,-0.3 0.762,-0.253 0.744,-0.209C0.726,-0.165 0.701,-0.126 0.669,-0.093C0.636,-0.06 0.597,-0.034 0.552,-0.015C0.506,0.004 0.455,0.013 0.4,0.013ZM0.4,-0.13C0.429,-0.13 0.457,-0.136 0.482,-0.147C0.506,-0.158 0.528,-0.173 0.546,-0.193C0.564,-0.213 0.578,-0.236 0.588,-0.264C0.598,-0.291 0.603,-0.32 0.603,-0.351C0.603,-0.382 0.598,-0.411 0.588,-0.438C0.578,-0.465 0.564,-0.488 0.546,-0.508C0.528,-0.528 0.506,-0.544 0.482,-0.555C0.457,-0.566 0.429,-0.571 0.4,-0.571C0.37,-0.571 0.343,-0.566 0.318,-0.555C0.293,-0.544 0.272,-0.528 0.254,-0.508C0.235,-0.488 0.221,-0.465 0.212,-0.438C0.202,-0.411 0.197,-0.382 0.197,-0.351C0.197,-0.32 0.202,-0.291 0.212,-0.264C0.221,-0.236 0.235,-0.213 0.254,-0.193C0.272,-0.173 0.293,-0.158 0.318,-0.147C0.343,-0.136 0.37,-0.13 0.4,-0.13Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2704.41,1719.32)">
<path d="M0.421,0C0.378,0 0.341,-0.01 0.309,-0.029C0.276,-0.048 0.251,-0.073 0.233,-0.105C0.214,-0.137 0.205,-0.173 0.205,-0.212L0.205,-0.56L0.015,-0.56L0.015,-0.7L0.562,-0.7L0.562,-0.56L0.373,-0.56L0.373,-0.197C0.373,-0.182 0.378,-0.168 0.389,-0.157C0.4,-0.146 0.413,-0.14 0.429,-0.14L0.484,-0.14L0.484,0L0.421,0Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2778.89,1719.32)">
<path d="M0.4,0.013C0.344,0.013 0.293,0.004 0.248,-0.015C0.203,-0.034 0.164,-0.06 0.131,-0.093C0.098,-0.126 0.073,-0.165 0.056,-0.209C0.038,-0.253 0.029,-0.3 0.029,-0.351C0.029,-0.402 0.038,-0.449 0.056,-0.493C0.073,-0.536 0.098,-0.575 0.131,-0.608C0.164,-0.641 0.203,-0.667 0.248,-0.686C0.293,-0.704 0.344,-0.713 0.4,-0.713C0.455,-0.713 0.506,-0.704 0.552,-0.686C0.597,-0.667 0.636,-0.641 0.669,-0.608C0.701,-0.575 0.726,-0.536 0.744,-0.493C0.762,-0.449 0.771,-0.402 0.771,-0.351C0.771,-0.3 0.762,-0.253 0.744,-0.209C0.726,-0.165 0.701,-0.126 0.669,-0.093C0.636,-0.06 0.597,-0.034 0.552,-0.015C0.506,0.004 0.455,0.013 0.4,0.013ZM0.4,-0.13C0.429,-0.13 0.457,-0.136 0.482,-0.147C0.506,-0.158 0.528,-0.173 0.546,-0.193C0.564,-0.213 0.578,-0.236 0.588,-0.264C0.598,-0.291 0.603,-0.32 0.603,-0.351C0.603,-0.382 0.598,-0.411 0.588,-0.438C0.578,-0.465 0.564,-0.488 0.546,-0.508C0.528,-0.528 0.506,-0.544 0.482,-0.555C0.457,-0.566 0.429,-0.571 0.4,-0.571C0.37,-0.571 0.343,-0.566 0.318,-0.555C0.293,-0.544 0.272,-0.528 0.254,-0.508C0.235,-0.488 0.221,-0.465 0.212,-0.438C0.202,-0.411 0.197,-0.382 0.197,-0.351C0.197,-0.32 0.202,-0.291 0.212,-0.264C0.221,-0.236 0.235,-0.213 0.254,-0.193C0.272,-0.173 0.293,-0.158 0.318,-0.147C0.343,-0.136 0.37,-0.13 0.4,-0.13Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2887.69,1719.32)">
<path d="M0.058,0L0.058,-0.422C0.058,-0.482 0.071,-0.534 0.096,-0.577C0.121,-0.62 0.157,-0.654 0.202,-0.677C0.247,-0.7 0.298,-0.712 0.356,-0.712C0.414,-0.712 0.465,-0.7 0.51,-0.677C0.555,-0.654 0.59,-0.62 0.616,-0.577C0.641,-0.534 0.654,-0.482 0.654,-0.422L0.654,0L0.488,0L0.488,-0.431C0.488,-0.457 0.483,-0.481 0.471,-0.502C0.461,-0.523 0.445,-0.54 0.426,-0.552C0.407,-0.564 0.383,-0.57 0.356,-0.57C0.329,-0.57 0.306,-0.564 0.286,-0.552C0.266,-0.54 0.251,-0.523 0.24,-0.502C0.229,-0.481 0.224,-0.457 0.224,-0.431L0.224,0L0.058,0Z" style="fill:rgb(255,216,67);fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(2.05664,0,0,3.26643,-2793.17,-4723.02)">
<g transform="matrix(136.163,0,0,136.163,2412.48,1719.32)">
<path d="M0.341,0.012C0.314,0.012 0.29,0.005 0.269,-0.008C0.248,-0.021 0.231,-0.043 0.22,-0.072L0.001,-0.7L0.133,-0.7L0.319,-0.121C0.322,-0.114 0.325,-0.109 0.328,-0.106C0.331,-0.102 0.336,-0.1 0.341,-0.1C0.346,-0.1 0.351,-0.102 0.355,-0.106C0.358,-0.109 0.361,-0.114 0.363,-0.121L0.555,-0.7L0.681,-0.7L0.462,-0.072C0.452,-0.044 0.436,-0.023 0.414,-0.009C0.393,0.005 0.368,0.012 0.341,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2505.34,1719.32)">
<path d="M0.052,0L0.052,-0.105L0.182,-0.105L0.182,-0.595L0.052,-0.595L0.052,-0.7L0.432,-0.7L0.432,-0.595L0.302,-0.595L0.302,-0.105L0.432,-0.105L0.432,0L0.052,0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2571.25,1719.32)">
<path d="M0.071,0L0.071,-0.105L0.372,-0.105C0.391,-0.105 0.408,-0.11 0.423,-0.119C0.437,-0.128 0.448,-0.141 0.457,-0.156C0.465,-0.171 0.469,-0.186 0.469,-0.203C0.469,-0.221 0.465,-0.237 0.457,-0.252C0.449,-0.267 0.438,-0.278 0.424,-0.287C0.409,-0.296 0.393,-0.3 0.374,-0.3L0.249,-0.3C0.21,-0.3 0.174,-0.308 0.143,-0.325C0.113,-0.341 0.088,-0.364 0.07,-0.393C0.052,-0.422 0.043,-0.457 0.043,-0.497C0.043,-0.537 0.052,-0.572 0.069,-0.603C0.086,-0.633 0.11,-0.657 0.14,-0.674C0.17,-0.691 0.204,-0.7 0.242,-0.7L0.543,-0.7L0.543,-0.595L0.253,-0.595C0.236,-0.595 0.22,-0.591 0.206,-0.582C0.192,-0.573 0.181,-0.562 0.173,-0.548C0.166,-0.534 0.162,-0.519 0.162,-0.503C0.162,-0.486 0.166,-0.471 0.173,-0.458C0.181,-0.444 0.192,-0.433 0.205,-0.425C0.219,-0.416 0.235,-0.412 0.252,-0.412L0.38,-0.412C0.423,-0.412 0.46,-0.404 0.491,-0.387C0.522,-0.37 0.546,-0.347 0.563,-0.318C0.58,-0.289 0.588,-0.255 0.588,-0.217C0.588,-0.172 0.579,-0.134 0.562,-0.102C0.544,-0.069 0.52,-0.044 0.49,-0.027C0.459,-0.009 0.425,0 0.387,0L0.071,0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2656.62,1719.32)">
<path d="M0.052,0L0.052,-0.105L0.182,-0.105L0.182,-0.595L0.052,-0.595L0.052,-0.7L0.432,-0.7L0.432,-0.595L0.302,-0.595L0.302,-0.105L0.432,-0.105L0.432,0L0.052,0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2722.52,1719.32)">
<path d="M0.401,0.013C0.346,0.013 0.296,0.003 0.251,-0.016C0.205,-0.035 0.167,-0.061 0.136,-0.094C0.104,-0.127 0.079,-0.166 0.062,-0.21C0.045,-0.254 0.036,-0.301 0.036,-0.351C0.036,-0.401 0.045,-0.448 0.062,-0.492C0.079,-0.535 0.104,-0.574 0.136,-0.607C0.167,-0.64 0.205,-0.666 0.251,-0.685C0.296,-0.704 0.346,-0.713 0.402,-0.713C0.457,-0.713 0.507,-0.704 0.552,-0.685C0.597,-0.666 0.635,-0.64 0.667,-0.607C0.699,-0.574 0.724,-0.535 0.741,-0.492C0.758,-0.448 0.767,-0.401 0.767,-0.351C0.767,-0.301 0.758,-0.254 0.741,-0.21C0.724,-0.166 0.699,-0.127 0.667,-0.094C0.635,-0.061 0.597,-0.035 0.552,-0.016C0.507,0.003 0.457,0.013 0.401,0.013ZM0.401,-0.093C0.436,-0.093 0.469,-0.099 0.499,-0.112C0.529,-0.125 0.555,-0.143 0.578,-0.167C0.6,-0.19 0.617,-0.218 0.629,-0.249C0.641,-0.28 0.647,-0.314 0.647,-0.351C0.647,-0.388 0.641,-0.422 0.629,-0.453C0.617,-0.484 0.6,-0.511 0.578,-0.535C0.555,-0.558 0.529,-0.576 0.499,-0.589C0.469,-0.602 0.436,-0.608 0.401,-0.608C0.366,-0.608 0.334,-0.602 0.304,-0.589C0.274,-0.576 0.248,-0.558 0.226,-0.535C0.203,-0.511 0.186,-0.484 0.173,-0.453C0.161,-0.422 0.155,-0.388 0.155,-0.351C0.155,-0.314 0.161,-0.28 0.173,-0.249C0.186,-0.218 0.203,-0.19 0.226,-0.167C0.248,-0.143 0.274,-0.125 0.304,-0.112C0.334,-0.099 0.366,-0.093 0.401,-0.093Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(136.163,0,0,136.163,2831.86,1719.32)">
<path d="M0.069,0L0.069,-0.426C0.069,-0.485 0.081,-0.536 0.106,-0.579C0.13,-0.622 0.163,-0.655 0.206,-0.678C0.249,-0.701 0.298,-0.712 0.353,-0.712C0.408,-0.712 0.457,-0.701 0.5,-0.678C0.543,-0.655 0.576,-0.622 0.601,-0.579C0.626,-0.536 0.638,-0.485 0.638,-0.426L0.638,0L0.518,0L0.518,-0.436C0.518,-0.467 0.511,-0.496 0.498,-0.522C0.485,-0.548 0.466,-0.569 0.442,-0.584C0.417,-0.599 0.388,-0.607 0.353,-0.607C0.32,-0.607 0.291,-0.599 0.266,-0.584C0.241,-0.569 0.222,-0.548 0.209,-0.522C0.195,-0.496 0.188,-0.467 0.188,-0.436L0.188,0L0.069,0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(1.47838,0,0,2.34801,-955.236,-8562.95)">
<path d="M3055.09,3977.51C3050.3,3984.25 3045,3990.56 3039.21,3996.35C2987.91,4047.65 2917.1,4038.77 2881.16,3976.54C2845.23,3914.3 2857.71,3822.13 2909.01,3770.83C2960.31,3719.53 3031.13,3728.41 3067.06,3790.64C3069.85,3795.48 3072.35,3800.49 3074.56,3805.67L3039.78,3811.64C3012.82,3769.64 2962.9,3764.58 2926.45,3801.04C2888.89,3838.59 2879.76,3906.07 2906.07,3951.63C2932.37,3997.19 2984.22,4003.69 3021.77,3966.14L3021.89,3966.01L3055.09,3977.51ZM3085.02,3841.47C3090.86,3875.56 3086.6,3912.35 3073.22,3944.57L3043.91,3934.42C3056.74,3907.59 3060.53,3875.54 3054.13,3846.78L3085.02,3841.47Z" style="fill:white;"/>
</g>
<g transform="matrix(1.47838,0,0,2.34801,-955.236,-3103.7)">
<path d="M2906.78,1571.77L3111.02,1642.48L3116.61,1626.34L3147.2,1664.74L3099.42,1675.99L3105,1659.86L2910.03,1592.35C2908.25,1585.69 2907.18,1578.77 2906.78,1571.77ZM2917.45,1517.07L3114.77,1483.17L3111.88,1466.34L3157.2,1485.21L3120.78,1518.13L3117.88,1501.3L2910.22,1536.97C2911.99,1530.09 2914.41,1523.4 2917.45,1517.07Z" style="fill:rgb(255,216,67);"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 508 507" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-1279,0)">
<g id="PhotonVision-Icon-BG" transform="matrix(0.264062,0,0,0.469444,1279.5,0)">
<rect x="0" y="0" width="1920" height="1080" style="fill:none;"/>
<clipPath id="_clip1">
<rect x="0" y="0" width="1920" height="1080"/>
</clipPath>
<g clip-path="url(#_clip1)">
<g transform="matrix(4.27015,0,0,2.40196,-20444.8,-3235.56)">
<circle cx="5012.55" cy="1571.77" r="224.918" style="fill:rgb(0,100,146);"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-10313.5)">
<path d="M3055.09,3977.51C3050.3,3984.25 3045,3990.56 3039.21,3996.35C2987.91,4047.65 2917.1,4038.77 2881.16,3976.54C2845.23,3914.3 2857.71,3822.13 2909.01,3770.83C2960.31,3719.53 3031.13,3728.41 3067.06,3790.64C3069.85,3795.48 3072.35,3800.49 3074.56,3805.67L3039.78,3811.64C3012.82,3769.64 2962.9,3764.58 2926.45,3801.04C2888.89,3838.59 2879.76,3906.07 2906.07,3951.63C2932.37,3997.19 2984.22,4003.69 3021.77,3966.14L3021.89,3966.01L3055.09,3977.51ZM3085.02,3841.47C3090.86,3875.56 3086.6,3912.35 3073.22,3944.57L3043.91,3934.42C3056.74,3907.59 3060.53,3875.54 3054.13,3846.78L3085.02,3841.47Z" style="fill:white;"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-3827.86)">
<path d="M2906.78,1571.77L3111.02,1642.48L3116.61,1626.34L3147.2,1664.74L3099.42,1675.99L3105,1659.86L2910.03,1592.35C2908.25,1585.69 2907.18,1578.77 2906.78,1571.77ZM2917.45,1517.07L3114.77,1483.17L3111.88,1466.34L3157.2,1485.21L3120.78,1518.13L3117.88,1501.3L2910.22,1536.97C2911.99,1530.09 2914.41,1523.4 2917.45,1517.07Z" style="fill:rgb(255,216,67);"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M23 15V18C23 18.5 22.64 18.88 22.17 18.97L18.97 15.77C19 15.68 19 15.59 19 15.5C19 14.12 17.88 13 16.5 13C16.41 13 16.32 13 16.23 13.03L10.2 7H11V5.73C10.4 5.39 10 4.74 10 4C10 2.9 10.9 2 12 2S14 2.9 14 4C14 4.74 13.6 5.39 13 5.73V7H14C17.87 7 21 10.13 21 14H22C22.55 14 23 14.45 23 15M22.11 21.46L20.84 22.73L19.89 21.78C19.62 21.92 19.32 22 19 22H5C3.9 22 3 21.11 3 20V19H2C1.45 19 1 18.55 1 18V15C1 14.45 1.45 14 2 14H3C3 11.53 4.29 9.36 6.22 8.11L1.11 3L2.39 1.73L22.11 21.46M10 15.5C10 14.12 8.88 13 7.5 13S5 14.12 5 15.5 6.12 18 7.5 18 10 16.88 10 15.5M16.07 17.96L14.04 15.93C14.23 16.97 15.04 17.77 16.07 17.96Z" /></svg>

Before

Width:  |  Height:  |  Size: 928 B

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M12,2C13.1,2 14,2.9 14,4C14,4.74 13.6,5.39 13,5.73V7H14C17.87,7 21,10.13 21,14H22C22.55,14 23,14.45 23,15V18C23,18.55 22.55,19 22,19H21V20C21,21.1 20.1,22 19,22H5C3.9,22 3,21.1 3,20V19H2C1.45,19 1,18.55 1,18V15C1,14.45 1.45,14 2,14H3C3,10.13 6.13,7 10,7H11V5.73C10.4,5.39 10,4.74 10,4C10,2.9 10.9,2 12,2M7.5,13C6.12,13 5,14.12 5,15.5C5,16.88 6.12,18 7.5,18C8.88,18 10,16.88 10,15.5C10,14.12 8.88,13 7.5,13M16.5,13C15.12,13 14,14.12 14,15.5C14,16.88 15.12,18 16.5,18C17.88,18 19,16.88 19,15.5C19,14.12 17.88,13 16.5,13Z" /></svg>

Before

Width:  |  Height:  |  Size: 827 B

View File

@@ -7,7 +7,7 @@
:alt="alt"
@click="clickHandler"
@error="loadErrHandler"
/>
>
</template>
<script>
@@ -34,7 +34,7 @@
"margin-right": "auto",
"max-height": this.maxHeight,
height: `${this.scale}%`,
cursor: (this.colorPicking ? `url(${require("../../assets/eyedropper.svg")}),` : "pointer") + "default",
cursor: (this.colorPicking ? `url(${require("../../assets/icons/eyedropper.svg")}),` : "pointer") + "default",
};
if (this.$vuetify.breakpoint.xl) {
@@ -50,10 +50,10 @@
},
src: {
get() {
var port = this.getCurPort();
const port = this.getCurPort();
if(port <= 0){
//Invalid port, keep it spinny
return require("../../assets/loading.gif");
//Invalid port, keep it spiny
return require("../../../public/loading.svg");
} else {
//Valid port, connect
return this.getSrcURLFromPort(port);
@@ -66,13 +66,13 @@
},
methods: {
getCurPort(){
var port = -1;
let port = -1;
if(this.disconnected){
//Disconnected, port is unknown.
port = -1;
} else {
//Connected - get the port
if(this.id == 'raw-stream'){
if(this.id === 'raw-stream'){
port = this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].inputStreamPort
} else {
port = this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].outputStreamPort
@@ -92,7 +92,7 @@
if(this.colorPicking){
this.$emit('click', event);
} else {
var port = this.getCurPort();
const port = this.getCurPort();
if(port <= 0){
console.log("No valid port, ignoring click.");
} else {

View File

@@ -4,7 +4,7 @@
dense
align="center"
>
<v-col :cols="12 - (inputCols || 8)">
<v-col :cols="labelCols || (12 - (inputCols || 8))">
<tooltipped-label
:tooltip="tooltip"
:text="name"
@@ -36,7 +36,7 @@
TooltippedLabel
},
// eslint-disable-next-line vue/require-prop-types
props: ['name', 'value', 'disabled', 'errorMessage', 'inputCols', 'rules', 'tooltip'],
props: ['name', 'value', 'disabled', 'errorMessage', 'inputCols', 'labelCols', 'rules', 'tooltip'],
data() {
return {}
},

View File

@@ -106,7 +106,7 @@ export default {
await this.delay(200).then(() => {
let i = 0;
if (this.prependFocused === false && this.appendFocused === true) {
if (!this.prependFocused && this.appendFocused) {
i = 1;
}

View File

@@ -8,7 +8,7 @@
align="center"
cols="12"
>
<span class="white--text">Target Location</span>
<span class="white--text">Target Visualization</span>
</v-col>
</v-row>
<v-row>
@@ -22,19 +22,17 @@
style="width:100%;height:100%"
/>
</v-col>
<v-row>
<v-col>
<v-row style="margin-bottom: 24px">
<v-col style="display: flex; justify-content: center">
<v-btn
class="ml-10"
color="secondary"
@click="resetCamFirstPerson"
>
First Person
</v-btn>
</v-col>
<v-col>
<v-col style="display: flex; justify-content: center">
<v-btn
class="ml-10"
color="secondary"
@click="resetCamThirdPerson"
>
@@ -57,11 +55,13 @@ import {
PerspectiveCamera,
Quaternion,
Scene,
TrackballControls,
Vector3,
Color,
WebGLRenderer
} from "three-full";
} from "three";
// This import conflicts with Three.js docs but is required for the build to succeed
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls"
export default {
name: "MiniMap",
@@ -138,7 +138,7 @@ export default {
this.refFrameCues.push(camBody)
this.refFrameCues.push(camLens)
var controls = new TrackballControls(
const controls = new TrackballControls(
camera,
renderer.domElement
);
@@ -231,7 +231,7 @@ export default {
},
onWindowResize() {
var container = document.getElementById("MapContainer")
const container = document.getElementById("MapContainer");
if(container){
this.canvas.width = container.clientWidth * 0.95;
this.canvas.height = container.clientWidth * 0.85;

View File

@@ -2,7 +2,7 @@
<div>
<v-row
align="center"
class="pl-6"
style="padding: 12px 12px 12px 24px"
>
<v-col
cols="10"
@@ -31,12 +31,13 @@
cols="2"
md="1"
lg="2"
class="pl-5"
>
<CVicon
v-if="isCameraNameEdit === false"
color="#c5c5c5"
:hover="true"
text="edit"
text="mdi-pencil"
tooltip="Edit camera name"
@click="changeCameraName"
/>
@@ -45,7 +46,7 @@
color="#c5c5c5"
style="display: inline-block;"
:hover="true"
text="save"
text="mdi-content-save"
tooltip="Save Camera Name"
@click="saveCameraNameChange"
/>
@@ -53,7 +54,7 @@
color="error"
style="display: inline-block;"
:hover="true"
text="close"
text="mdi-close"
tooltip="Discard Changes"
@click="discardCameraNameChange"
/>
@@ -79,6 +80,7 @@
cols="2"
md="1"
lg="2"
class="pl-5"
>
<v-menu
v-if="!$store.getters.isDriverMode"
@@ -90,7 +92,7 @@
color="#c5c5c5"
v-on="on"
>
menu
mdi-menu
</v-icon>
</template>
<v-list
@@ -103,7 +105,7 @@
<CVicon
color="#c5c5c5"
:right="true"
text="edit"
text="mdi-pencil"
tooltip="Edit pipeline name"
/>
</v-list-item-title>
@@ -113,7 +115,7 @@
<CVicon
color="#c5c5c5"
:right="true"
text="add"
text="mdi-plus"
tooltip="Add new pipeline"
/>
</v-list-item-title>
@@ -123,7 +125,7 @@
<CVicon
color="red darken-2"
:right="true"
text="delete"
text="mdi-delete"
tooltip="Delete pipeline"
/>
</v-list-item-title>
@@ -249,156 +251,156 @@ import CVselect from '../common/cv-select'
import CVinput from '../common/cv-input'
export default {
name: "CameraAndPipelineSelect",
components: {
CVicon,
CVselect,
CVinput
},
data: () => {
return {
re: RegExp("^[A-Za-z0-9_ \\-)(]*[A-Za-z0-9][A-Za-z0-9_ \\-)(.]*$"),
isCameraNameEdit: false,
newCameraName: "",
cameraNameError: "",
isPipelineNameEdit: false,
namingDialog: false,
newPipelineName: "",
duplicateDialog: false,
showPipeTypeDialog: false,
proposedPipelineType : 0,
pipeIndexToDuplicate: undefined
}
},
computed: {
checkCameraName() {
if (this.newCameraName !== this.$store.getters.cameraList[this.currentCameraIndex]) {
if (this.re.test(this.newCameraName)) {
for (let cam in this.cameraList) {
if (this.cameraList.hasOwnProperty(cam)) {
if (this.newCameraName === this.cameraList[cam]) {
return "A camera by that name already exists"
}
name: "CameraAndPipelineSelect",
components: {
CVicon,
CVselect,
CVinput
},
data: () => {
return {
re: RegExp("^[A-Za-z0-9_ \\-)(]*[A-Za-z0-9][A-Za-z0-9_ \\-)(.]*$"),
isCameraNameEdit: false,
newCameraName: "",
cameraNameError: "",
isPipelineNameEdit: false,
namingDialog: false,
newPipelineName: "",
duplicateDialog: false,
showPipeTypeDialog: false,
proposedPipelineType : 0,
pipeIndexToDuplicate: undefined
}
},
computed: {
checkCameraName() {
if (this.newCameraName !== this.$store.getters.cameraList[this.currentCameraIndex]) {
if (this.re.test(this.newCameraName)) {
for (let cam in this.cameraList) {
if (this.cameraList.hasOwnProperty(cam)) {
if (this.newCameraName === this.cameraList[cam]) {
return "A camera by that name already exists"
}
}
}
} else {
return "A camera name can only contain letters, numbers, and spaces"
}
}
}
} else {
return "A camera name can only contain letters, numbers, and spaces"
}
}
return "";
},
checkPipelineName() {
if (this.newPipelineName !== this.$store.getters.pipelineList[this.currentPipelineIndex - 1] || this.isPipelineNameEdit === false) {
if (this.re.test(this.newPipelineName)) {
for (let pipe in this.$store.getters.pipelineList) {
if (this.$store.getters.pipelineList.hasOwnProperty(pipe)) {
if (this.newPipelineName === this.$store.getters.pipelineList[pipe]) {
return "A pipeline with this name already exists"
}
return "";
},
checkPipelineName() {
if (this.newPipelineName !== this.$store.getters.pipelineList[this.currentPipelineIndex - 1] || !this.isPipelineNameEdit) {
if (this.re.test(this.newPipelineName)) {
for (let pipe in this.$store.getters.pipelineList) {
if (this.$store.getters.pipelineList.hasOwnProperty(pipe)) {
if (this.newPipelineName === this.$store.getters.pipelineList[pipe]) {
return "A pipeline with this name already exists"
}
}
}
} else {
return "A pipeline name can only contain letters, numbers, and spaces"
}
}
return ""
},
currentCameraIndex: {
get() {
return this.$store.getters.currentCameraIndex;
},
set(value) {
this.$store.commit('currentCameraIndex', value);
}
},
currentPipelineIndex: {
get() {
return this.$store.getters.currentPipelineIndex + (this.$store.getters.isDriverMode ? 1 : 0);
},
set(value) {
this.$store.commit('currentPipelineIndex', value - (this.$store.getters.isDriverMode ? 1 : 0));
}
},
currentPipelineType: {
get() {
return this.$store.getters.currentPipelineSettings.pipelineType - 2;
},
set(value) {
value; // nop, since we have the dialog for this
}
}
} else {
return "A pipeline name can only contain letters, numbers, and spaces"
}
}
return ""
},
currentCameraIndex: {
get() {
return this.$store.getters.currentCameraIndex;
},
set(value) {
this.$store.commit('currentCameraIndex', value);
}
},
currentPipelineIndex: {
get() {
return this.$store.getters.currentPipelineIndex + (this.$store.getters.isDriverMode ? 1 : 0);
},
set(value) {
this.$store.commit('currentPipelineIndex', value - (this.$store.getters.isDriverMode ? 1 : 0));
}
},
currentPipelineType: {
get() {
return this.$store.getters.currentPipelineSettings.pipelineType - 2;
},
set(value) {
value; // nop, since we have the dialog for this
}
methods: {
showTypeDialog(idx) {
// Only show the dialog if it's a new type
this.showPipeTypeDialog = idx !== this.currentPipelineType;
this.proposedPipelineType = idx;
},
changePipeType(actuallyChange) {
const newIdx = actuallyChange ? this.proposedPipelineType : this.currentPipelineType
this.handleInputWithIndex('pipelineType', newIdx);
this.showPipeTypeDialog = false;
},
changeCameraName() {
this.newCameraName = this.$store.getters.cameraList[this.currentCameraIndex];
this.isCameraNameEdit = true;
},
saveCameraNameChange() {
if (this.checkCameraName === "") {
// this.handleInputWithIndex("changeCameraName", this.newCameraName);
this.axios.post('http://' + this.$address + '/api/setCameraNickname',
{name: this.newCameraName, cameraIndex: this.$store.getters.currentCameraIndex})
// eslint-disable-next-line
.then(r => {
this.$emit('camera-name-changed')
})
.catch(e => {
console.log("HTTP error while changing camera name " + e);
this.$emit('camera-name-changed')
})
this.discardCameraNameChange();
}
},
discardCameraNameChange() {
this.isCameraNameEdit = false;
this.newCameraName = "";
},
toPipelineNameChange() {
this.newPipelineName = this.$store.getters.pipelineList[this.currentPipelineIndex - 1];
this.isPipelineNameEdit = true;
this.namingDialog = true;
},
toCreatePipeline() {
this.newPipelineName = "New Pipeline";
this.isPipelineNameEdit = false;
this.namingDialog = true;
},
deleteCurrentPipeline() {
if (this.$store.getters.pipelineList.length > 1) {
this.handleInputWithIndex('deleteCurrentPipeline', {});
} else {
this.snackbar = true;
}
},
savePipelineNameChange() {
if (this.checkPipelineName === "") {
if (this.isPipelineNameEdit) {
this.handleInputWithIndex("changePipelineName", this.newPipelineName);
} else {
this.handleInputWithIndex("addNewPipeline", [this.newPipelineName, this.currentPipelineType]); // 0 for reflective, 1 for colored shape
}
this.discardPipelineNameChange();
}
},
duplicatePipeline() {
this.handleInputWithIndex("duplicatePipeline", this.currentPipelineIndex);
},
discardPipelineNameChange() {
this.namingDialog = false;
this.isPipelineNameEdit = false;
this.newPipelineName = "";
},
}
},
methods: {
showTypeDialog(idx) {
// Only show the dialog if it's a new type
this.showPipeTypeDialog = idx !== this.currentPipelineType;
this.proposedPipelineType = idx;
},
changePipeType(actuallyChange) {
const newIdx = actuallyChange ? this.proposedPipelineType : this.currentPipelineType
this.handleInputWithIndex('pipelineType', newIdx);
this.showPipeTypeDialog = false;
},
changeCameraName() {
this.newCameraName = this.$store.getters.cameraList[this.currentCameraIndex];
this.isCameraNameEdit = true;
},
saveCameraNameChange() {
if (this.checkCameraName === "") {
// this.handleInputWithIndex("changeCameraName", this.newCameraName);
this.axios.post('http://' + this.$address + '/api/setCameraNickname',
{name: this.newCameraName, cameraIndex: this.$store.getters.currentCameraIndex})
// eslint-disable-next-line
.then(r => {
this.$emit('camera-name-changed')
})
.catch(e => {
console.log("HTTP error while changing camera name " + e);
this.$emit('camera-name-changed')
})
this.discardCameraNameChange();
}
},
discardCameraNameChange() {
this.isCameraNameEdit = false;
this.newCameraName = "";
},
toPipelineNameChange() {
this.newPipelineName = this.$store.getters.pipelineList[this.currentPipelineIndex - 1];
this.isPipelineNameEdit = true;
this.namingDialog = true;
},
toCreatePipeline() {
this.newPipelineName = "New Pipeline";
this.isPipelineNameEdit = false;
this.namingDialog = true;
},
deleteCurrentPipeline() {
if (this.$store.getters.pipelineList.length > 1) {
this.handleInputWithIndex('deleteCurrentPipeline', {});
} else {
this.snackbar = true;
}
},
savePipelineNameChange() {
if (this.checkPipelineName === "") {
if (this.isPipelineNameEdit) {
this.handleInputWithIndex("changePipelineName", this.newPipelineName);
} else {
this.handleInputWithIndex("addNewPipeline", [this.newPipelineName, this.currentPipelineType]); // 0 for reflective, 1 for colored shpae
}
this.discardPipelineNameChange();
}
},
duplicatePipeline() {
this.handleInputWithIndex("duplicatePipeline", this.currentPipelineIndex);
},
discardPipelineNameChange() {
this.namingDialog = false;
this.isPipelineNameEdit = false;
this.newPipelineName = "";
},
}
}
</script>

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
# JSPDF Fonts
These are .js interpretations of the .tff files in the branding folder. They are used by jspdf to apply branding-approprate fonts to any .pdf file generation (ex: calibration targets)
These are .js interpretations of the .tff files in the branding folder. They are used by jspdf to apply branding-appropriate fonts to any .pdf file generation (ex: calibration targets)
https://peckconsulting.s3.amazonaws.com/fontconverter/fontconverter.html is the converter used to generate them.

View File

@@ -1,5 +1,5 @@
var canvas = undefined;
var image = undefined;
let canvas = undefined;
let image = undefined;
function initColorPicker() {
if (!canvas)
@@ -33,8 +33,8 @@ function colorPickerClick(event, currentFunction, currentRange) {
function eyeDrop(pixel) {
let hsv = RGBtoHSV(pixel);
let range = widenRange([hsv, hsv.slice(0)]);//sends hsv and a copy of hsv
return range
//sends hsv and a copy of hsv
return widenRange([hsv, hsv.slice(0)])
}
function expand(pixel, currentRange) {
@@ -46,8 +46,8 @@ function expand(pixel, currentRange) {
function shrink(pixel, currentRange) {
let hsv = RGBtoHSV(pixel);
let widenHSV = widenRange([[].concat(hsv), hsv]);
if (!shrinkRange(currentRange, widenHSV[0]))//Tries to shrink the lower part of the widen HSV
shrinkRange(currentRange, widenHSV[1]);//If the prev attempt failed, try to shrink the higher part of the widen HSV
if (!shrinkRange(currentRange, widenHSV[0])) //Tries to shrink the lower part of to widened HSV
shrinkRange(currentRange, widenHSV[1]); //If the prev attempt failed, try to shrink the higher part of to widened HSV
return currentRange
}
@@ -78,10 +78,10 @@ function RGBtoHSV(numbers) {
//Loops though the colors array, finds the smallest and biggest value for H,S and V. Returns the range containing every color
function createRange(HSVColors) {
let range = [[], []];
for (var i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
range[0][i] = HSVColors[0][i];
range[1][i] = HSVColors[0][i];
for (var j = HSVColors.length - 1; j >= 0; j--) {
for (let j = HSVColors.length - 1; j >= 0; j--) {
range[0][i] = Math.min(HSVColors[j][i], range[0][i]);
range[1][i] = Math.max(HSVColors[j][i], range[1][i]);
}
@@ -89,7 +89,7 @@ function createRange(HSVColors) {
return range;//[[Hmin,Smin,Vmin],[Hmax,Smax,Vmax]]
}
//This function adds 10 extra units to each side of the sliders, not to be confued with the expand selection button
//This function adds 10 extra units to each side of the sliders, not to be confused with the expand selection button
function widenRange(range) {
let expanded = [[], []];
for (let i = 0; i < 3; i++) {

View File

@@ -10,30 +10,30 @@ class StatsHistoryBuffer{
this.frameCount = 0;
this.bitAvgAccum = 0;
//calculated vals
//calculated values
this.bitRate_Mbps = 0;
this.framerate_fps = 0;
}
putAndPop(v){
this.headPtr++;
var idx = (this.headPtr)%this._array.length;
var poppedVal = this._array[idx];
const idx = (this.headPtr) % this._array.length;
const poppedVal = this._array[idx];
this._array[idx] = v;
return poppedVal;
}
addSample(time, frameSize_bits, dispFrame_count) {
var oldVal = this.putAndPop([time, frameSize_bits, dispFrame_count]);
const oldVal = this.putAndPop([time, frameSize_bits, dispFrame_count]);
this.bitAvgAccum += frameSize_bits;
if(oldVal !=null){
var oldTime = oldVal[0];
var oldFrameSize = oldVal[1];
var oldFrameCount = oldVal[2];
const oldTime = oldVal[0];
const oldFrameSize = oldVal[1];
const oldFrameCount = oldVal[2];
var deltaTime_s = (time - oldTime);
const deltaTime_s = (time - oldTime);
this.bitAvgAccum -= oldFrameSize;
@@ -125,10 +125,10 @@ export class WebsocketVideoStream{
}
dispImageData(){
if(this.prevImgDataTime != this.imgDataTime){
if(this.prevImgDataTime !== this.imgDataTime){
//From https://stackoverflow.com/questions/67507616/set-image-src-from-image-blob/67507685#67507685
//Ensure uniqueness by making the new one before revoking the old one.
var oldURL = this.imgObjURL
const oldURL = this.imgObjURL;
this.imgObjURL = URL.createObjectURL(this.imgData);
if(oldURL != null){
URL.revokeObjectURL(oldURL)
@@ -143,19 +143,19 @@ export class WebsocketVideoStream{
}
dispNoStream() {
this.image.src = require("../assets/loading.gif");
this.image.src = require("../../public/loading.svg");
}
animationLoop(){
// Update time metrics
var curTime_s = window.performance.now() / 1000.0;
var timeInState = curTime_s - this.dsm_restart_start_time;
const curTime_s = window.performance.now() / 1000.0;
const timeInState = curTime_s - this.dsm_restart_start_time;
// Save previous state
this.dsm_prev_state = this.dsm_cur_state;
// Evaluate state transitions
if(this.serverConnectionActive == false){
if(!this.serverConnectionActive){
//Any state - if the server connection goes false, always transition to disconnected
this.dsm_cur_state = this.DSM_DISCONNECTED;
} else {
@@ -221,12 +221,12 @@ export class WebsocketVideoStream{
//take current-state or state-transition actions
if(this.dsm_cur_state != this.dsm_prev_state){
if(this.dsm_cur_state !== this.dsm_prev_state){
//Any state transition
console.log("State Change: " + this.dsm_prev_state + " -> " + this.dsm_cur_state);
}
if(this.dsm_cur_state == this.DSM_SHOWING){
if(this.dsm_cur_state === this.DSM_SHOWING){
// Currently in SHOWING
// Show image and update status text
this.dispImageData();
@@ -236,24 +236,24 @@ export class WebsocketVideoStream{
this.statsTextDiv.innerHTML = this.dsm_cur_state;
}
if(this.dsm_cur_state != this.DSM_SHOWING && this.dsm_prev_state == this.DSM_SHOWING ){
if(this.dsm_cur_state !== this.DSM_SHOWING && this.dsm_prev_state === this.DSM_SHOWING ){
//Any transition out of showing - no stream
this.dispNoStream();
}
if(this.dsm_cur_state == this.DSM_RESTART_UNSUBSCRIBE){
if(this.dsm_cur_state === this.DSM_RESTART_UNSUBSCRIBE){
// Currently in UNSUBSCRIBE, do the unsubscribe actions
this.stopStream();
this.dsm_restart_start_time = curTime_s;
}
if(this.dsm_cur_state == this.DSM_SUBSCRIBE){
if(this.dsm_cur_state === this.DSM_SUBSCRIBE){
// Currently in SUBSCRIBE, do the subscribe actions
this.startStream();
this.dsm_restart_start_time = curTime_s;
}
if(this.dsm_cur_state == this.DSM_WAIT_FOR_VALID_PORT){
if(this.dsm_cur_state === this.DSM_WAIT_FOR_VALID_PORT){
// Currently waiting for a vaild port to be requested
if(this.newStreamPortReq != null){
this.streamPort = this.newStreamPortReq;
@@ -315,7 +315,7 @@ export class WebsocketVideoStream{
ws_onMessage(e){
//console.log("Got message from " + this.serverAddr)
var msgTime_s = window.performance.now() / 1000.0;
const msgTime_s = window.performance.now() / 1000.0;
if(typeof e.data === 'string'){
//string data from host
//TODO - anything to receive info here? Maybe "available streams?"

View File

@@ -1,5 +1,4 @@
import '@mdi/font/css/materialdesignicons.css';
import 'material-design-icons-iconfont/dist/material-design-icons.css'
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import theme from "../theme";

View File

@@ -97,7 +97,7 @@ export default new Vuex.Store({
threads: 1,
debug: false,
refineEdges: true,
numIterations: 1,
numIterations: 30,
decisionMargin: 0,
hammingDist: 0,
}
@@ -106,24 +106,26 @@ export default new Vuex.Store({
pipelineResults: {
fps: 0,
latency: 0,
targets: [{
// Available in both 2D and 3D
pitch: 0,
yaw: 0,
skew: 0,
area: 0,
// 3D only
pose: {x: 1, y: 1, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
},
{
// Available in both 2D and 3D
pitch: 0,
yaw: 0,
skew: 0,
area: 0,
// 3D only
pose: {x: 2, y: 3, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
}]
targets: [
{
// Available in both 2D and 3D
pitch: 0,
yaw: 0,
skew: 0,
area: 0,
// 3D only
pose: {x: 1, y: 1, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
},
{
// Available in both 2D and 3D
pitch: 0,
yaw: 0,
skew: 0,
area: 0,
// 3D only
pose: {x: 2, y: 3, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
}
]
},
settings: {
general: {
@@ -152,7 +154,7 @@ export default new Vuex.Store({
calibrationData: {
count: 0,
videoModeIndex: 0,
minCount: 12, // Gets set by backend anyways, but we need a sane default
minCount: 12, // Gets set by backend anyway, but we need a sane default
hasEnough: false,
squareSizeIn: 1.0,
patternWidth: 8,

View File

@@ -26,7 +26,7 @@
<CVnumberinput
v-model="cameraSettings.fov"
:tooltip="cameraSettings.isFovConfigurable ? 'Field of view (in degrees) of the camera measured across the diagonal of the frame, in a video mode which covers the whole sensor area.' : 'This setting is managed by a vendor'"
name="Maximum diagonal FOV"
name="Maximum Diagonal FOV"
:disabled="!cameraSettings.isFovConfigurable"
:label-cols="$vuetify.breakpoint.mdAndUp ? undefined : 7"
/>
@@ -98,7 +98,7 @@
/>
<CVnumberinput
v-model="boardWidth"
name="Board width"
name="Board Width"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[v => (v >= 4) || 'Width must be at least 4']"
@@ -106,7 +106,7 @@
/>
<CVnumberinput
v-model="boardHeight"
name="Board height"
name="Board Height"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[v => (v >= 4) || 'Height must be at least 4']"
@@ -296,7 +296,7 @@
<v-icon left>
mdi-download
</v-icon>
Download Target
Download Calibration Target
</v-btn>
</v-col>
<v-col>
@@ -324,7 +324,7 @@
<template>
<CVimage
:id="cameras-cal"
:idx=1
:idx="1"
:disconnected="!$store.state.backendConnected"
scale="100"
style="border-radius: 5px;"
@@ -389,19 +389,23 @@
</v-col>
</v-row>
<!-- Special hidden upload input that gets 'clicked' when the user imports settings -->
<!-- Special hidden upload input that gets 'clicked' when the user imports calibdb data -->
<input
ref="importCalibrationFromCalibdb"
type="file"
accept=".json"
style="display: none;"
@change="readImportedCalibration"
/>
>
<v-snackbar v-model="uploadSnack" top :color="uploadSnackData.color" timeout="-1">
<v-snackbar
v-model="uploadSnack"
top
:color="uploadSnackData.color"
timeout="-1"
>
<span>{{ uploadSnackData.text }}</span>
</v-snackbar>
</div>
</template>
@@ -474,7 +478,7 @@ export default {
calibrationDivisors: {
get() {
return this.unfilteredStreamDivisors.filter(item => {
var res = this.stringResolutionList[this.selectedFilteredResIndex].split(" X ").map(it => parseInt(it));
const res = this.stringResolutionList[this.selectedFilteredResIndex].split(" X ").map(it => parseInt(it));
console.log(res);
console.log(item);
// Realistically, we need more than 320x240, but lower than this is
@@ -603,57 +607,54 @@ export default {
},
},
methods: {
readImportedCalibration(event) {
// let formData = new FormData();
// formData.append("zipData", event.target.files[0]);
const filename = event.target.files[0].name;
readImportedCalibration(event) {
// let formData = new FormData();
// formData.append("zipData", event.target.files[0]);
const filename = event.target.files[0].name;
event.target.files[0].text().then(fileText => {
const data = {
"cameraIndex": this.$store.getters.currentCameraIndex,
"payload": fileText,
"filename": filename,
};
this.axios
.post("http://" + this.$address + "/api/calibration/import", data, {
headers: { "Content-Type": "text/plain" },
})
.then(() => {
this.uploadSnackData = {
color: "success",
text:
"Calibration imported successfully!",
};
this.uploadSnack = true;
})
.catch((err) => {
if (err.response) {
this.uploadSnackData = {
color: "error",
text:
"Error while uploading calibration file! Could not process provided file.",
event.target.files[0].text().then(fileText => {
const data = {
"cameraIndex": this.$store.getters.currentCameraIndex,
"payload": fileText,
"filename": filename,
};
} else if (err.request) {
this.uploadSnackData = {
color: "error",
text:
"Error while uploading calibration file! No respond to upload attempt.",
};
} else {
this.uploadSnackData = {
color: "error",
text: "Error while uploading calibration file!",
};
}
this.uploadSnack = true;
});
})
this.axios
.post("http://" + this.$address + "/api/calibration/import", data, {
headers: { "Content-Type": "text/plain" },
})
.then(() => {
this.uploadSnackData = {
color: "success",
text:
"Calibration imported successfully!",
};
this.uploadSnack = true;
})
.catch((err) => {
if (err.response) {
this.uploadSnackData = {
color: "error",
text:
"Error while uploading calibration file! Could not process provided file.",
};
} else if (err.request) {
this.uploadSnackData = {
color: "error",
text:
"Error while uploading calibration file! No respond to upload attempt.",
};
} else {
this.uploadSnackData = {
color: "error",
text: "Error while uploading calibration file!",
};
}
this.uploadSnack = true;
});
})
},
closeDialog() {
this.snack = false;
this.calibrationInProgress = false;
@@ -670,97 +671,83 @@ export default {
return ret;
},
downloadBoard() {
// Generates a .pdf of a board for calibration and downloads it
const config = {
type: this.boardType === 0 ? "chessboard" : "dotgrid",
boardWidthIn: this.boardWidth,
boardHeightIn: this.boardHeight,
patternSpacingIn: this.squareSizeIn
}
//Murica paper.
var doc = new jsPDF({unit: 'in', format:'letter'});
var paper_x = 8.5;
var paper_y = 11.0;
const doc = new jsPDF({ unit: "in", format: "letter" })
//Load in custom fonts
console.log(doc.getFontList());
doc.setFont('Prompt-Regular');
doc.setFontSize(12);
doc.setFont("Prompt-Regular")
doc.setFontSize(12)
// Common Parameters
var num_x = this.boardWidth;
var num_y = this.boardHeight;
var patternSize = this.squareSizeIn;
var isCheckerboard = (this.boardType==0);
const paperWidth = 8.5
const paperHeight = 11.0
var x_coord = 0.0;
var y_coord = 0.0;
var x_idx = 0;
var y_idx = 0;
var start_x = 0;
var start_y = 0;
// Draw the selected pattern to the document
switch (config.type) {
case "chessboard":
// eslint-disable-next-line no-case-declarations
const chessboardStartX = (paperWidth - config.boardWidthIn * config.patternSpacingIn) / 2
// eslint-disable-next-line no-case-declarations
const chessboardStartY = (paperHeight - config.boardWidthIn * config.patternSpacingIn) / 2
var annotation = num_x + " x " + num_y + " | " + patternSize + "in "
for (let squareY = 0; squareY < config.boardHeightIn; squareY++) {
for (let squareX = 0; squareX < config.boardWidthIn; squareX++) {
const xPos = chessboardStartX + squareX * config.patternSpacingIn
const yPos = chessboardStartY + squareY * config.patternSpacingIn
if(isCheckerboard){
///////////////////////////////////////////
// Checkerboard Pattern
start_x = paper_x/2.0 - (num_x * patternSize)/2.0;
start_y = paper_y/2.0 - (num_y * patternSize)/2.0;
for(y_idx = 0; y_idx < num_y; y_idx++){
for(x_idx = 0; x_idx < num_x; x_idx++){
x_coord = start_x + x_idx * patternSize;
y_coord = start_y + y_idx * patternSize;
if((x_idx + y_idx) % 2 == 0){
doc.rect(x_coord, y_coord, patternSize, patternSize, "F");
// Only draw the odd squares to create the chessboard pattern
if ((xPos + yPos + 0.25) % 2 === 0) {
doc.rect(xPos, yPos, config.patternSpacingIn, config.patternSpacingIn, "F")
}
}
}
break
case "dotgrid":
// eslint-disable-next-line no-case-declarations
const dotgridStartX = (paperWidth - (2 * (config.boardWidthIn - 1) + ((config.boardHeightIn - 1) % 2)) * config.patternSpacingIn) / 2.0
// eslint-disable-next-line no-case-declarations
const dotgridStartY = (paperHeight - (config.boardHeightIn - config.patternSpacingIn)) / 2
} else {
///////////////////////////////////////////
// Assymetric Dot-Grid Pattern
// see https://github.com/opencv/opencv/blob/b450dd7a87bc69997a8417d94bdfb87427a9fe62/modules/calib3d/src/circlesgrid.cpp#L437
// as well as FindBoardCornersPipe.java's Dotboard implementation
for (let squareY = 0; squareY < config.boardHeightIn; squareY++) {
for (let squareX = 0; squareX < config.boardWidthIn; squareX++) {
const xPos = dotgridStartX + (2 * squareX + (squareY % 2)) * config.patternSpacingIn
const yPos = dotgridStartY + squareY * config.patternSpacingIn
start_x = paper_x/2.0 - ((2*(num_x-1) + (num_y-1) % 2) * patternSize)/2.0;
start_y = paper_y/2.0 - (num_y-1 * patternSize)/2.0;
// Dot Grid Pattern
for(y_idx = 0; y_idx < num_y; y_idx++){
for(x_idx = 0; x_idx < num_x; x_idx++){
x_coord = start_x + (2*x_idx + y_idx % 2) * patternSize;
y_coord = start_y + y_idx * patternSize;
doc.circle(x_coord, y_coord, patternSize/4.0, "F");
doc.circle(xPos, yPos, config.patternSpacingIn / 4, "F")
}
}
}
break
}
///////////////////////////////////////////
// Draw a fixed size inch ruler pattern to
// help users debug their printers
var lineStartX = 1.0;
var lineEndX = paper_x - lineStartX;
var lineY = paper_y - 1.0;
doc.setFont('Prompt-Regular');
doc.setLineWidth(0.01);
doc.line(lineStartX, lineY, lineEndX, lineY);
var segIdx = 0;
for(var tickX = lineStartX; tickX <= lineEndX; tickX += 1.0){
doc.line(tickX, lineY, tickX, lineY + 0.25);
doc.text(String(segIdx) + (segIdx == 0 ? " in" : ""), tickX + 0.1, lineY + 0.25);
segIdx++;
}
// Draw ruler pattern
const lineStartX = 1.0
const lineEndX = paperWidth - lineStartX
const lineY = paperHeight - 1.0
doc.setLineWidth(0.01)
doc.line(lineStartX, lineY, lineEndX, lineY)
///////////////////////////////////////////
// Annotate what was drawn + branding
var img = new Image();
img.src = require('@/assets/logoMono.png');
doc.addImage(img, 'PNG', 1.0, 0.75, 1.4, 0.5 );
doc.setFont('Prompt-Regular');
doc.text(annotation, paper_x-1.0, 1.0, {maxWidth:(paper_x - 2.0)/2, align:"right"});
for (let tickX = lineStartX; tickX <= lineEndX; tickX++) {
doc.line(tickX, lineY, tickX, lineY + 0.25)
doc.text(`${tickX - 1}${tickX - 1 === 0 ? " in" : ""}`, tickX + 0.1, lineY + 0.25)
}
doc.save("calibrationTarget.pdf");
// Add branding
const logoImage = new Image();
logoImage.src = require('@/assets/logos/logoMono.png');
doc.addImage(logoImage, 'PNG', 1.0, 0.75, 1.4, 0.5);
doc.text(`${config.boardWidthIn} x ${config.boardHeightIn} | ${config.patternSpacingIn}in`, paperWidth - 1, 1.0,
{
maxWidth: (paperWidth - 2.0) / 2,
align: "right",
}
)
doc.save(`calibrationTarget-${config.type}.pdf`)
},
sendCameraSettings() {
this.axios.post("http://" + this.$address + "/api/settings/camera", {
@@ -773,18 +760,16 @@ export default {
}
)
},
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) {
if (this.isCalibrating) {
data['takeCalibrationSnapshot'] = true
} else {
// This store prevents an edge case of a user not selecting a different resolution, which causes the set logic to not be called
@@ -803,7 +788,7 @@ export default {
this.snack = true;
this.calibrationInProgress = true;
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", this.$store.getters.currentCameraIndex)
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", {idx: this.$store.getters.currentCameraIndex})
.then((response) => {
if (response.status === 200) {
this.calibrationInProgress = false;
@@ -819,6 +804,12 @@ export default {
}
</script>
<style>
::-webkit-scrollbar{
height: 0.55em;
}
</style>
<style scoped>
.v-data-table {
text-align: center;

View File

@@ -1,33 +1,22 @@
<template>
<div
style="overflow:hidden;height:100%;width:100%"
height="100%"
width="100%"
style="overflow:hidden; height:100%; width:100%"
>
<iframe
src="docs/index.html"
frameborder="0"
style="overflow:hidden;height:100%;width:100%"
height="100%"
width="100%"
/>
<div
v-if="() => process.env.NODE_ENV === 'development'"
style="width: 100%; height: 100%; padding: 16px"
>
<span style="color: white; font-weight: bold">PhotonClient is in development mode so the documentation page will not load. Please recompile in production mode with the documentation copied over after a full build.</span>
</div>
<div
v-else
style="width: 100%; height: 100%"
>
<!--suppress HtmlUnknownTarget -->
<iframe
src="docs/index.html"
style="overflow:hidden; height:100%; width:100%; border: 0"
/>
</div>
</div>
</template>
<!-- <iframe src="test.html" frameborder="0" style="overflow:hidden;height:100%;width:100%" height="100%" width="100%"></iframe> -->
<style scoped>
.videoClass {
text-align: center;
}
.videoClass img {
padding-top: 10px;
height: auto !important;
vertical-align: middle;
}
.colsClass {
padding: 0 !important;
}
</style>

View File

@@ -26,7 +26,6 @@
:href="'http://' + this.$address + '/api/settings/photonvision-journalctl.txt'"
download="photonvision-journalctl.txt"
/>
</v-btn>
</v-card-title>
<div class="pr-6 pl-6">

View File

@@ -1,75 +1,81 @@
<template>
<div>
<v-container
class="pa-3"
fluid
class="pa-3"
fluid
>
<v-row
no-gutters
align="center"
justify="center"
no-gutters
align="center"
justify="center"
>
<v-col
cols="12"
:class="['pb-3 ', 'pr-lg-3']"
lg="8"
align-self="stretch"
cols="12"
:class="['pb-3 ', 'pr-lg-3']"
lg="8"
align-self="stretch"
>
<v-card
color="primary"
height="100%"
style="display: flex; flex-direction: column"
dark
color="primary"
height="100%"
style="display: flex; flex-direction: column"
dark
>
<v-card-title
class="pb-0 mb-0 pl-4 pt-1"
style="height: 15%; min-height: 50px;"
class="pb-0 mb-0 pl-4 pt-1"
style="min-height: 50px; justify-content: space-between; align-content: center"
>
Cameras
<v-chip
:class="fpsTooLow ? 'ml-2 mt-1' : 'mt-2'"
x-small
<div class="pt-2">
<span class="mr-4">Cameras</span>
<v-chip
label
:color="fpsTooLow ? 'error' : 'transparent'"
:text-color="fpsTooLow ? 'white' : 'grey'"
>
<span class="pr-1">Processing @ {{ Math.round($store.state.pipelineResults.fps) }}&nbsp;FPS &ndash;</span>
<span v-if="fpsTooLow && !$store.getters.currentPipelineSettings.inputShouldShow && $store.getters.pipelineType == 2">HSV thresholds are too broad; narrow them for better performance</span>
<span v-else-if="fpsTooLow && getters.currentCameraSettings.inputShouldShow">stop viewing the raw stream for better performance</span>
<span v-else>{{ Math.min(Math.round($store.state.pipelineResults.latency), 9999) }} ms latency</span>
</v-chip>
<v-switch
:text-color="fpsTooLow ? '#C7EA46' : '#ff4d00'"
style="font-size: 1rem; padding: 0; margin: 0"
>
<span class="pr-1">Processing @ {{ Math.round($store.state.pipelineResults.fps) }}&nbsp;FPS &ndash;</span>
<span v-if="fpsTooLow && !$store.getters.currentPipelineSettings.inputShouldShow && $store.getters.pipelineType === 2">HSV thresholds are too broad; narrow them for better performance</span>
<span v-else-if="fpsTooLow && $store.getters.currentCameraSettings.inputShouldShow">stop viewing the raw stream for better performance</span>
<span v-else>{{ Math.min(Math.round($store.state.pipelineResults.latency), 9999) }} ms latency</span>
</v-chip>
</div>
<div>
<v-switch
v-model="driverMode"
label="Driver Mode"
style="margin-left: auto;"
color="accent"
/>
class="pt-2"
/>
</div>
</v-card-title>
<v-divider />
<v-row
align="center"
align="center"
class="pl-3 pr-3"
>
<v-col
v-for="idx in (selectedOutputs instanceof Array ? selectedOutputs : [selectedOutputs])"
:key="idx"
cols="12"
:md="selectedOutputs.length === 1 ? 12 : Math.floor(12 / selectedOutputs.length)"
class="pb-0 pt-0"
style="height: 100%;"
v-for="idx in (selectedOutputs instanceof Array ? selectedOutputs : [selectedOutputs])"
:key="idx"
cols="12"
:md="selectedOutputs.length === 1 ? 12 : Math.floor(12 / selectedOutputs.length)"
style="height: 100%;"
>
<div style="position: relative; width: 100%; height: 100%;">
<cv-image
:id="idx === 0 ? 'raw-stream' : 'processed-stream'"
ref="streams"
:idx=idx
:disconnected="!$store.state.backendConnected"
scale="100"
:max-height="$store.getters.isDriverMode ? '40vh' : '300px'"
:max-height-md="$store.getters.isDriverMode ? '50vh' : '380px'"
:max-height-lg="$store.getters.isDriverMode ? '55vh' : '390px'"
:max-height-xl="$store.getters.isDriverMode ? '60vh' : '450px'"
:alt="idx === 0 ? 'Raw stream' : 'Processed stream'"
:color-picking="$store.state.colorPicking && idx === 0"
@click="onImageClick"
:id="idx === 0 ? 'raw-stream' : 'processed-stream'"
ref="streams"
:idx="idx"
:disconnected="!$store.state.backendConnected"
scale="95"
:max-height="$store.getters.isDriverMode ? '40vh' : '300px'"
:max-height-md="$store.getters.isDriverMode ? '50vh' : '380px'"
:max-height-lg="$store.getters.isDriverMode ? '55vh' : '390px'"
:max-height-xl="$store.getters.isDriverMode ? '60vh' : '450px'"
:alt="idx === 0 ? 'Raw stream' : 'Processed stream'"
:color-picking="$store.state.colorPicking && idx === 0"
style="padding-top: 22px;"
@click="onImageClick"
/>
</div>
</v-col>
@@ -77,44 +83,44 @@
</v-card>
</v-col>
<v-col
cols="12"
class="pb-3"
lg="4"
align-self="stretch"
cols="12"
class="pb-3"
lg="4"
style="display: flex; flex-direction: column"
>
<v-card
color="primary"
color="primary"
>
<camera-and-pipeline-select />
</v-card>
<v-card
:disabled="$store.getters.isDriverMode || $store.state.colorPicking"
class="mt-3"
color="primary"
:disabled="$store.getters.isDriverMode || $store.state.colorPicking"
class="mt-3"
color="primary"
>
<v-row
align="center"
class="pl-3 pr-3"
align="center"
class="pa-sm-3"
>
<v-col lg="12">
<p style="color: white;">
Processing mode:
Processing Mode
</p>
<v-btn-toggle
v-model="processingMode"
mandatory
dark
class="fill"
v-model="processingMode"
mandatory
dark
class="fill"
>
<v-btn
color="secondary"
color="secondary"
>
<v-icon>mdi-crop-square</v-icon>
<v-icon>mdi-square-outline</v-icon>
<span>2D</span>
</v-btn>
<v-btn
color="secondary"
@click="on3DClick"
color="secondary"
@click="on3DClick"
>
<v-icon>mdi-cube-outline</v-icon>
<span>3D</span>
@@ -123,25 +129,25 @@
</v-col>
<v-col lg="12">
<p style="color: white;">
Stream display:
Stream Display
</p>
<v-btn-toggle
v-model="selectedOutputs"
:multiple="$vuetify.breakpoint.mdAndUp"
mandatory
dark
class="fill"
v-model="selectedOutputs"
:multiple="$vuetify.breakpoint.mdAndUp"
mandatory
dark
class="fill"
>
<v-btn
color="secondary"
class="fill"
color="secondary"
class="fill"
>
<v-icon>mdi-import</v-icon>
<span>Raw</span>
</v-btn>
<v-btn
color="secondary"
class="fill"
color="secondary"
class="fill"
>
<v-icon>mdi-export</v-icon>
<span>Processed</span>
@@ -154,40 +160,40 @@
</v-row>
<v-row no-gutters>
<v-col
v-for="(tabs, idx) in tabGroups"
:key="idx"
:cols="Math.floor(12 / tabGroups.length)"
:class="idx !== tabGroups.length - 1 ? 'pr-3' : ''"
align-self="stretch"
v-for="(tabs, idx) in tabGroups"
:key="idx"
:cols="Math.floor(12 / tabGroups.length)"
:class="idx !== tabGroups.length - 1 ? 'pr-3' : ''"
align-self="stretch"
>
<v-card
color="primary"
height="100%"
class="pr-4 pl-4"
color="primary"
height="100%"
class="pr-4 pl-4"
>
<v-tabs
v-if="!$store.getters.isDriverMode"
v-model="selectedTabs[idx]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
v-if="!$store.getters.isDriverMode"
v-model="selectedTabs[idx]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
>
<v-tab
v-for="(tab, i) in tabs"
:key="i"
v-for="(tab, i) in tabs"
:key="i"
>
{{ tab.name }}
</v-tab>
</v-tabs>
<div class="pl-4 pr-4 pt-2">
<div class="pl-4 pr-4 pt-4 pb-2">
<keep-alive>
<component
:is="(tabs[selectedTabs[idx]] || tabs[0]).component"
:ref="(tabs[selectedTabs[idx]] || tabs[0]).name"
v-model="$store.getters.pipeline"
@update="$emit('save')"
:is="(tabs[selectedTabs[idx]] || tabs[0]).component"
:ref="(tabs[selectedTabs[idx]] || tabs[0]).name"
v-model="$store.getters.pipeline"
@update="$emit('save')"
/>
</keep-alive>
</div>
@@ -197,18 +203,18 @@
</v-container>
<v-snackbar
v-model="showNTWarning"
color="error"
timeout="-1"
top
v-model="showNTWarning"
color="error"
timeout="-1"
top
>
{{ $store.state.settings.networkSettings.runNTServer ?
"NetworkTables server enabled! PhotonLib may not work." :
"NetworkTables not connected! Are you on a network with a robot?" }}
<template v-slot:action>
<v-btn
text
@click="hideNTWarning = true"
text
@click="hideNTWarning = true"
>
Hide
</v-btn>
@@ -216,12 +222,12 @@
</v-snackbar>
<v-dialog
v-model="dialog"
width="500"
v-model="dialog"
width="500"
>
<v-card
color="primary"
dark
color="primary"
dark
>
<v-card-title>
Current resolution not calibrated
@@ -230,9 +236,9 @@
<v-card-text>
Because the current resolution {{ this.$store.getters.currentVideoFormat.width }} x {{ this.$store.getters.currentVideoFormat.height }} is not yet calibrated, 3D mode cannot be enabled. Please
<a
href="/#/cameras"
class="white--text"
@click="$emit('switch-to-cameras')"
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>
@@ -241,9 +247,9 @@
<v-card-actions>
<v-spacer />
<v-btn
color="white"
text
@click="closeUncalibratedDialog"
color="white"
text
@click="closeUncalibratedDialog"
>
OK
</v-btn>
@@ -372,15 +378,14 @@ export default {
const group = ret[i];
// All the tabs we allow
const filteredGroup = group.filter(it =>
ret[i] = group.filter(it =>
!(!allow3d && it.name === "3D") //Filter out 3D tab any time 3D isn't calibrated
&& !((!allow3d || isAprilTag || isAruco) && it.name === "PnP") //Filter out the PnP config tab if 3D isn't available, or we're doing Apriltags
&& !((isAprilTag || isAruco) && (it.name === "Threshold")) //Filter out threshold tab if we're doing apriltags
&& !((isAprilTag || isAruco)&& (it.name === "Contours")) //Filter out contours if we're doing Apriltag
&& !((isAprilTag || isAruco) && (it.name === "Contours")) //Filter out contours if we're doing Apriltag
&& !(!isAprilTag && it.name === "AprilTag") //Filter out apriltag unless we actually are doing Apriltags
&& !(!isAruco && it.name === "Aruco")
);
ret[i] = filteredGroup;
}
// One last filter to remove empty lists
@@ -408,7 +413,7 @@ export default {
}
},
selectedOutputs: {
// All this logic exists to deal with the reality that the output select buttons sometimes need an array and sometimes need a number (depending on whether or not they're exclusive)
// All this logic exists to deal with the reality that the output select buttons sometimes need an array and sometimes need a number (depending on whether they're exclusive)
get() {
// We switch the selector to single-select only on sm-and-down size devices, so we have to return a Number instead of an Array in that state
let ret = [];
@@ -445,11 +450,11 @@ export default {
},
fpsTooLow: {
get() {
// For now we only show the FPS is too low warning when GPU acceleration is enabled, because we don't really trust the presented video modes otherwise
// For now, we only show the FPS is too low warning when GPU acceleration is enabled, because we don't really trust the presented video modes otherwise
const currFPS = this.$store.state.pipelineResults.fps;
const targetFPS = this.$store.getters.currentVideoFormat.fps;
const driverMode = this.$store.getters.isDriverMode;
const gpuAccel = this.$store.state.settings.general.gpuAcceleration === true;
const gpuAccel = this.$store.state.settings.general.gpuAcceleration;
const isReflective = this.$store.getters.pipelineType === 2;
return (currFPS - targetFPS) < -5 && this.$store.state.pipelineResults.fps !== 0 && !driverMode && gpuAccel && isReflective;

View File

@@ -1,74 +1,74 @@
<template>
<div>
<CVselect
v-model="selectedFamily"
name="Target family"
:list="['AprilTag family 36h11', 'AprilTag family 25h9', 'AprilTag family 16h5']"
select-cols="8"
@input="handlePipelineUpdate('tagFamily', selectedFamily)"
v-model="selectedFamily"
name="Target family"
:list="['AprilTag family 36h11', 'AprilTag family 25h9', 'AprilTag family 16h5']"
select-cols="8"
@input="handlePipelineUpdate('tagFamily', selectedFamily)"
/>
<CVslider
v-model="decimate"
class="pt-2"
slider-cols="8"
name="Decimate"
min="1"
max="8"
step="1.0"
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
@input="handlePipelineData('decimate')"
v-model="decimate"
class="pt-2"
slider-cols="8"
name="Decimate"
min="1"
max="8"
step="1.0"
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
@input="handlePipelineData('decimate')"
/>
<CVslider
v-model="blur"
class="pt-2"
slider-cols="8"
name="Blur"
min="0"
max="5"
step=".01"
tooltip="Gaussian blur added to the image, high FPS cost for slightly decreased noise"
@input="handlePipelineData('blur')"
v-model="blur"
class="pt-2"
slider-cols="8"
name="Blur"
min="0"
max="5"
step=".01"
tooltip="Gaussian blur added to the image, high FPS cost for slightly decreased noise"
@input="handlePipelineData('blur')"
/>
<CVslider
v-model="threads"
class="pt-2"
slider-cols="8"
name="Threads"
min="1"
max="8"
step="1"
tooltip="Number of threads spawned by the AprilTag detector"
@input="handlePipelineData('threads')"
v-model="threads"
class="pt-2"
slider-cols="8"
name="Threads"
min="1"
max="8"
step="1"
tooltip="Number of threads spawned by the AprilTag detector"
@input="handlePipelineData('threads')"
/>
<CVswitch
v-model="refineEdges"
class="pt-2"
slider-cols="8"
name="Refine Edges"
tooltip="Further refines the apriltag corner position initial estimate, suggested left on"
@input="handlePipelineData('refineEdges')"
v-model="refineEdges"
class="pt-2"
slider-cols="8"
name="Refine Edges"
tooltip="Further refines the AprilTag corner position initial estimate, suggested left on"
@input="handlePipelineData('refineEdges')"
/>
<CVslider
v-model="decisionMargin"
class="pt-2 pb-4"
slider-cols="8"
name="Decision Margin Cutoff"
min="0"
max="250"
step="1"
tooltip="Tags with a 'margin' (decoding quality score) less than this wil be rejected. Increase this to reduce the number of false positive detections"
@input="handlePipelineData('decisionMargin')"
v-model="decisionMargin"
class="pt-2 pb-4"
slider-cols="8"
name="Decision Margin Cutoff"
min="0"
max="250"
step="1"
tooltip="Tags with a 'margin' (decoding quality score) less than this wil be rejected. Increase this to reduce the number of false positive detections"
@input="handlePipelineData('decisionMargin')"
/>
<CVslider
v-model="numIterations"
class="pt-2 pb-4"
slider-cols="8"
name="Pose Estimation Iterations"
min="0"
max="500"
step="1"
tooltip="Number of iterations the pose estimation algorithm will run, 50-100 is a good starting point"
@input="handlePipelineData('numIterations')"
v-model="numIterations"
class="pt-2 pb-4"
slider-cols="8"
name="Pose Estimation Iterations"
min="0"
max="500"
step="1"
tooltip="Number of iterations the pose estimation algorithm will run, 50-100 is a good starting point"
@input="handlePipelineData('numIterations')"
/>
</div>
</template>

View File

@@ -1,37 +1,37 @@
<template>
<div>
<CVslider
v-model="decimate"
class="pt-2"
slider-cols="8"
name="Decimate"
min="1"
max="8"
step=".5"
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
@input="handlePipelineData('decimate')"
v-model="decimate"
class="pt-2"
slider-cols="8"
name="Decimate"
min="1"
max="8"
step="0.5"
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
@input="handlePipelineData('decimate')"
/>
<CVslider
v-model="numIterations"
class="pt-2"
slider-cols="8"
name="Corner Iterations"
min="30"
max="1000"
step="5"
tooltip="How many iterations are going to be used in order to refine corners. Higher values are lead to more accuracy at the cost of performance"
@input="handlePipelineData('numIterations')"
v-model="numIterations"
class="pt-2"
slider-cols="8"
name="Corner Iterations"
min="30"
max="1000"
step="5"
tooltip="How many iterations are going to be used in order to refine corners. Higher values are lead to more accuracy at the cost of performance"
@input="handlePipelineData('numIterations')"
/>
<CVslider
v-model="cornerAccuracy"
class="pt-2"
slider-cols="8"
name="Corner Accuracy"
min=".01"
max="100"
step=".01"
tooltip="Minimum accuracy for the corners, lower is better but more performance intensive "
@input="handlePipelineData('cornerAccuracy')"
v-model="cornerAccuracy"
class="pt-2"
slider-cols="8"
name="Corner Accuracy"
min="0.01"
max="100"
step="0.01"
tooltip="Minimum accuracy for the corners, lower is better but more performance intensive "
@input="handlePipelineData('cornerAccuracy')"
/>
</div>
</template>
@@ -61,9 +61,9 @@ export default {
this.$store.commit("mutatePipeline", {"numIterations": val});
},
},
cornerAccuracy: {
cornerAccuracy: {
get() {
return this.$store.getters.currentPipelineSettings.cornerAccuracy
return this.$store.getters.currentPipelineSettings.cornerDetectionAccuracyPercentage
},
set(val) {
this.$store.commit("mutatePipeline", {"cornerAccuracy": val});

View File

@@ -18,19 +18,15 @@
@input="handlePipelineData('contourTargetOrientation')"
@rollback="e=> rollback('contourTargetOrientation', e)"
/>
<CVswitch
v-model="outputShowMultipleTargets"
name="Show Multiple Targets"
tooltip="If enabled, up to five targets will be displayed and sent to user code, instead of just one"
:disabled="isTagPipeline"
class="mb-4"
text-cols="3"
@input="handlePipelineData('outputShowMultipleTargets')"
@rollback="e=> rollback('outputShowMultipleTargets', e)"
/>
<v-divider />
<CVselect
v-model="offsetRobotOffsetMode"
name="Robot Offset Mode"

View File

@@ -16,42 +16,42 @@
<thead style="font-size: 1.25rem;">
<tr>
<th class="text-center">
Target
Target Count
</th>
<th
v-if="$store.getters.pipelineType === 4 || (($store.getters.pipelineType - 2) === 3)"
v-if="$store.getters.pipelineType === 4 || (($store.getters.pipelineType - 2) === 3)"
class="text-center"
>
Fiducial ID
</th>
<template v-if="!$store.getters.currentPipelineSettings.solvePNPEnabled">
<th class="text-center">
Pitch,&nbsp;&deg;
Pitch &theta;&deg;
</th>
<th class="text-center">
Yaw,&nbsp;&deg;
Yaw &theta;&deg;
</th>
<th class="text-center">
Skew,&nbsp;&deg;
Skew &theta;&deg;
</th>
<th class="text-center">
Area, %
Area %
</th>
</template>
<template v-else>
<th class="text-center">
X,&nbsp;m
X meters
</th>
<th class="text-center">
Y,&nbsp;m
Y meters
</th>
<th class="text-center">
Z Angle,&nbsp;&deg;
Z Angle &theta;&deg;
</th>
</template>
<template v-if="$store.getters.pipelineType === 4 && $store.getters.currentPipelineSettings.solvePNPEnabled">
<th class="text-center">
Ambiguity
Ambiguity %
</th>
</template>
</tr>

View File

@@ -132,9 +132,9 @@ export default {
},
averageHue: {
get() {
var isInverted = this.$store.getters.currentPipelineSettings.hueInverted;
const isInverted = this.$store.getters.currentPipelineSettings.hueInverted;
const arr = this.$store.getters.currentPipelineSettings.hsvHue;
var retVal = 0;
let retVal = 0;
if (Array.isArray(arr)) {
retVal = (arr[0] + arr[1]);
@@ -251,17 +251,17 @@ export default {
#hue-slider >>> .v-slider {
background: linear-gradient( to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
}
#sat-slider >>> .v-slider {
background: linear-gradient( to right, #fff 0%, hsl(var(--averageHue), 100%, 50%) 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
}
#value-slider >>> .v-slider {
background: linear-gradient( to right, #000 0%, hsl(var(--averageHue), 100%, 50%) 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
}
>>> .v-slider__thumb {
outline: black solid thin;

View File

@@ -1,56 +1,95 @@
<template>
<div>
<v-row>
<v-col cols="12" lg="4" md="6">
<v-btn color="red" @click="restartProgram()">
<v-col
cols="12"
lg="4"
md="6"
>
<v-btn
color="red"
@click="restartProgram()"
>
<v-icon left>
mdi-restart
</v-icon>
Restart PhotonVision
</v-btn>
</v-col>
<v-col cols="12" lg="4" md="6">
<v-btn color="red" @click="restartDevice()">
<v-col
cols="12"
lg="4"
md="6"
>
<v-btn
color="red"
@click="restartDevice()"
>
<v-icon left>
mdi-restart-alert
</v-icon>
Restart Device
</v-btn>
</v-col>
<v-col cols="12" lg="4">
<v-btn color="secondary" @click="$refs.offlineUpdate.click()">
<v-col
cols="12"
lg="4"
>
<v-btn
color="secondary"
@click="$refs.offlineUpdate.click()"
>
<v-icon left>
mdi-update
mdi-upload
</v-icon>
Offline Update
</v-btn>
</v-col>
</v-row>
<v-divider />
<v-divider style="margin: 12px 0;" />
<v-row>
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="$refs.exportSettings.click()">
<v-col
cols="12"
sm="6"
>
<v-btn
color="secondary"
@click="$refs.importSettings.click()"
>
<v-icon left>
mdi-download
</v-icon>
Export Settings
</v-btn>
</v-col>
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="$refs.importSettings.click()">
<v-icon left>
mdi-upload
mdi-import
</v-icon>
Import Settings
</v-btn>
</v-col>
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="$refs.exportLogFile.click()">
<v-col
cols="12"
sm="6"
>
<v-btn
color="secondary"
@click="$refs.exportSettings.click()"
>
<v-icon left>
mdi-file
mdi-export
</v-icon>
Export current log
Export Settings
</v-btn>
</v-col>
<v-col
cols="12"
sm="6"
>
<v-btn
color="secondary"
@click="$refs.exportLogFile.click()"
>
<v-icon left>
mdi-download
</v-icon>
Download Current Log
<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
<a
@@ -66,16 +105,27 @@
</v-btn>
</v-col>
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="showLogs()">
<v-col
cols="12"
sm="6"
>
<v-btn
color="secondary"
@click="showLogs()"
>
<v-icon left>
mdi-bug
mdi-eye
</v-icon>
Show log viewer
</v-btn>
</v-col>
</v-row>
<v-snackbar v-model="snack" top :color="snackbar.color" timeout="-1">
<v-snackbar
v-model="snack"
top
:color="snackbar.color"
timeout="-1"
>
<span>{{ snackbar.text }}</span>
</v-snackbar>
@@ -86,14 +136,12 @@
accept=".zip, .json"
style="display: none;"
@change="readImportedSettings"
/>
>
<!-- Special hidden link that gets 'clicked' when the user exports settings -->
<a
ref="exportSettings"
style="color: black; text-decoration: none; display: none"
:href="
'http://' + this.$address + '/api/settings/photonvision_config.zip'
"
:href="'http://' + this.$address + '/api/settings/photonvision_config.zip'"
download="photonvision-settings.zip"
/>
@@ -104,12 +152,13 @@
accept=".jar"
style="display: none;"
@change="doOfflineUpdate"
/>
>
</div>
</template>
<script>
export default {
// eslint-disable-next-line
name: "Device Control",
data() {
return {
@@ -270,7 +319,7 @@ export default {
.infoTable {
border: 1px solid;
border-collapse: separate;
border-spacing: 0px;
border-spacing: 0;
border-radius: 5px;
text-align: left;
margin-bottom: 10px;
@@ -278,12 +327,9 @@ export default {
display: block;
overflow-x: auto;
}
w
.infoElem {
padding-right: 15px;
padding-bottom: 1px;
padding-top: 1px;
padding-left: 10px;
border-right: 1px solid;
padding: 1px 15px 1px 10px;
border-right: 1px solid;
}
</style>

View File

@@ -16,7 +16,8 @@
import CVslider from "../../components/common/cv-slider";
export default {
name: 'LEDs',
// eslint-disable-next-line
name: 'LED Control',
components: {
CVslider,
},

View File

@@ -6,19 +6,22 @@
>
<CVinput
v-model="ntServerAddress"
:input-cols="inputCols"
:input-cols="inputCols - 1"
:label-cols="3"
:disabled="settings.runNTServer"
name="Team Number"
tooltip="enter the team number or the IP address of the robot NetworkTables server"
:rules="[v => isValidTeamNumber(v) || 'Team Number must be non blank and a team number, IP address, or hostname']"
name="Team Number/NetworkTables Server Address"
tooltip="Enter the Team Number or the IP address of the NetworkTables Server"
:rules="[v => isValidTeamNumber(v) || 'The NetworkTables Server Address must be a non blank team number, IP address, or hostname']"
/>
<v-banner
v-show="!isValidTeamNumber(ntServerAddress) && !runNTServer"
rounded
color="red"
text-color="white"
style="margin: 8px 0"
icon="mdi-alert-circle-outline"
>
Team Number unset or invalid. NetworkTables will not be able to connect.
NetworkTables Server Address is unset or invalid. NetworkTables is unable to connect
</v-banner>
<CVradio
v-show="$store.state.settings.networkSettings.shouldManage"
@@ -54,8 +57,9 @@
rounded
color="red"
text-color="white"
icon="mdi-information-outline"
>
This switch is intended for testing; it should be off on a robot. PhotonLib will NOT work!
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
</v-banner>
</v-form>
<v-btn
@@ -77,12 +81,9 @@
</v-snackbar>
<template v-if="$store.state.settings.networkSettings.shouldManage && false">
<!-- Advanced controls for changing DHCP settings and stuff -->
<v-divider class="mt-4 mb-4" />
<v-title> Advanced </v-title>
<v-card-title> Advanced </v-card-title>
<CVinput
:input-cols="inputCols"
name="Set DHCP command"
@@ -99,69 +100,7 @@
:input-cols="inputCols"
name="Physical interface"
/>
</template>
<!-- TEMP - RIO finder is not currently enabled
<v-row>
<v-col
cols="12"
sm="6"
>
<v-simple-table
fixed-header
height="100%"
dense
>
<template v-slot:default>
<thead style="font-size: 1.25rem;">
<tr>
<th>
Device IPs
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.state.networkInfo.deviceips"
:key="index"
>
<td>{{ value }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
<v-col
cols="12"
sm="6"
>
<v-simple-table
fixed-header
height="100%"
dense
>
<template v-slot:default>
<thead style="font-size: 1.25rem;">
<tr>
<th>
Possible RoboRIOs
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.state.networkInfo.possibleRios"
:key="index"
>
<td>{{ value }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
-->
</div>
</template>
@@ -274,7 +213,7 @@ export default {
let restAreOnes = false;
for (let i = 3; i >= 0; i--) {
for (let j = 0; j < 8; j++) {
let bitValue = (octets[i] >>> j & 1) == 1;
let bitValue = (octets[i] >>> j & 1) === 1;
if (restAreOnes && !bitValue)
return false;
restAreOnes = bitValue;
@@ -343,3 +282,8 @@ export default {
font-family: monospace !important;
}
</style>
<style>
.v-banner__wrapper {
padding: 6px !important;
}
</style>

View File

@@ -3,16 +3,16 @@
<v-row class="pa-4">
<table class="infoTable">
<tr>
<th class="infoElem">
<th class="infoElem infoElemTitle">
Version
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
Hardware Model
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
Platform
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
GPU Acceleration
</th>
</tr>
@@ -34,19 +34,19 @@
<table class="infoTable">
<tr>
<th class="infoElem">
<th class="infoElem infoElemTitle">
CPU Usage
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
CPU Temp
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
CPU Memory Usage
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
GPU Memory Usage
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
Disk Usage
</th>
<th class="infoElem">
@@ -54,17 +54,18 @@
<template v-slot:activator="{ on, attrs }">
<span
v-bind="attrs"
class="infoElemTitle"
v-on="on"
>
CPU Throttling
CPU Throttling
</span>
</template>
<span>
Current or Previous Reason for the cpu being held back from maximum performance.
Current or Previous Reason for the cpu being held back from maximum performance.
</span>
</v-tooltip>
</th>
<th class="infoElem">
<th class="infoElem infoElemTitle">
CPU Uptime
</th>
</tr>
@@ -125,32 +126,6 @@
>
<span>{{ snackbar.text }}</span>
</v-snackbar>
<!-- Special hidden upload input that gets 'clicked' when the user imports settings -->
<input
ref="importSettings"
type="file"
accept=".zip, .json"
style="display: none;"
@change="readImportedSettings"
>
<!-- Special hidden link that gets 'clicked' when the user exports settings -->
<a
ref="exportSettings"
style="color: black; text-decoration: none; display: none"
:href="'http://' + this.$address + '/api/settings/photonvision_config.zip'"
download="photonvision-settings.zip"
/>
<!-- Special hidden new jar upload input that gets 'clicked' when the user posts a new .jar -->
<input
ref="offlineUpdate"
type="file"
accept=".jar"
style="display: none;"
@change="doOfflineUpdate"
>
</div>
</template>
@@ -192,94 +167,6 @@ export default {
return this.$store.state.metrics;
}
},
methods: {
restartProgram() {
this.axios.post('http://' + this.$address + '/api/restartProgram', {});
},
restartDevice() {
this.axios.post('http://' + this.$address + '/api/restartDevice', {});
},
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! PhotonVision will restart in the background...",
};
this.snack = true;
}).catch(err => {
if (err.response) {
this.snackbar = {
color: "error",
text: "Error while uploading settings file! Could not process provided file.",
};
} else if (err.request) {
this.snackbar = {
color: "error",
text: "Error while uploading settings file! No respond to upload attempt.",
};
} else {
this.snackbar = {
color: "error",
text: "Error while uploading settings file!",
};
}
this.snack = true;
});
},
doOfflineUpdate(event) {
this.snackbar = {
color: "secondary",
text: "New Software Upload in Process..."
};
this.snack = true;
let formData = new FormData();
formData.append("jarData", event.target.files[0]);
this.axios.post("http://" + this.$address + "/api/settings/offlineUpdate", formData,
{headers: {"Content-Type": "multipart/form-data"},
onUploadProgress: function( progressEvent ) {
this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded / progressEvent.total ) * 100 ) );
if(this.uploadPercentage < 99.5){
this.snackbar.text = "New Software Upload in Process, " + this.uploadPercentage + "% complete";
} else {
this.snackbar.text = "Installing uploaded software...";
}
}.bind(this)
}).then(() => {
this.snackbar = {
color: "success",
text: "New .jar copied successfully! PhotonVision will restart in the background...",
};
this.snack = true;
}).catch(err => {
if (err.response) {
this.snackbar = {
color: "error",
text: "Error while uploading new .jar file! Could not process provided file.",
};
} else if (err.request) {
this.snackbar = {
color: "error",
text: "Error while uploading new .jar file! No respond to upload attempt.",
};
} else {
this.snackbar = {
color: "error",
text: "Error while uploading new .jar file!",
};
}
this.snack = true;
});
},
showLogs(event) {
event;
this.$store.state.logsOverlay = true;
}
}
}
</script>
@@ -291,7 +178,7 @@ export default {
.infoTable{
border: 1px solid;
border-collapse: separate;
border-spacing: 0px;
border-spacing: 0;
border-radius: 5px;
text-align: left;
margin-bottom: 10px;
@@ -301,11 +188,15 @@ export default {
}
.infoElem {
padding-right: 15px;
padding-bottom: 1px;
padding-top: 1px;
padding-left: 10px;
padding: 1px 15px 1px 10px;
border-right: 1px solid;
font-weight: normal;
}
.infoElemTitle {
font-size: 18px;
text-decoration: underline;
text-decoration-color: #ffd843;
}
</style>

View File

@@ -238,7 +238,16 @@ public class RequestHandler {
public static void onCalibrationEnd(Context ctx) {
logger.info("Calibrating camera! This will take a long time...");
var index = Integer.parseInt(ctx.body());
int index;
try {
index = (int) kObjectMapper.readValue(ctx.body(), HashMap.class).get("idx");
} catch (Exception e) {
logger.error("Cannot parse calibration idx", e);
ctx.status(500);
return;
}
var calData = VisionModuleManager.getInstance().getModule(index).endCalibration();
if (calData == null) {
ctx.status(500);