2020-05-25 22:46:44 +03:00
< template >
2020-06-26 04:39:14 -07:00
< div >
2020-07-13 19:34:31 -07:00
< v-container
2023-01-05 13:25:44 -05:00
class = "pa-3"
fluid
2020-07-13 19:34:31 -07:00
>
< v-row
2023-01-05 13:25:44 -05:00
no - gutters
align = "center"
justify = "center"
2020-06-26 04:39:14 -07:00
>
2020-07-13 19:34:31 -07:00
< v-col
2023-01-05 13:25:44 -05:00
cols = "12"
: class = "['pb-3 ', 'pr-lg-3']"
lg = "8"
align - self = "stretch"
2020-06-26 04:39:14 -07:00
>
2020-07-13 19:34:31 -07:00
< v-card
2023-01-05 13:25:44 -05:00
color = "primary"
height = "100%"
style = "display: flex; flex-direction: column"
dark
2020-06-26 04:39:14 -07:00
>
2020-07-13 19:34:31 -07:00
< v-card-title
2023-01-05 13:25:44 -05:00
class = "pb-0 mb-0 pl-4 pt-1"
style = "height: 15%; min-height: 50px;"
2020-07-13 19:34:31 -07:00
>
2020-08-14 12:39:21 -07:00
Cameras
2020-12-08 02:34:21 -05:00
< v-chip
2023-01-05 13:25:44 -05:00
: class = "fpsTooLow ? 'ml-2 mt-1' : 'mt-2'"
x - small
label
: color = "fpsTooLow ? 'error' : 'transparent'"
: text - color = "fpsTooLow ? 'white' : 'grey'"
2020-12-08 02:34:21 -05:00
>
2022-12-01 13:42:21 -05:00
< span class = "pr-1" > Processing @ { { Math . round ( $store . state . pipelineResults . fps ) } } & nbsp ; FPS & ndash ; < / span >
2022-12-28 11:21:41 -08:00
< span v-if = "fpsTooLow && !$store.getters.currentPipelineSettings.inputShouldShow && $store.getters.pipelineType == 2" > HSV thresholds are too broad ; narrow them for better performance < / span >
2023-01-06 17:25:11 -08:00
< span v-else-if = "fpsTooLow && getters.currentCameraSettings.inputShouldShow" > stop viewing the raw stream for better performance < / span >
2022-12-28 11:21:41 -08:00
< span v-else > {{ Math.min ( Math.round ( $ store.state.pipelineResults.latency ) , 9999 ) }} ms latency < / span >
2020-12-08 02:34:21 -05:00
< / v-chip >
2020-07-31 13:50:50 -07:00
< v-switch
2023-01-05 13:25:44 -05:00
v - model = "driverMode"
label = "Driver Mode"
style = "margin-left: auto;"
color = "accent"
2020-07-31 13:50:50 -07:00
/ >
2020-07-13 19:34:31 -07:00
< / v-card-title >
< v-row
2023-01-05 13:25:44 -05:00
align = "center"
2020-07-13 19:34:31 -07:00
>
< v-col
2023-01-05 13:25:44 -05:00
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%;"
2020-06-26 04:39:14 -07:00
>
2020-07-13 19:34:31 -07:00
< div style = "position: relative; width: 100%; height: 100%;" >
2020-10-16 16:48:24 -07:00
< cv-image
2023-01-05 13:25:44 -05:00
: 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'"
2023-01-07 06:51:01 -08:00
: alt = "idx === 0 ? 'Raw stream' : 'Processed stream'"
2023-01-05 13:25:44 -05:00
: color - picking = "$store.state.colorPicking && idx === 0"
@ click = "onImageClick"
2020-07-13 19:34:31 -07:00
/ >
< / div >
< / v-col >
2020-06-26 04:39:14 -07:00
< / v-row >
2020-07-13 19:34:31 -07:00
< / v-card >
< / v-col >
< v-col
2023-01-05 13:25:44 -05:00
cols = "12"
class = "pb-3"
lg = "4"
align - self = "stretch"
2020-07-13 19:34:31 -07:00
>
< v-card
2023-01-05 13:25:44 -05:00
color = "primary"
2020-07-13 19:34:31 -07:00
>
2022-10-30 13:16:17 -05:00
< camera-and-pipeline-select / >
2020-07-13 19:34:31 -07:00
< / v-card >
< v-card
2023-01-05 13:25:44 -05:00
: disabled = "$store.getters.isDriverMode || $store.state.colorPicking"
class = "mt-3"
color = "primary"
2020-07-13 19:34:31 -07:00
>
< v-row
2023-01-05 13:25:44 -05:00
align = "center"
class = "pl-3 pr-3"
2020-07-13 19:34:31 -07:00
>
< v-col lg = "12" >
< p style = "color: white;" >
Processing mode :
< / p >
< v-btn-toggle
2023-01-05 13:25:44 -05:00
v - model = "processingMode"
mandatory
dark
class = "fill"
2020-07-13 19:34:31 -07:00
>
2020-07-31 13:50:50 -07:00
< v-btn
2023-01-05 13:25:44 -05:00
color = "secondary"
2020-07-31 13:50:50 -07:00
>
2020-07-13 19:34:31 -07:00
< v-icon > mdi - crop - square < / v-icon >
< span > 2 D < / span >
< / v-btn >
2020-07-31 13:50:50 -07:00
< v-btn
2023-01-05 13:25:44 -05:00
color = "secondary"
@ click = "on3DClick"
2020-07-31 13:50:50 -07:00
>
< v-icon > mdi - cube - outline < / v-icon >
< span > 3 D < / span >
< / v-btn >
2020-07-13 19:34:31 -07:00
< / v-btn-toggle >
< / v-col >
< v-col lg = "12" >
< p style = "color: white;" >
Stream display :
< / p >
< v-btn-toggle
2023-01-05 13:25:44 -05:00
v - model = "selectedOutputs"
: multiple = "$vuetify.breakpoint.mdAndUp"
mandatory
dark
class = "fill"
2020-07-13 19:34:31 -07:00
>
< v-btn
2023-01-05 13:25:44 -05:00
color = "secondary"
class = "fill"
2020-07-13 19:34:31 -07:00
>
2022-10-09 20:30:16 -05:00
< v-icon > mdi - import < / v-icon >
< span > Raw < / span >
2020-07-13 19:34:31 -07:00
< / v-btn >
< v-btn
2023-01-05 13:25:44 -05:00
color = "secondary"
class = "fill"
2020-07-13 19:34:31 -07:00
>
2022-10-09 20:30:16 -05:00
< v-icon > mdi - export < / v-icon >
< span > Processed < / span >
2020-07-13 19:34:31 -07:00
< / v-btn >
< / v-btn-toggle >
< / v-col >
< / v-row >
< / v-card >
< / v-col >
< / v-row >
< v-row no -gutters >
< v-col
2023-01-05 13:25:44 -05:00
v - for = "(tabs, idx) in tabGroups"
: key = "idx"
: cols = "Math.floor(12 / tabGroups.length)"
: class = "idx !== tabGroups.length - 1 ? 'pr-3' : ''"
align - self = "stretch"
2020-07-13 19:34:31 -07:00
>
< v-card
2023-01-05 13:25:44 -05:00
color = "primary"
height = "100%"
class = "pr-4 pl-4"
2020-07-13 19:34:31 -07:00
>
< v-tabs
2023-01-05 13:25:44 -05:00
v - if = "!$store.getters.isDriverMode"
v - model = "selectedTabs[idx]"
grow
background - color = "primary"
dark
height = "48"
slider - color = "accent"
2020-07-13 19:34:31 -07:00
>
< v-tab
2023-01-05 13:25:44 -05:00
v - for = "(tab, i) in tabs"
: key = "i"
2020-07-13 19:34:31 -07:00
>
{ { tab . name } }
< / v-tab >
< / v-tabs >
< div class = "pl-4 pr-4 pt-2" >
< keep-alive >
< component
2023-01-05 13:25:44 -05:00
: is = "(tabs[selectedTabs[idx]] || tabs[0]).component"
: ref = "(tabs[selectedTabs[idx]] || tabs[0]).name"
v - model = "$store.getters.pipeline"
@ update = "$emit('save')"
2020-07-13 19:34:31 -07:00
/ >
< / keep-alive >
< / div >
< / v-card >
< / v-col >
< / v-row >
< / v-container >
2022-01-16 08:25:37 -08:00
2020-06-26 04:39:14 -07:00
< v-snackbar
2023-01-05 13:25:44 -05:00
v - model = "showNTWarning"
color = "error"
timeout = "-1"
top
2020-06-26 04:39:14 -07:00
>
2022-01-16 08:25:37 -08:00
{ { $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
2023-01-05 13:25:44 -05:00
text
@ click = "hideNTWarning = true"
2022-01-16 08:25:37 -08:00
>
Hide
< / v-btn >
< / template >
2020-06-26 04:39:14 -07:00
< / v-snackbar >
2020-09-04 18:18:44 -07:00
< v-dialog
2023-01-05 13:25:44 -05:00
v - model = "dialog"
width = "500"
2020-09-04 18:18:44 -07:00
>
< v-card
2023-01-05 13:25:44 -05:00
color = "primary"
dark
2020-09-04 18:18:44 -07:00
>
< v-card-title >
Current resolution not calibrated
< / v-card-title >
< v-card-text >
2020-12-08 02:34:21 -05:00
Because the current resolution { { this . $store . getters . currentVideoFormat . width } } x { { this . $store . getters . currentVideoFormat . height } } is not yet calibrated , 3 D mode cannot be enabled . Please
2020-09-04 18:18:44 -07:00
< a
2023-01-05 13:25:44 -05:00
href = "/#/cameras"
class = "white--text"
@ click = "$emit('switch-to-cameras')"
2020-09-04 18:18:44 -07:00
> visit the Cameras tab < / a > to calibrate this resolution . For now , SolvePNP will do nothing .
< / v-card-text >
< v-divider / >
< v-card-actions >
< v-spacer / >
< v-btn
2023-01-05 13:25:44 -05:00
color = "white"
text
@ click = "closeUncalibratedDialog"
2020-09-04 18:18:44 -07:00
>
OK
< / v-btn >
< / v-card-actions >
< / v-card >
< / v-dialog >
2020-06-26 04:39:14 -07:00
< / div >
2020-05-25 22:46:44 +03:00
< / template >
< script >
2020-10-16 16:48:24 -07:00
import CameraAndPipelineSelect from "../components/pipeline/CameraAndPipelineSelect" ;
import cvImage from '../components/common/cv-image' ;
import InputTab from './PipelineViews/InputTab' ;
import ThresholdTab from './PipelineViews/ThresholdTab' ;
import ContoursTab from './PipelineViews/ContoursTab' ;
import OutputTab from './PipelineViews/OutputTab' ;
import TargetsTab from "./PipelineViews/TargetsTab" ;
2022-10-09 22:26:49 -05:00
import Map3DTab from './PipelineViews/Map3DTab' ;
2020-10-16 16:48:24 -07:00
import PnPTab from './PipelineViews/PnPTab' ;
2022-09-28 18:21:41 -07:00
import AprilTagTab from './PipelineViews/AprilTagTab' ;
2023-01-05 13:25:44 -05:00
import ArucoTab from './PipelineViews/ArucoTab' ;
2020-05-25 22:46:44 +03:00
2020-10-16 16:48:24 -07:00
export default {
2023-01-05 13:25:44 -05:00
name : 'Pipeline' ,
components : {
CameraAndPipelineSelect ,
cvImage ,
InputTab ,
ThresholdTab ,
ContoursTab ,
OutputTab ,
TargetsTab ,
Map3DTab ,
PnPTab ,
AprilTagTab ,
ArucoTab ,
} ,
data ( ) {
return {
selectedTabsData : [ 0 , 0 , 0 , 0 ] ,
counterData : 0 ,
dialog : false ,
processingModeOverride : false ,
hideNTWarning : false ,
}
} ,
computed : {
selectedTabs : {
get ( ) {
return this . $store . getters . isDriverMode ? [ 0 ] : this . selectedTabsData ;
} ,
set ( value ) {
this . selectedTabsData = value ;
}
2020-10-16 16:48:24 -07:00
} ,
2023-01-05 13:25:44 -05:00
tabGroups : {
get ( ) {
let tabs = {
input : {
name : "Input" ,
component : "InputTab" ,
} ,
threshold : {
name : "Threshold" ,
component : "ThresholdTab" ,
} ,
contours : {
name : "Contours" ,
component : "ContoursTab" ,
} ,
apriltag : {
name : "AprilTag" ,
component : "AprilTagTab" ,
} ,
aruco : {
name : "Aruco" ,
component : "ArucoTab" ,
} ,
output : {
name : "Output" ,
component : "OutputTab" ,
} ,
targets : {
name : "Targets" ,
component : "TargetsTab" ,
} ,
pnp : {
name : "PnP" ,
component : "PnPTab" ,
} ,
map3d : {
name : "3D" ,
component : "Map3DTab" ,
}
} ;
2020-07-13 19:34:31 -07:00
2023-01-05 13:25:44 -05:00
// If not in 3d, name "3D" is illegal
const allow3d = this . $store . getters . currentPipelineSettings . solvePNPEnabled ;
// If in apriltag, "Threshold" and "Contours" are illegal -- otherwise "AprilTag" is
const isAprilTag = ( this . $store . getters . currentPipelineSettings . pipelineType - 2 ) === 2 ;
const isAruco = ( this . $store . getters . currentPipelineSettings . pipelineType - 2 ) === 3 ;
2022-09-28 18:21:41 -07:00
2023-01-05 13:25:44 -05:00
// 2D array of tab names and component names; each sub-array is a separate tab group
let ret = [ ] ;
if ( this . $vuetify . breakpoint . smAndDown || this . $store . getters . isDriverMode || ( this . $vuetify . breakpoint . mdAndDown && ! this . $store . state . compactMode ) ) {
// One big tab group with all the tabs
ret [ 0 ] = Object . values ( tabs ) ;
} else if ( this . $vuetify . breakpoint . mdAndDown || ! this . $store . state . compactMode ) {
// Two tab groups, one with "input, threshold, contours, output" and the other with "target info, 3D"
ret [ 0 ] = [ tabs . input , tabs . threshold , tabs . contours , tabs . apriltag , tabs . aruco , tabs . output ] ;
ret [ 1 ] = [ tabs . targets , tabs . pnp , tabs . map3d ] ;
} else if ( this . $vuetify . breakpoint . lgAndDown ) {
// Three tab groups, one with "input", one with "threshold, contours, output", and the other with "target info, 3D"
ret [ 0 ] = [ tabs . input ] ;
ret [ 1 ] = [ tabs . threshold , tabs . contours , tabs . apriltag , tabs . aruco , tabs . output ] ;
ret [ 2 ] = [ tabs . targets , tabs . pnp , tabs . map3d ] ;
} else if ( this . $vuetify . breakpoint . xl ) {
// Three tab groups, one with "input", one with "threshold, contours", and the other with "output, target info, 3D"
ret [ 0 ] = [ tabs . input ] ;
ret [ 1 ] = [ tabs . threshold ] ;
ret [ 2 ] = [ tabs . contours , tabs . apriltag , tabs . aruco , tabs . output ] ;
ret [ 3 ] = [ tabs . targets , tabs . pnp , tabs . map3d ] ;
}
2020-10-16 16:48:24 -07:00
2023-01-05 13:25:44 -05:00
for ( let i = 0 ; i < ret . length ; i ++ ) {
const group = ret [ i ] ;
2022-09-28 18:21:41 -07:00
2023-01-05 13:25:44 -05:00
// All the tabs we allow
const filteredGroup = 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 && it . name === "AprilTag" ) //Filter out apriltag unless we actually are doing Apriltags
&& ! ( ! isAruco && it . name === "Aruco" )
) ;
ret [ i ] = filteredGroup ;
}
2022-09-28 18:21:41 -07:00
2023-01-05 13:25:44 -05:00
// One last filter to remove empty lists
return ret . filter ( it => it !== undefined && it . length > 0 ) ;
}
} ,
processingMode : {
get ( ) {
return ( this . $store . getters . currentPipelineSettings . solvePNPEnabled || this . processingModeOverride ) ? 1 : 0 ;
} ,
set ( value ) {
if ( this . $store . getters . isCalibrated ) {
this . $store . getters . currentPipelineSettings . solvePNPEnabled = value === 1 ;
this . handlePipelineUpdate ( "solvePNPEnabled" , value === 1 ) ;
}
}
} ,
driverMode : {
get ( ) {
return this . $store . getters . isDriverMode ;
} ,
set ( value ) {
this . $store . getters . currentCameraSettings . currentPipelineIndex = value ? - 1 : 0 ;
this . handleInputWithIndex ( 'currentPipeline' , value ? - 1 : 0 ) ;
}
} ,
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)
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 = [ ] ;
if ( this . $store . state . colorPicking ) {
ret = [ 0 ] ; // We want the input stream only while color picking
} else if ( this . $store . getters . isDriverMode ) {
ret = [ 1 ] ; // We want only the output stream in driver mode
} else {
if ( this . $store . getters . currentPipelineSettings . inputShouldShow ) ret = ret . concat ( [ 0 ] ) ;
if ( this . $store . getters . currentPipelineSettings . outputShouldShow ) ret = ret . concat ( [ 1 ] ) ;
if ( ! ret . length ) ret = [ 0 ] ;
}
2020-07-13 19:34:31 -07:00
2023-01-05 13:25:44 -05:00
if ( this . $vuetify . breakpoint . mdAndUp ) {
return ret ;
} else {
return ret [ 0 ] || 0 ;
}
} ,
set ( value ) {
let valToCommit = [ 0 ] ;
if ( value instanceof Array ) {
// Value is already an array, we don't need to do anything
valToCommit = value ;
} else if ( value ) {
// Value is assumed to be a number, so we wrap it into an array
valToCommit = [ value ] ;
}
2020-12-08 02:34:21 -05:00
2023-01-05 13:25:44 -05:00
this . $store . commit ( "mutatePipeline" , { "inputShouldShow" : valToCommit . includes ( 0 ) } ) ;
this . $store . commit ( "mutatePipeline" , { "outputShouldShow" : valToCommit . includes ( 1 ) } ) ;
this . handlePipelineUpdate ( "inputShouldShow" , valToCommit . includes ( 0 ) ) ;
}
2020-10-16 16:48:24 -07:00
} ,
2023-01-05 13:25:44 -05:00
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
2023-01-06 17:25:11 -08:00
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 isReflective = this . $store . getters . pipelineType === 2 ;
return ( currFPS - targetFPS ) < - 5 && this . $store . state . pipelineResults . fps !== 0 && ! driverMode && gpuAccel && isReflective ;
2023-01-05 13:25:44 -05:00
}
2020-10-16 16:48:24 -07:00
} ,
2023-01-05 13:25:44 -05:00
latency : {
get ( ) {
return this . $store . getters . currentPipelineResults . latency ;
}
} ,
isCalibrated : {
get ( ) {
const resolution = this . $store . getters . videoFormatList [ this . $store . getters . currentPipelineSettings . cameraVideoModeIndex ] ;
return this . $store . getters . currentCameraSettings . calibrations
. some ( e => e . width === resolution . width && e . height === resolution . height )
}
} ,
isRobotConnected : {
get ( ) {
// return this.$store.state.ntConnectionInfo.connected && this.$store.state.backendConnected;
return true ;
}
} ,
showNTWarning : {
get ( ) {
return ( ! this . $store . state . ntConnectionInfo . connected || this . $store . state . settings . networkSettings . runNTServer ) && this . $store . state . settings . networkSettings . teamNumber > 0 && this . $store . state . backendConnected && ! this . hideNTWarning ;
}
} ,
} ,
created ( ) {
this . $store . state . connectedCallbacks . push ( this . reloadStreams )
} ,
methods : {
reloadStreams ( ) {
// Reload the streams as we technically close and reopen them
this . $refs . streams . forEach ( it => it . reload ( ) )
} ,
onImageClick ( event ) {
// Get a reference to the threshold tab (if it is shown) and call its "onClick" method
let ref = this . $refs [ "Threshold" ] ;
if ( ref && ref [ 0 ] )
ref [ 0 ] . onClick ( event )
} ,
on3DClick ( ) {
if ( ! this . $store . getters . isCalibrated ) {
this . dialog = true ;
this . processingModeOverride = true ;
}
} ,
closeUncalibratedDialog ( ) {
this . dialog = false ;
this . processingModeOverride = false ;
// this.$store.getters.currentPipelineSettings.solvePNPEnabled = false;
this . handlePipelineUpdate ( "solvePNPEnabled" , false ) ;
2020-05-25 22:46:44 +03:00
}
2023-01-05 13:25:44 -05:00
}
2020-10-16 16:48:24 -07:00
}
2020-05-25 22:46:44 +03:00
< / script >
< style scoped >
2020-10-16 16:48:24 -07:00
. v - btn - toggle . fill {
2023-01-05 13:25:44 -05:00
width : 100 % ;
height : 100 % ;
2020-10-16 16:48:24 -07:00
}
2020-07-13 19:34:31 -07:00
2020-10-16 16:48:24 -07:00
. v - btn - toggle . fill > . v - btn {
2023-01-05 13:25:44 -05:00
width : 50 % ;
height : 100 % ;
2020-10-16 16:48:24 -07:00
}
2020-07-13 19:34:31 -07:00
2020-10-16 16:48:24 -07:00
th {
2023-01-05 13:25:44 -05:00
width : 80 px ;
text - align : center ;
2020-10-16 16:48:24 -07:00
}
2021-11-21 17:22:56 -08:00
< / style >