add inference result
This commit is contained in:
parent
5c4a8b7abf
commit
3c86c1e241
31
package-lock.json
generated
31
package-lock.json
generated
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
|
108
src/components/content/InferenceResultAnalysis.vue
Normal file
108
src/components/content/InferenceResultAnalysis.vue
Normal 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>
|
384
src/components/content/cards/StatResultAnalysis.vue
Normal file
384
src/components/content/cards/StatResultAnalysis.vue
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user