Browse Source

!1145 UI XAI / page of saliency-map add functionality to support api of uncertainty and add page of HOC

From: @weiyanxi
Reviewed-by: @ouwenchang,@lixiaohui33
Signed-off-by:
tags/v1.2.0-rc1
mindspore-ci-bot Gitee 4 years ago
parent
commit
8d10627db1
10 changed files with 1569 additions and 317 deletions
  1. +40
    -13
      mindinsight/ui/src/components/search-select.vue
  2. +21
    -4
      mindinsight/ui/src/locales/en-us.json
  3. +21
    -4
      mindinsight/ui/src/locales/zh-cn.json
  4. +15
    -2
      mindinsight/ui/src/main.js
  5. +5
    -1
      mindinsight/ui/src/router.js
  6. +7
    -0
      mindinsight/ui/src/services/request-service.js
  7. +5
    -0
      mindinsight/ui/src/store.js
  8. +917
    -0
      mindinsight/ui/src/views/explain/conterfactual-interpretation.vue
  9. +495
    -280
      mindinsight/ui/src/views/explain/saliency-map.vue
  10. +43
    -13
      mindinsight/ui/src/views/explain/summary-list.vue

+ 40
- 13
mindinsight/ui/src/components/search-select.vue View File

@@ -110,7 +110,7 @@ limitations under the License.
* The PublicStore holds the key of focused selector
* When there is more than two component in same page, help selector to keep correct display
*/
const PublicStore = {activeKey: {key: ''}};
const PublicStore = { activeKey: { key: '' } };
export default {
props: {
multiple: {
@@ -215,8 +215,11 @@ export default {
* The logic of click event that add to window, which can make response to defocus
*/
clickHandler() {
this.ifFocus = false;
this.ifActive = false;
if (this.ifFocus) {
this.ifFocus = false;
this.ifActive = false;
this.$emit('selectBlur');
}
},
/**
* The logic of click the selector, including everywhere
@@ -310,9 +313,14 @@ export default {
* @param {Number} index
*/
mulDeselectOption(option, index) {
const indexTemp = this.indexes.indexOf(index);
this.indexes.splice(indexTemp, 1);
option.selected = false;
return new Promise((resolve) => {
const indexTemp = this.indexes.indexOf(index);
this.indexes.splice(indexTemp, 1);
option.selected = false;
this.$nextTick(() => {
resolve(true);
});
});
},
/**
* The logic of select option when multiple is true
@@ -361,7 +369,11 @@ export default {
cancelLabel(event, index) {
event.stopPropagation();
event.preventDefault();
this.mulDeselectOption(this.options[index], index);
this.mulDeselectOption(this.options[index], index).then(() => {
if (!this.ifActive) {
this.$emit('cancelLabel');
}
});
},
},
created() {
@@ -411,7 +423,7 @@ export default {
}, this.filterDebounce);
},
// The watcher of source can process asynchronous data input, or make response when original data changed
'source': {
source: {
handler(newVal) {
this.indexes = [];
this.options = this.processData(newVal);
@@ -427,7 +439,7 @@ export default {
}
},
},
'indexes': {
indexes: {
handler() {
this.$nextTick(() => {
this.$emit('selectedUpdate', this.calValues());
@@ -558,16 +570,29 @@ export default {
.cl-search-select .select-container .option-container .select-option:hover {
background-color: #f5f7fa;
}
.cl-search-select .select-container .option-container .select-option .label-container {
.cl-search-select
.select-container
.option-container
.select-option
.label-container {
white-space: nowrap;
max-width: 320px;
text-overflow: ellipsis;
overflow: hidden;
}
.cl-search-select .select-container .option-container .select-option .icon-container {
.cl-search-select
.select-container
.option-container
.select-option
.icon-container {
width: 14px;
}
.cl-search-select .select-container .option-container .select-option .icon-container .icon-no-selected {
.cl-search-select
.select-container
.option-container
.select-option
.icon-container
.icon-no-selected {
display: none;
}
.cl-search-select .select-container .option-container .is-selected {
@@ -591,7 +616,9 @@ export default {
-webkit-box-shadow: inset 0 0 6px rgba(144, 147, 153, 0.3);
background-color: #e8e8e8;
}
.cl-search-select .select-container .option-container::-webkit-scrollbar-thumb:hover {
.cl-search-select
.select-container
.option-container::-webkit-scrollbar-thumb:hover {
-webkit-box-shadow: inset 0 0 6px rgba(144, 147, 153, 0.3);
background-color: #cacaca;
border-radius: 3px;


+ 21
- 4
mindinsight/ui/src/locales/en-us.json View File

@@ -23,7 +23,8 @@
"deselectAll": "Clear",
"dataLoading": "Loading data...",
"notice": "Information",
"caseMode": "Not case sensitive"
"caseMode": "Not case sensitive",
"all": "All"
},
"symbols": {
"leftbracket": "(",
@@ -799,24 +800,34 @@
"explainSummaryCurrentFolder": "Root path of the explanation log:",
"summaryPath": "Explanation Log Path",
"title": "Saliency Map Visualization",
"conterfactualInterpretation": "Counterfactual Explanation",
"explainMethod": "Explanation Methods",
"viewScore": "View Score",
"fetch": "Filter",
"minConfidence": "Probability Threshold",
"confidenceRange": "Probability and Range",
"imgSort": "Sort Images By",
"default": "Default",
"byProbability": "Probabilities in descending order",
"byUncertainty": "Uncertainties in descending order",
"uncertainty": "uncertainty",
"superposeImg": "Overlay on Original Image",
"originalPicture": "Original Image",
"forecastTag": "Prediction Tag",
"forecastTagPosibility": "Prediction Tag (Probability)",
"tag": "Tag",
"tagTip": "Query the image data that contains any filter label",
"filterImg": "Filter Image",
"tagTip": "Query image data that contains any filter tag",
"typeTip": "Query image data that contains any prediction type",
"confidence": "Probability",
"forecastTagTip": "When the inference image has the correct tag, the following four flags are displayed in the tag row",
"forecastTagTip": "When the inference image has the correct tag, the following three flags are displayed in the tag row",
"TP": "TP, indicates true positive. The tag is a positive sample, and the classification is a positive sample;",
"FN": "FN, indicates false negative. The tag is a positive sample, and the classification is a negative sample;",
"FP": "FP, indicates false positive. The tag is a negative sample, and the classification is a positive sample;",
"TN": "TN, Indicates true negative. The tag is a negative sample, and the classification is a negative sample;",
"coverfactualInterpretation": "Mask-based Counterfactual Explanation",
"viewExplanation": "View Explanation",
"maskingProcess": "Layer-by-layer Masking Process",
"mainTipTitle": "Function description:",
"mainTipPartOne": "This function visualizes the basis for model classification. After the to-be-explained model, the image, and the tag are selected, a contribution degree of each pixel in the original image to the selected tag is calculated by using an explanation method, and visualization is performed by using a saliency map similar to a heatmap. A brighter color indicates that the corresponding area contributes more to the selected tag of the model prediction. The darker the color, the smaller the contribution of the area to the selected tag.",
"mainTipPartTwo": "A saliency map helps you understand the features related to the specified tag during deep neural network inference. When the inference basis and expectation of a model are different, you can debug the model by referring to the saliency map so that the model can perform inference based on proper features.",
@@ -824,7 +835,13 @@
"mainTipPartFour": "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9050829",
"noExplainer": "Select Explanation Method",
"minConfidenceTip": "Probability threshold of a prediction tag. A tag is recorded as a prediction tag if its output probability is greater than the threshold.",
"noData": "Loading data, please refresh the page later"
"noData": "Loading data... Refresh the page later",
"predictionType": "Prediction Type",
"disableSaliencyMapTip": "Failed to view saliency map visualization because no saliency map log is available.",
"disableHOCTip": "Failed to view counterfactual explanation because no counterfactual explanation log is available.",
"hocMinConfidenceTip": "Display and explain the labels whose prediction probability is greater than the prediction threshold",
"imageList": "Picture list",
"hocTitleTip": "Gradually shrink the display range to find the minimum display area where the prediction probability is greater than the threshold"
},
"metric": {
"scoreSystem": "Scoring System",


+ 21
- 4
mindinsight/ui/src/locales/zh-cn.json View File

@@ -23,7 +23,8 @@
"deselectAll": "取消全选",
"dataLoading": "数据加载中",
"notice": "提示",
"caseMode": "不区分大小写"
"caseMode": "不区分大小写",
"all": "全部"
},
"symbols": {
"leftbracket": "(",
@@ -795,24 +796,34 @@
"explainSummaryCurrentFolder": "解释日志根路径:",
"summaryPath": "解释日志路径",
"title": "显著图可视化",
"conterfactualInterpretation": "反事实解释",
"explainMethod": "解释方法",
"viewScore": "查看评分",
"fetch": "筛选",
"minConfidence": "概率阈值",
"confidenceRange": "概率(区间)",
"imgSort": "图片排序",
"default": "默认",
"byProbability": "概率值降序",
"byUncertainty": "不确定性值降序",
"uncertainty": "不确定性",
"superposeImg": "叠加于原图",
"originalPicture": "原始图片",
"forecastTag": "预测标签",
"forecastTagPosibility": "预测标签 (概率)",
"tag": "标签",
"filterImg": "图片筛选",
"tagTip": "查询包含任一筛选标签的图片数据",
"typeTip": "查询包含任一预测类型的图片数据",
"confidence": "概率",
"forecastTagTip": "当推理图片带有正确标签时,标签行会显示下列种旗标",
"forecastTagTip": "当推理图片带有正确标签时,标签行会显示下列种旗标",
"TP": "TP,代表Ture Positive,标签为正样本,分类为正样本;",
"FN": "FN,代表False Negative,标签为正样本,分类为负样本;",
"FP": "FP,代表Fasle Positive,标签为负样本,分类为正样本;",
"FP": "FP,代表False Positive,标签为负样本,分类为正样本;",
"TN": "TN,代表Ture Negative,标签为负样本,分类为负样本;",
"coverfactualInterpretation": "遮掩反事实解释",
"viewExplanation": "查看解释",
"maskingProcess": "逐层遮掩过程",
"mainTipTitle": "功能说明:",
"mainTipPartOne": "本功能对模型分类的依据进行可视化。选定待解释模型、图片和标签后,解释方法计算得到原始图像中每个像素对选定标签的贡献度,以类似热力图的显著图进行可视。显著图颜色越亮,表示对应区域对于模型预测选定标签的贡献越多;颜色越暗,该区域对选定标签的贡献越小。",
"mainTipPartTwo": "显著图可以帮助我们了解深度神经网络推理时,和指定标签有关系的特征。当模型推理依据和期望不同时,可以参考显著图对模型进行调试,让模型依据合理的特征进行推理。",
@@ -820,7 +831,13 @@
"mainTipPartFour": "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9050829",
"noExplainer": "请选择解释方法",
"minConfidenceTip": "预测标签的概率阈值,当一个标签的输出概率大于阈值时就会被记为预测标签。",
"noData": "数据正在加载,请稍后刷新页面"
"noData": "数据正在加载,请稍后刷新页面",
"predictionType": "预测类型",
"disableSaliencyMapTip": "无显著图可视日志,无法查看",
"disableHOCTip": "无反事实解释日志,无法查看",
"hocMinConfidenceTip": "对预测概率大于预测阈值的标签进行显示解释",
"imageList": "图片列表",
"hocTitleTip": "逐步收缩显示范围,以寻找出预测概率大于阈值的最小显示区域"
},
"metric": {
"scoreSystem": "评分体系",


+ 15
- 2
mindinsight/ui/src/main.js View File

@@ -29,10 +29,23 @@ import {basePath} from '@/services/fetcher';

let language = window.localStorage.getItem('milang');
const languageList = ['zh-cn', 'en-us'];
if (!language || !languageList.includes(language)) {
language = languageList[1];
if (!language) {
let tempLang = navigator.language || navigator.userLanguage;
tempLang = tempLang.substr(0, 2);
if (tempLang === 'zh') {
language = languageList[0];
} else {
language = languageList[1];
}
window.localStorage.setItem('milang', language);
} else {
if (!languageList.includes(language)) {
// set English if no default language
language = languageList[1];
window.localStorage.setItem('milang', language);
}
}
store.commit('setLanguage', language);

if (language !== languageList[0]) {
Vue.use(ElementUI, {locale});


+ 5
- 1
mindinsight/ui/src/router.js View File

@@ -139,8 +139,12 @@ export default new Router({
},
{
path: '/explain/saliency-map',
component: () => import('./views/explain/saliency-map.vue'),
},
{
path: '/explain/conterfactual-interpretation',
component: () =>
import('./views/explain/saliency-map.vue'),
import('./views/explain/conterfactual-interpretation.vue'),
},
{
path: '/explain/xai-metric',


+ 7
- 0
mindinsight/ui/src/services/request-service.js View File

@@ -440,6 +440,13 @@ export default {
params: params,
});
},
queryHOCData(params) {
return axios({
method: 'post',
url: 'v1/mindinsight/explainer/hoc',
data: params,
});
},
tensorHitsData(params) {
return axios({
method: 'get',


+ 5
- 0
mindinsight/ui/src/store.js View File

@@ -40,6 +40,8 @@ export default new Vuex.Store({
// Echart column selected by model traceability box
selectedBarList: [],
customizedColumnOptions: [],
// Current language
language: 'en-us',
},
mutations: {
// set cancelTokenArr
@@ -87,6 +89,9 @@ export default new Vuex.Store({
componentsNum(state) {
state.componentsCount++;
},
setLanguage(state, val) {
state.language = val;
},
},
actions: {},
});

+ 917
- 0
mindinsight/ui/src/views/explain/conterfactual-interpretation.vue View File

@@ -0,0 +1,917 @@
<!--
Copyright 2021 Huawei Technologies Co., Ltd.All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<div class="cl-hoc">
<div class="cl-hoc-container">
<div class="cl-hoc-title cl-hoc-tip-container">
{{$t('explain.coverfactualInterpretation')}}
<el-tooltip placement="right-start"
effect="light">
<div slot="content"
class="tooltip-container">
<div class="tip-text">
{{$t('explain.hocTitleTip')}}
</div>
</div>
<i class="el-icon-info"></i>
</el-tooltip>
</div>
<div class="cl-hoc-con">
<div class="cl-hoc-left">
<!-- Top Bar -->
<div class="cl-left-top-container">
<div class="hoc-title-container left-title">
{{$t('explain.imageList')}}
</div>
<!-- Selecting a hoc image -->
<div class="hoc-filter-container">
<div class="title-text">{{$t('explain.tag')}}</div>
<div class="select-options">
<el-select v-model="curFilterLabel"
:placeholder="$t('public.select')"
@change="filterLabelChange"
size="small">
<el-option v-for="item in labelLlist"
:key="item.label"
:label="item.label"
:value="item.label"></el-option>
</el-select>
</div>
</div>
<div class="hoc-filter-container">
<div class="title-text">{{$t('explain.imgSort')}}</div>
<div class="select-options">
<el-select v-model="pageData.sortName"
:placeholder="$t('public.select')"
size="small">
<el-option v-for="item in sortOption"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
</div>
</div>
</div>
<!-- Thumbnail bar -->
<div class="cl-left-middle-container">
<div class="cl-left-thumb"
v-if="!pageData.totalNum || !initOver">
<!-- No data -->
<div class="image-noData">
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<div v-if="initOver"
class="noData-text">{{$t("public.noData")}}</div>
<div v-else
class="noData-text">{{$t("public.dataLoading")}}</div>
</div>
</div>
<div class="cl-left-thumb"
v-else>
<div class="cl-left-thumb-item"
v-for="(data, index) in fullData"
:key="data.id"
:class="index === curSelectedDataIndex ? 'active' : ''"
@click="dataSelect(index)">
<img :src="formateUrl(data.image)">
</div>
</div>
<!-- pagination -->
<div class="cl-left-page">
<el-pagination layout="prev, pager ,next, jumper"
:total="pageData.totalNum"
:current-page="pageData.curPage"
:pager-count="5"
:page-size="pageData.pageSize"
@current-change="pageChange"></el-pagination>
</div>
</div>
</div>
<div class="cl-hoc-right">
<div class="cl-right-top">
<div class="cl-right-top-item left-item">
<!-- Original image label selection bar -->
<div class="cl-right-title">
<div class="hoc-title-container ori-image-title">
{{$t('explain.originalPicture')}}
</div>
<div class="ori-tag-select-container">
<div class="ori-select-title"
:title="$t('explain.forecastTagPosibility')">
{{$t('explain.forecastTagPosibility')}}
</div>
<div class="cl-left-options">
<el-select v-model="curImageData.selectedLabel"
:placeholder="$t('public.select')"
size="small"
@change="selectLableChange">
<el-option v-for="item in curImageData.labels"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
</div>
</div>
</div>
<!-- Original image -->
<div class="ori-image-container">
<img v-if="curImageData.src"
:src="formateUrl(curImageData.src)">
<div class="image-noData"
v-else>
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<div v-if="initOver"
class="noData-text">{{$t("public.noData")}}</div>
<div v-else
class="noData-text">{{$t("public.dataLoading")}}</div>
</div>
</div>
</div>
<div class="cl-right-top-item">
<div class="cl-right-title">
<div class="hoc-title-container">{{$t('explain.viewExplanation')}}</div>
<div class="cl-right-title-silde">
<div class="cl-right-title-label cl-hoc-tip-container">
{{ $t('explain.minConfidence') }}
<el-tooltip placement="bottom-start"
effect="light">
<div slot="content">
{{ $t('explain.hocMinConfidenceTip') }}
</div>
<i class="el-icon-info"></i>
</el-tooltip>
<span>{{ $t('symbols.colon') }}</span>
<span>{{ minConfidence }}</span>
</div>
</div>
</div>
<div class="cl-right-con">
<div class="img-container">
<img v-if="curImageData.curSampleData.hoc_layers && curImageData.curSampleData.hoc_layers.length"
:src="formateUrl(curImageData.curSampleData.hoc_layers[curImageData.imageIndex].outcome)"
alt="">
<div class="image-noData"
v-else>
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<div v-if="initOver"
class="noData-text">{{$t("public.noData")}}</div>
<div v-else
class="noData-text">{{$t("public.dataLoading")}}</div>
</div>
</div>
<div class="image-title"
v-if="curImageData.curSampleData.hoc_layers && curImageData.curSampleData.hoc_layers.length">
{{curImageData.curSampleData.label}}
{{$t('symbols.leftbracket')}}
{{curImageData.curSampleData.hoc_layers[curImageData.imageIndex].confidence.toFixed(3)}}
{{$t('symbols.rightbracket')}}
</div>
</div>
</div>
</div>
<div class="cl-right-footer"
ref="foot">
<div class="cl-right-footer-info hoc-title-container">
{{$t('explain.maskingProcess')}}
</div>
<div class="cl-right-footer-con">
<div class="cl-right-arrowCon"
ref="sampleContainer">
<div class="cl-right-footer-marquee"
v-if="curImageData.curSampleData.hoc_layers && curImageData.curSampleData.hoc_layers.length">
<div class="cl-right-footer-item"
v-for="(sample, index) in curImageData.curSampleData.hoc_layers"
:key="index">
<div class="cl-right-footer-image"
:style="{width: (widthBase * 2) + 'px'}"
@click="jumpImage(index)">
<div class="image-container"
:class="[curImageData.imageIndex===index ? 'itemActive':'']">
<img :src="formateUrl(sample.outcome)"
alt="">
</div>
<div class="cl-right-footer-title">
{{curImageData.curSampleData.label}}
{{$t('symbols.leftbracket')}}
{{sample.confidence.toFixed(3)}}
{{$t('symbols.rightbracket')}}
</div>
</div>
<div class="cl-right-footer-arrow"
:style="{width: widthBase + 'px'}"
v-if="index!==curImageData.curSampleData.hoc_layers.length-1">
<i class="el-icon-right"></i>
</div>
</div>
</div>
<!-- No data -->
<div class="image-noData"
v-else>
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<div v-if="initOver"
class="noData-text">{{$t("public.noData")}}</div>
<div v-else
class="noData-text">{{$t("public.dataLoading")}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import RequestService from '../../services/request-service';
import {basePath} from '@/services/fetcher';

export default {
data() {
return {
trainId: this.$route.query.id, // Train id
initOver: false, // Initialization completion flag
// Sorting mode
sortOption: [
{
label: 'confidence',
value: 'confidence',
},
],
// Pagination data
pageData: {
curPage: 1,
totalNum: 0,
pageSize: 24,
pageSizes: [24],
sortName: 'confidence',
sortType: 'descending',
},
// Prediction tag of the current image
labelLlist: [],
// All tags
emptyLabelSelect: {
id: -1,
label: this.$t('public.all'),
},
curFilterLabel: this.$t('public.all'), // Current filter label
resizeTimer: null, // Timer for changing the window size
widthBase: 150, // Foundation width
textHeight: 30, // Text height
partBase: 2, // Number of basic block
minConfidence: '', // Probability threshold
fullData: [], // Full data on current page
curSelectedDataIndex: 0, // Current selected data subscript
// Information about the selected data
curImageData: {
src: '',
labels: [],
selectedLabel: '',
oriData: {},
imageIndex: 0,
curSampleData: {},
},
dataWaitCount: 2, // Number of waiting times when no data is available
dataWaitTimer: null, // No data available timer
};
},
computed: {},
watch: {},
created() {
this.getHOCData();
},
mounted() {
if (!this.trainId) {
this.$message.error(this.$t('trainingDashboard.invalidId'));
document.title = `${this.$t(
'explain.conterfactualInterpretation',
)}-MindInsight`;
return;
}
document.title = `${this.$t(
'explain.conterfactualInterpretation',
)}-MindInsight`;

window.addEventListener('resize', this.resizeCallback, false);
this.$nextTick(() => {
this.resizeCallback();
});
},
destroyed() {
// Remove the listener of window size change
window.removeEventListener('resize', this.resizeCallback);
// Remove timer
if (this.dataWaitTimer) {
clearTimeout(this.dataWaitTimer);
this.dataWaitTimer = null;
}
},
methods: {
/**
* Obtains explanation job meta information
*/
getMetaData() {
const params = {
train_id: this.trainId,
};
RequestService.queryTrainInfo(params).then(
(res) => {
if (
!res ||
!res.data ||
!res.data.classes ||
!res.data.classes.length ||
!res.data.saliency
) {
return;
}
this.labelLlist = [this.emptyLabelSelect].concat(res.data.classes);
this.minConfidence = res.data.saliency.min_confidence;
},
);
},

/**
* Obtains HOC data
*/
getHOCData() {
const params = {
train_id: this.trainId,
limit: this.pageData.pageSize,
offset: this.pageData.curPage - 1,
sorted_name: this.pageData.sortName,
sorted_type: this.pageData.sortType,
};
if (this.curFilterLabel !== this.$t('public.all')) {
params.labels = [this.curFilterLabel];
}
RequestService.queryHOCData(params).then(
(res) => {
if (!res || !res.data) {
this.initOver = true;
return;
}
if (!res.data.count && !this.initOver && this.dataWaitCount) {
this.dataWaitCount--;
if (this.dataWaitTimer) {
clearTimeout(this.dataWaitTimer);
this.dataWaitTimer = null;
}
this.dataWaitTimer = setTimeout(() => {
this.getHOCData();
}, 1500);
} else {
if (!this.initOver) {
this.getMetaData();
}
this.initOver = true;
this.pageData.totalNum = res.data.count;
this.fullData = res.data.samples;
this.curSelectedDataIndex = 0;
this.formateCurrentHOCData();
}
},
() => {
this.pageData.totalNum = 0;
this.initOver = true;
this.resetIniitData();
},
);
},
/**
* Initializes and loads selected HOC data
*/
formateCurrentHOCData() {
const curData = this.fullData[this.curSelectedDataIndex];
if (!curData) {
this.resetIniitData();
return;
}
const labelOptions = [];
curData.inferences.forEach((inference, index) => {
const label = `${inference.label}${this.$t(
'symbols.leftbracket',
)}${inference.confidence.toFixed(3)}${this.$t('symbols.rightbracket')}`;
labelOptions.push({
label: label,
value: index,
});
});
this.curImageData.src = curData.image;
this.curImageData.labels = labelOptions;
this.curImageData.oriData = curData;
if (labelOptions.length) {
this.curImageData.selectedLabel = labelOptions[0].value;
this.curImageData.curSampleData = this.curImageData.oriData.inferences[
this.curImageData.selectedLabel
];
} else {
this.curImageData.selectedLabel = '';
this.curImageData.curSampleData = {};
}
if (
this.curImageData.curSampleData.hoc_layers &&
this.curImageData.curSampleData.hoc_layers.length
) {
this.curImageData.imageIndex =
this.curImageData.curSampleData.hoc_layers.length - 1;
} else {
this.curImageData.imageIndex = 0;
}
},

/**
* Page number change
* @param {Number} page Page number after change
*/
pageChange(page) {
this.pageData.curPage = page;
this.getHOCData();
},

/**
* Selected data change
* @param {Number} index Selected data subscript
*/
dataSelect(index) {
this.curSelectedDataIndex = index;
this.formateCurrentHOCData();
},

/**
* The images label is changed
* @param {Number} value Selected label subscript
*/
selectLableChange(value) {
this.curImageData.selectedLabel = value;
this.curImageData.curSampleData = this.curImageData.oriData.inferences[
value
];
if (
this.curImageData.curSampleData.hoc_layers &&
this.curImageData.curSampleData.hoc_layers.length
) {
this.curImageData.imageIndex =
this.curImageData.curSampleData.hoc_layers.length - 1;
} else {
this.curImageData.imageIndex = 0;
}
},

/**
*Jump image
* @param {Number} index Selected image subscript
*/
jumpImage(index) {
this.curImageData.imageIndex = index;
},

/**
* Resize callback
*/
resizeCallback() {
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
this.resizeTimer = null;
}
this.resizeTimer = setTimeout(() => {
const sampleContainer = this.$refs.sampleContainer;
if (sampleContainer) {
this.widthBase =
(sampleContainer.clientHeight - this.textHeight) / this.partBase;
}
}, 300);
},
/**
* Concatenate a valid URL
* @param {String} url
* @return {String} Concatenated character string
*/
formateUrl(url) {
if (!url) {
return '';
}
const newURL = `${basePath}${url}&date=${new Date().getTime()}`;
return newURL.replace(/(?<!:)\/\//g, '/');
},
/**
* Filter label change
*/
filterLabelChange() {
this.pageData.curPage = 1;
this.getHOCData();
},
/**
* Reset initial data
*/
resetIniitData() {
this.curImageData = {
src: '',
labels: [],
selectedLabel: '',
oriData: {},
imageIndex: 0,
curSampleData: {},
};
},
},
};
</script>
<style>
.cl-hoc {
height: 100%;
background-color: #fff;
}
.cl-hoc .no-image-tip {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.cl-hoc .image-noData {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.cl-hoc .image-noData .noData-text {
margin-top: 33px;
font-size: 18px;
}
.cl-hoc .hoc-title-container {
font-size: 16px;
font-weight: bold;
line-height: 21px;
}
.cl-hoc .cl-hoc-container {
height: 100%;
padding: 0 32px 14px 32px;
}
.cl-hoc .cl-hoc-container .cl-hoc-tip-container .el-icon-info {
color: #6c7280;
}
.cl-hoc .cl-hoc-title {
height: 56px;
font-size: 20px;
font-weight: bold;
line-height: 56px;
}
.cl-hoc .cl-hoc-title .tooltip-container .tip-text {
padding: 10px;
}
.cl-hoc .cl-hoc-title .tooltip-container .tip-text .tip-title {
font-size: 16px;
font-weight: bold;
}
.cl-hoc .cl-hoc-title .tooltip-container .tip-text .tip-part {
line-height: 20px;
word-break: normal;
}
.cl-hoc .cl-hoc-con {
height: calc(100% - 55px);
overflow-y: auto;
display: flex;
}
.cl-hoc .cl-hoc-con .cl-hoc-left {
width: 440px;
background-color: #edf0f5;
margin-right: 20px;
flex-shrink: 0;
padding: 20px 24px;
height: 100%;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-top-container .left-title {
margin-bottom: 20px;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-top-container .hoc-filter-container {
display: flex;
line-height: 32px;
margin-bottom: 12px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-left
.cl-left-top-container
.hoc-filter-container
.title-text {
width: 100px;
flex: 1;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-middle-container {
display: flex;
flex-direction: column;
height: calc(100% - 131px);
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-thumb {
flex: 1;
display: flex;
background-color: #fff;
flex-wrap: wrap;
overflow: hidden;
align-content: flex-start;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-thumb .cl-left-thumb-item {
width: calc(25% - 10px);
height: calc(16.6% - 10px);
margin: 5px;
overflow: hidden;
cursor: pointer;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-thumb .cl-left-thumb-item img {
width: 100%;
height: 100%;
object-fit: contain;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-thumb .active,
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-thumb .cl-left-thumb-item:hover {
border: solid 1px #00a5a7;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-page {
padding: 0 10px;
flex-shrink: 0;
background-color: #fff;
text-align: right;
}
.cl-hoc .cl-hoc-con .cl-hoc-left .cl-left-page .el-pagination {
border-top: solid 1px #ccc;
}
.cl-hoc .cl-hoc-con .cl-hoc-right {
overflow: hidden;
flex: 1;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-top {
height: 60%;
display: flex;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-top .left-item {
margin-right: 20px;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-top .cl-right-top-item {
width: calc(50% - 10px);
padding: 20px 24px;
height: 100%;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title {
padding-bottom: 12px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title
.ori-image-title {
margin-bottom: 6px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title
.ori-tag-select-container {
display: flex;
font-size: 14px;
line-height: 32px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title
.ori-tag-select-container
.ori-select-title {
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-right: 10px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title
.cl-right-title-silde {
display: flex;
margin-top: 12px;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.cl-right-title
.cl-right-title-silde
.cl-right-title-label {
font-size: 14px;
color: #333333;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.ori-image-container {
height: calc(100% - 100px);
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-top
.cl-right-top-item
.ori-image-container
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-con {
height: calc(100% - 63px);
position: relative;
overflow: hidden;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-con .img-container {
height: calc(100% - 28px);
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-con .img-container img {
width: 100%;
height: 100%;
object-fit: contain;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-con .image-title {
width: 100%;
margin-top: 12px;
text-align: center;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-footer {
height: calc(40% - 20px);
overflow: hidden;
margin-top: 20px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 20px 24px 24px 24px;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-footer .cl-right-footer-info {
height: 40px;
}
.cl-hoc .cl-hoc-con .cl-hoc-right .cl-right-footer .cl-right-footer-con {
height: calc(100% - 40px);
display: flex;
overflow: hidden;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-arrowCon {
height: 100%;
width: 100%;
overflow: hidden;
justify-content: center;
display: flex;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-marquee {
display: flex;
max-width: 100%;
height: 100%;
overflow-x: auto;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item {
width: auto;
height: 100%;
display: flex;
position: relative;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-image {
cursor: pointer;
float: left;
width: 220px;
height: 100%;
position: relative;
overflow: hidden;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-image
.image-container {
width: 100%;
height: calc(100% - 30px);
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-image
.image-container
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-title {
width: 220px;
height: 30px;
line-height: 30px;
text-align: center;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-arrow {
float: left;
width: 110px;
height: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.cl-right-footer-item
.cl-right-footer-arrow
i {
font-size: 40px;
color: #dcdfe6;
}
.cl-hoc
.cl-hoc-con
.cl-hoc-right
.cl-right-footer
.cl-right-footer-con
.itemActive {
border: 1px solid #00a5a7;
}
</style>

+ 495
- 280
mindinsight/ui/src/views/explain/saliency-map.vue
File diff suppressed because it is too large
View File


+ 43
- 13
mindinsight/ui/src/views/explain/summary-list.vue View File

@@ -68,8 +68,24 @@ limitations under the License.
<template slot-scope="scope">
<span class="menu-item operate-btn first-btn"
@contextmenu.prevent="rightClick(scope.row, $event, 0)"
@click.stop="goToSaliencyMap(scope.row)">
@click.stop="goToSaliencyMap(scope.row)"
v-if="scope.row.saliency_map">
{{$t('explain.title')}} </span>
<span class="menu-item operate-btn button-disable"
v-else
:title="$t('explain.disableSaliencyMapTip')">
{{$t('explain.title')}}
</span>
<span class="menu-item operate-btn"
@contextmenu.prevent="rightClick(scope.row, $event, 1)"
@click.stop="goToConterfactualinterpretation(scope.row)"
v-if="scope.row.hierarchical_occlusion">
{{$t('explain.conterfactualInterpretation')}} </span>
<span class="menu-item operate-btn button-disable"
v-else
:title="$t('explain.disableHOCTip')">
{{$t('explain.conterfactualInterpretation')}}
</span>
</template>
</el-table-column>
</el-table>
@@ -124,7 +140,7 @@ export default {
type: 0,
},
tableDom: null,
operateWidth: localStorage.getItem('milang') === 'en-us' ? 230 : 145,
operateWidth: this.$store.state.language === 'en-us' ? 430 : 290,
};
},
computed: {},
@@ -224,7 +240,7 @@ export default {
*/
goToSaliencyMap(row) {
this.contextMenu.show = false;
const trainId = encodeURIComponent(row.train_id);
const trainId = row.train_id;

this.$router.push({
path: '/explain/saliency-map',
@@ -232,6 +248,27 @@ export default {
});
},

/**
* go to Profiler
* @param {Object} row select row
*/
goToConterfactualinterpretation(row) {
this.contextMenu.show = false;
const profilerDir = row.profiler_dir;
const trainId = row.train_id;
const path = row.relative_path;
const router = '/explain/conterfactual-interpretation';

this.$router.push({
path: router,
query: {
dir: profilerDir,
id: trainId,
path: path,
},
});
},

rightClick(row, event, type) {
const maxWidth = 175;
this.contextMenu.data = row;
@@ -243,31 +280,24 @@ export default {
this.contextMenu.show = true;
},

doRightClick(key) {
doRightClick() {
const row = this.contextMenu.data;
if (!row) {
return;
}
this.contextMenu.show = false;
const trainId = row.train_id;
if (this.contextMenu.type) {
this.contextMenu.show = false;
const profilerDir = encodeURIComponent(row.profiler_dir);
const trainId = encodeURIComponent(row.train_id);
const path = encodeURIComponent(row.relative_path);
const router = '/explain/conterfactual-interpretation';

const routeUrl = this.$router.resolve({
path: router,
query: {
dir: profilerDir,
id: trainId,
path: path,
},
});
window.open(routeUrl.href, '_blank');
} else {
this.contextMenu.show = false;
const trainId = encodeURIComponent(row.train_id);

const routeUrl = this.$router.resolve({
path: '/explain/saliency-map',
query: {id: trainId},


Loading…
Cancel
Save