add inference result

This commit is contained in:
hofee 2024-09-28 18:13:03 +08:00
parent 5c4a8b7abf
commit 3c86c1e241
5 changed files with 534 additions and 7 deletions

31
package-lock.json generated
View File

@ -4580,6 +4580,22 @@
"safer-buffer": "^2.1.0"
}
},
"echarts": {
"version": "5.5.1",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
"requires": {
"tslib": "2.3.0",
"zrender": "5.6.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
},
"editorconfig": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
@ -17562,6 +17578,21 @@
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"zrender": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
"requires": {
"tslib": "2.3.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
}
}
}

View File

@ -15,6 +15,7 @@
},
"dependencies": {
"axios": "^0.26.1",
"echarts": "^5.5.1",
"video.js": "^7.19.2",
"videojs-contrib-hls": "^5.15.0",
"view-design": "^4.7.0",

View File

@ -80,16 +80,19 @@
<div class="layout-logo">
<img :src="logo" alt="Logo" style="width: 60px; margin-right: 20px;" />{{ project_name }}</div>
</Menu>
<Menu mode="horizontal" active-name="1" @on-select="handleSubMenuSelect" class="sub_menu">
<Menu mode="horizontal" active-name="3" @on-select="handleSubMenuSelect" class="sub_menu">
<div class="layout-assistant">
<Menu-item name="1">
<Icon type="md-pie" size="16" />{{ sub_navi_name[0] }}
</Menu-item>
<Menu-item name="2">
<Icon type="md-stats" size="16" />{{ sub_navi_name[1] }}
<Icon type="ios-image" size="16" />{{ sub_navi_name[1] }}
</Menu-item>
<Menu-item name="3">
<Icon type="md-barcode" size="16" />{{ sub_navi_name[2] }}
<Icon type="md-stats" size="16" />{{ sub_navi_name[2] }}
</Menu-item>
<Menu-item name="4">
<Icon type="md-barcode" size="16" />{{ sub_navi_name[3] }}
</Menu-item>
</div>
</Menu>
@ -112,17 +115,17 @@
import SequenceVisualize from '@/components/content/SeqVisualize.vue'
import SeqInferenceResultVisualize from '@/components/content/SeqInferenceResultVisualize.vue';
import OnlineInference from '@/components/content/OnlineInference.vue';
import InferenceResultAnalysis from '@/components/content/InferenceResultAnalysis.vue';
export default {
data() {
return {
project_name: "Next Best View for 3D Reconstruction",
logo: require('@/assets/white_logo.png'),
sub_navi_name: ["Sequence Visualization", "Inference Result Visualization", "Online Inference"],
sub_navi_name: ["Sequence Visualization", "Inference Result Visualization", "Inference Result Analysis","Online Inference"],
curr_navi_idx: 0,
curr_sub_navi_idx: 0,
components: [SequenceVisualize, SeqInferenceResultVisualize, OnlineInference],
curr_sub_navi_idx: 2,
components: [SequenceVisualize, SeqInferenceResultVisualize, InferenceResultAnalysis, OnlineInference],
};
},
mounted() {

View File

@ -0,0 +1,108 @@
<template>
<div style="padding: 40px;">
<div style="display: flex; justify-content: center;">
<h1>Inference Result Analysis</h1>
</div>
<Divider />
<StatResultAnalysis
v-for="(result, name) in results"
:key="name"
:result="result"
:name="name"
/>
<Divider/>
<Upload multiple type="drag" action="" accept=".json" :before-upload="beforeUploadInferenceResultFile"
style="width: 100%; position: relative; margin-top:10px">
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff" v-if="!loading">
</Icon>
<p v-if="!loading">Select or drag the inference statistical result file(.json) here to upload
</p>
<div class="demo-spin-container" v-if="loading">
<Spin fix size="large"></Spin>
</div>
<p v-if="loading">Waiting for server response...</p>
</div>
</Upload>
</div>
</template>
<script>
import StatResultAnalysis from './cards/StatResultAnalysis.vue';
export default {
name: "InferenceResultAnalysis",
components: {
StatResultAnalysis
},
watch: {
},
computed: {
},
data() {
return {
results: {},
loading: false,
};
},
mounted() {
},
methods: {
beforeUploadInferenceResultFile(file) {
this.loading = true;
const isJson = file.name.endsWith('.json');
var result_name = file.name;
if (!isJson) {
this.$Notice.warning({
title: 'File format error',
desc: 'You can only upload a .json file, current file: ' + file.name
});
this.loading = false;
return false;
}
const reader = new FileReader();
reader.onload = (event) => {
try {
const jsonObject = JSON.parse(event.target.result);
var repeat = 0;
while (this.results[result_name]) {
result_name = file.name + `(${repeat})`;
repeat++;
}
this.results[result_name] = jsonObject;
console.log(this.results);
} catch (error) {
console.log(error)
this.$Notice.error({
title: 'File parse error',
desc: `The JSON file(${file.name}) could not be parsed.`
});
} finally {
this.loading = false;
}
};
reader.readAsText(file);
return false;
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,384 @@
<template>
<Card style="width: 100%;">
<p slot="title" style="font-size: 18px; font-weight: bold; color: #464c5b;">
<Icon type="md-stats" size="20" style="color: #464c5b;" />
Inference Result: <span style="color: green;">{{ name }}</span>
</p>
<Alert show-icon><span style="font-weight: bold;">Success Rate(SR)</span> = Predicted Final Coverage Rate / Max
GT Final Coverage Rate</Alert>
<Row>
<!-- 左侧展示条形图 -->
<Col span="12">
<Card>
<p slot="title" style="font-size: 16px; font-weight: bold;">Success Rate Histogram</p>
<div ref="histogramChart" style="height: 600px; width: 100%;"></div>
</Card>
</Col>
<!-- 右侧展示饼状图 -->
<Col span="12">
<Card>
<p slot="title" style="font-size: 16px; font-weight: bold;">Success Rate Pie Chart</p>
<div ref="pieChart" style="height: 600px; width: 100%;"></div>
</Card>
</Col>
</Row>
<Row style="margin-top: 10px;">
<Card style="width: 100%; height: 600px;">
<p slot="title" style="font-size: 16px; font-weight: bold;">Single Object Analysis</p>
<Row>
<Col span="8">
<Row style="margin-bottom: 10px; margin-left: 10px; margin-right: 10px;">
<Col span="8">
<p style="font-size: 16px; color: #464c5b; font-weight: bold;">Select Scene:</p>
</Col>
<Col span="14">
<Select v-model="sceneName" style="width: 100%;" @on-change="handleSceneChange"
placeholder="please select..." filterable>
<Option v-for="(value, key) in result" :value="key" :key="key" :label="key"><span>{{ key
}}</span>
<span style="float:right;color:#ccc">(SR=<span
:style="{ color: getSuccessRateColor(result[key]['success_rate']) }">{{
(result[key]["success_rate"] * 100).toFixed(2) }}%</span>)</span>
</Option>
</Select>
</Col>
</Row>
</Col>
<Col span="1">
<Divider type="vertical" style="height: 100%;" />
</Col>
<Col span="14">
<span style="font-weight: bold;">Max Coverage Rate: <span style="color: green;">{{ result[sceneName]
? (result[sceneName]["max_coverage_rate"] * 100).toFixed(2) : 0 }}%</span></span>
<Divider type="vertical" style="height: 100%;" />
<span style="font-weight: bold;">Pred Coverage Rate: <span style="color: green;">{{
result[sceneName]
? (result[sceneName]["pred_max_coverage_rate"]*100).toFixed(2) : 0 }}%</span></span>
<Divider type="vertical" style="height: 100%;" />
<span style="font-weight: bold;">Success Rate: <span style="color: green;">{{ result[sceneName] ?
(result[sceneName]["success_rate"] * 100).toFixed(2) : 0 }}%</span></span>
<Divider type="vertical" style="height: 100%;" />
<span style="font-weight: bold;">Pred Length/GT Length:
<span style="color: red;">{{ result[sceneName] ? (result[sceneName]["pred_seq_len"]) : 0
}}</span>/<span style="color: green;">?</span>
</span>
</Col>
</Row>
<Divider />
<Row>
<Card style="height: 400px; width: 100%;">
<p slot="title" style="font-size: 16px; font-weight: bold;">Prediction Curve</p>
<div ref="predictionCurve" style="height: 350px; width: 100%;"></div>
</Card>
</Row>
</Card>
</Row>
</Card>
</template>
<script>
import * as echarts from 'echarts';
import { Divider } from 'view-design';
export default {
name: "StatResultAnalysis",
props: {
result: Object,
name: String,
},
mounted() {
console.log(this.result);
this.drawCharts();
},
data() {
return {
sceneName: null,
};
},
methods: {
getSuccessRateColor(rate) {
if (rate >= 0.7) {
return 'green';
} else if (rate >= 0.4) {
return 'orange';
} else {
return 'red';
}
},
handleSceneChange(val) {
if (val) {
this.sceneName = val;
console.log("Selected scene name:", this.sceneName);
this.drawLines(); // 线
}
},
drawLines() {
if (!this.sceneName || !this.result[this.sceneName]) return;
const coverageRateSeq = this.result[this.sceneName]["coverage_rate_seq"];
const maxCoverageRate = this.result[this.sceneName]["max_coverage_rate"];
const successRateSeq = coverageRateSeq.map(rate => (rate * 100 / maxCoverageRate).toFixed(2)); //
// 线
const predictionCurve = echarts.init(this.$refs.predictionCurve);
const lineOption = {
tooltip: {
trigger: 'axis',
formatter: (params) => {
let tooltip = params[0].name + '<br>';
params.forEach(item => {
tooltip += `${item.seriesName}: ${item.data}%<br>`;
});
return tooltip;
}
},
xAxis: {
type: 'category',
data: coverageRateSeq.map((_, index) => `${index + 1}`), // x
},
yAxis: {
type: 'value',
name: 'Coverage Rate',
axisLabel: {
formatter: '{value}%'
}
},
series: [
{
name: 'Coverage Rate',
type: 'line',
data: coverageRateSeq.map(rate => (rate * 100).toFixed(2)),
itemStyle: {
color: '#3399ff'
},
smooth: false // 线
},
{
name: 'Success Rate',
type: 'line',
data: successRateSeq,
itemStyle: {
color: '#ff5733' //
},
smooth: false // 线
},
{
name: 'Max Coverage Rate',
type: 'line',
lineStyle: {
type: 'dashed' // 线
},
markLine: {
data: [
{
name: 'GT Max Coverage Rate',
yAxis: (maxCoverageRate * 100).toFixed(2),
label: {
formatter: '{c}%' // 线
}
}
],
},
}
]
};
predictionCurve.setOption(lineOption);
},
drawCharts() {
//
const successRates = Object.values(this.result).map(item => item.success_rate);
//
const avgSuccessRate = parseFloat((successRates.reduce((sum, rate) => sum + rate, 0) / successRates.length).toFixed(4));
console.log("avg", avgSuccessRate);
//
const sortedRates = [...successRates].sort((a, b) => a - b);
const mid = Math.floor(sortedRates.length / 2);
const medianSuccessRate = parseFloat((sortedRates.length % 2 !== 0 ? sortedRates[mid] : ((sortedRates[mid - 1] + sortedRates[mid]) / 2)).toFixed(4));
//
const intervals = ['0-10%', '10-20%', '20-30%', '30-40%', '40-50%', '50-60%', '60-70%', '70-80%', '80-90%', '90-100%', '>=100%'];
const intervalCounts = new Array(intervals.length).fill(0);
//
successRates.forEach(rate => {
let index = Math.min(Math.floor(rate * 10), 10); // 100%
intervalCounts[index]++;
});
//
const histogramData = intervalCounts.map((count, index) => ({
name: intervals[index],
value: count
}));
const histogramChart = echarts.init(this.$refs.histogramChart);
const seriesData = [
{
name: 'Number of Objects',
type: 'bar',
data: histogramData.map(item => item.value),
itemStyle: {
color: '#3399ff'
},
markPoint: {
data: [
{
name: 'Median',
coord: [medianSuccessRate * 10 - 1, histogramData[Math.floor(medianSuccessRate * 10)].value],
label: {
formatter: `Median: \n${(medianSuccessRate * 100).toFixed(2)}%`,
position: 'top'
},
itemStyle: {
color: 'green'
}
},
{
name: 'Avg',
coord: [avgSuccessRate * 10 - 1, histogramData[Math.floor(avgSuccessRate * 10)].value],
label: {
formatter: `Average: \n${(avgSuccessRate * 100).toFixed(2)}%`,
position: 'top'
},
itemStyle: {
color: 'orange'
}
},
]
}
}
];
const histogramOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: intervals,
name: 'SR (%)'
},
yAxis: {
type: 'value',
name: 'Number of Objects'
},
series: seriesData
};
histogramChart.setOption(histogramOption);
const pieIntervals = ['0-40%', '40-70%', '70-100%', '>=100%'];
const pieIntervalCounts = new Array(intervals.length).fill(0);
successRates.forEach(rate => {
if (rate <= 0.4) {
pieIntervalCounts[0]++;
} else if (rate <= 0.7) {
pieIntervalCounts[1]++;
} else if (rate < 1.0) {
pieIntervalCounts[2]++;
} else {
pieIntervalCounts[3]++;
}
});
//
const innerPieData = pieIntervals.map((interval, index) => ({
name: interval,
value: pieIntervalCounts[index]
}));
//
const outerPieData = histogramData.map(item => ({
name: item.name,
value: item.value
}));
//
const pieChart = echarts.init(this.$refs.pieChart);
const pieOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
data: outerPieData.map(item => item.name) // 使
},
series: [
{
name: 'Success Rate',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: innerPieData //
},
{
name: 'Success Rate',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: outerPieData //
}
]
};
pieChart.setOption(pieOption);
}
}
};
</script>
<style scoped>
/* 添加必要的样式 */
</style>