|
<!doctype html> |
|
<html> |
|
<head> |
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> |
|
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> |
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js" integrity="sha256-CMMTrj5gGwOAXBeFi7kNokqowkzbeL8ydAJy39ewjkQ=" crossorigin="anonymous"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.js" integrity="sha256-qwbDmNVLiCqkqRBpF46q5bjYH11j5cd+K+Y6D3/ja28=" crossorigin="anonymous"></script> |
|
<style> |
|
[v-cloak] { |
|
display: none; |
|
} |
|
.unitviz, .unitviz .modal-header, .unitviz .modal-body, .unitviz .modal-footer { |
|
font-family: Arial; |
|
font-size: 15px; |
|
} |
|
.unitgrid { |
|
text-align: center; |
|
border-spacing: 5px; |
|
border-collapse: separate; |
|
} |
|
.unitgrid .info { |
|
text-align: left; |
|
} |
|
.unitgrid .layername { |
|
display: none; |
|
} |
|
.unitlabel { |
|
font-weight: bold; |
|
font-size: 150%; |
|
text-align: center; |
|
line-height: 1; |
|
} |
|
.lowscore .unitlabel { |
|
color: silver; |
|
} |
|
.thumbcrop { |
|
overflow: hidden; |
|
width: 288px; |
|
height: 72px; |
|
} |
|
.thumbcrop img, .img-scroller img { |
|
image-rendering: pixelated; |
|
} |
|
.unit { |
|
display: inline-block; |
|
background: white; |
|
padding: 3px; |
|
margin: 2px; |
|
box-shadow: 0 5px 12px grey; |
|
} |
|
.iou { |
|
display: inline-block; |
|
float: right; |
|
margin-left: 5px; |
|
} |
|
.modal .big-modal { |
|
width:auto; |
|
max-width:90%; |
|
max-height:80%; |
|
} |
|
.modal-title { |
|
display: inline-block; |
|
} |
|
.footer-caption { |
|
float: left; |
|
width: 100%; |
|
} |
|
.histogram { |
|
text-align: center; |
|
margin-top: 3px; |
|
} |
|
.img-wrapper { |
|
text-align: center; |
|
position: relative; |
|
} |
|
.img-mask, .img-seg { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
z-index: 0; |
|
visibility: hidden; |
|
} |
|
input.hidden-toggle { |
|
display: none; |
|
} |
|
#show-seg:checked ~ .img-wrapper .img-seg, |
|
#show-mask:checked ~ .img-wrapper .img-mask { |
|
visibility: visible; |
|
} |
|
.img-controls { |
|
text-align: right; |
|
} |
|
.img-controls label { |
|
display: inline-block; |
|
background: silver; |
|
padding: 10px; |
|
margin-top: 0; |
|
-webkit-user-select: none; |
|
-moz-user-select: none; |
|
-ms-user-select: none; |
|
user-select: none; |
|
} |
|
.seginfo { |
|
display: inline-block; |
|
padding: 10px; |
|
float: left; |
|
} |
|
.img-mask { |
|
pointer-events: none; |
|
} |
|
.colorsample { |
|
display: inline-block; |
|
height: 42px; |
|
width: 42px; |
|
float: left; |
|
} |
|
#show-seg:checked ~ .img-controls .toggle-seg, |
|
#show-mask:checked ~ .img-controls .toggle-mask { |
|
background: navy; |
|
color: white; |
|
} |
|
.big-modal img { |
|
max-height: 60vh; |
|
} |
|
.img-scroller { |
|
overflow-x: scroll; |
|
} |
|
.img-scroller .img-fluid { |
|
max-width: initial; |
|
} |
|
.gridheader { |
|
font-size: 12px; |
|
margin-bottom: 10px; |
|
margin-left: 30px; |
|
margin-right: 30px; |
|
} |
|
.gridheader:after { |
|
content: ''; |
|
display: table; |
|
clear: both; |
|
} |
|
.sortheader { |
|
float: right; |
|
cursor: default; |
|
} |
|
.layerinfo { |
|
float: left; |
|
} |
|
.sortby { |
|
text-decoration: underline; |
|
cursor: pointer; |
|
} |
|
.sortby.currentsort { |
|
text-decoration: none; |
|
font-weight: bold; |
|
cursor: default; |
|
} |
|
.bg-inverse { |
|
background: #021B54; |
|
} |
|
.dropmenu { |
|
display: inline-block; |
|
vertical-align: top; |
|
position: relative; |
|
} |
|
.dropmenulist { |
|
pointer-events: auto; |
|
visibility: hidden; |
|
transition: visiblity 1s; |
|
position: absolute; |
|
z-index: 1; |
|
background: white; |
|
right: 0; |
|
text-align: right; |
|
white-space: nowrap; |
|
} |
|
.dropmenu:focus { |
|
pointer-events: none; |
|
} |
|
.dropmenu:focus .dropmenulist { |
|
visibility: visible; |
|
} |
|
</style> |
|
</head> |
|
<body class="unitviz"> |
|
<div id="app" v-if="dissect" v-cloak> |
|
|
|
<nav class="navbar navbar-expand navbar-dark bg-inverse"> |
|
<span class="navbar-brand">{{ dissect.netname || 'Dissection' }}</span> |
|
<ul class="navbar-nav mr-auto"> |
|
<li :class="{'nav-item': true, active: lindex == selected_layer}" |
|
v-for="(lrec, lindex) in dissect.layers"> |
|
<a class="nav-link" :href="'#' + lindex" |
|
>{{lrec.layer}}</a> |
|
</li> |
|
</ul> |
|
<ul class="navbar-nav ml-auto" v-if="dissect.meta"> |
|
<li class="navbar-text ml-2" v-for="(v, k) in dissect.meta"> |
|
{{k}}={{v | fixed(3, true)}} |
|
</li> |
|
</ul> |
|
</nav> |
|
|
|
<div v-for="lrec in [dissect.layers[selected_layer]]"> |
|
<div v-if="'bargraph' in lrec" class="histogram"> |
|
<a data-toggle="lightbox" :href="lrec.dirname + '/bargraph.svg?'+Math.random()" |
|
:data-title="'Summary of ' + (dissect.netname || 'labels') |
|
+ ' at ' + lrec.layer"> |
|
<img class="img-fluid" |
|
:src="lrec.dirname + '/' + lrec.bargraph + '?'+Math.random()"> |
|
</a> |
|
</div> |
|
|
|
<div class="gridheader"> |
|
<div class="layerinfo"> |
|
<span v-if="'interpretable' in lrec" |
|
>{{lrec.interpretable}}/</span |
|
>{{lrec.units.length}} units |
|
<span v-if="'labels' in lrec"> |
|
covering {{lrec.labels.length}} concepts |
|
with IoU ≥ {{dissect.iou_threshold}} |
|
</span> |
|
</div> |
|
|
|
<div class="sortheader"> |
|
sort by |
|
<span v-for="rank in lrec['rankings']" v-if="!rank.metric"> |
|
<span :class="{sortby: true, currentsort: sort_order == rank.name}" |
|
:data-ranking="rank.name" |
|
v-on:click="sort_order = $event.currentTarget.dataset.ranking" |
|
>{{rank.name}}</span> |
|
<span> </span> |
|
</span> |
|
<span v-for="metric in _.filter(_.uniq(lrec.rankings.map(x => x.metric)))"> |
|
<div class="dropmenu sortby" tabindex="0"> |
|
<div class="dropmenutop"> |
|
*-{{ metric }} |
|
</div> |
|
<div class="dropmenulist"> |
|
<div v-for="rank in lrec['rankings']" v-if="rank.metric == metric"> |
|
<span :class="{sortby: true, currentsort: sort_order == rank.name}" |
|
:data-ranking="rank.name" |
|
v-on:click="sort_order = $event.currentTarget.dataset.ranking" |
|
>{{rank.name}}</span> |
|
</div> |
|
</div> |
|
</div> |
|
<span> </span> |
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
<div class="unitgrid" |
|
v-for="lk in [_.find(lrec.rankings, x=>x.name == sort_order) |
|
.metric || 'iou']" |
|
><div :class="{unit: true, lowscore: lk == 'iou' && !urec.interp}" |
|
v-for="urec in _.find(lrec.rankings, x=>x.name == sort_order) |
|
.ranking.map(x=>lrec.units[x])"> |
|
<div v-if="lk+'_label' in urec" class="unitlabel">{{urec[lk+'_label']}}</div> |
|
<div class="info" |
|
><span class="layername">{{lrec.layer}}</span |
|
> <span class="unitnum">unit {{urec.unit}}</span |
|
> <span v-if="lk+'_cat' in urec" class="category">({{urec[lk+'_cat']}})</span |
|
> <span v-if="lk+'_iou' in urec" class="iou" |
|
>iou {{urec[lk + '_iou'] | fixed(2)}}</span |
|
> <span v-if="lk in urec" class="iou" |
|
>{{lk}} {{urec[lk] | fixed(2)}}</span></div> |
|
<div class="thumbcrop" v-for="imprefix in [lrec['image_prefix_' + lk] || '']" |
|
><a data-toggle="lightbox" |
|
:href="lrec.dirname + '/' + imprefix + 'image/' + urec.unit + '-top.jpg'" |
|
><img |
|
:src="lrec.dirname + '/' + imprefix + 'image/' + urec.unit + '-top.jpg'" |
|
height="72"></a></div> |
|
</div></div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="modal" id="lightbox"> |
|
<div class="modal-dialog big-modal" role="document"> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<h5 class="modal-title"></h5> |
|
<button type="button" class="close" |
|
data-dismiss="modal" aria-label="Close"> |
|
<span aria-hidden="true">×</span> |
|
</button> |
|
</div> |
|
<div class="modal-body"> |
|
<input id="show-seg" class="hidden-toggle" type="checkbox"> |
|
<input id="show-mask" class="hidden-toggle" type="checkbox" checked> |
|
<div class="img-wrapper img-scroller"> |
|
<img class="fullsize img-fluid img-orig"> |
|
<img class="fullsize img-fluid img-seg"> |
|
<img class="fullsize img-fluid img-mask"> |
|
</div> |
|
<div class="img-controls"> |
|
<canvas class="colorsample" height=1 width=1></canvas> |
|
<div class="seginfo"> |
|
</div> |
|
<label for="show-seg" class="toggle-seg">segmentation</label> |
|
<label for="show-mask" class="toggle-mask">mask</label> |
|
</div> |
|
</div> |
|
<div class="modal-footer"> |
|
<div class="footer-caption"> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<script> |
|
$(document).on('click', '[data-toggle=lightbox]', function(event) { |
|
if ($(this).attr('href').match(/-top/)) { |
|
$('#lightbox img.img-orig').attr('src', |
|
$(this).attr('href').replace(/-top.jpg/, '-orig.jpg')); |
|
$('#lightbox img.img-seg').attr('src', |
|
$(this).attr('href').replace(/-top.jpg/, '-seg.png')); |
|
$('#lightbox img.img-mask').attr('src', |
|
$(this).attr('href').replace(/-top.jpg/, '-mask.png')); |
|
$('#lightbox .img-seg, #lightbox .img-mask, .img-controls').show(); |
|
} else { |
|
$('#lightbox img.img-orig').attr('src', $(this).attr('href')); |
|
$('#lightbox .img-seg, #lightbox .img-mask, .img-controls').hide(); |
|
} |
|
$('#lightbox .modal-title').text($(this).data('title') || |
|
$(this).closest('.unit').find('.unitlabel').text()); |
|
$('#lightbox .footer-caption').text($(this).data('footer') || |
|
$(this).closest('.unit').find('.info').text()); |
|
$('#lightbox .segcolors').text(''); |
|
event.preventDefault(); |
|
$('#lightbox').modal(); |
|
$('#lightbox img').closest('div').scrollLeft(0); |
|
}); |
|
$(document).on('click', '#lightbox img.img-seg', function(event) { |
|
var elt_pos = $(this).offset(); |
|
var img_x = event.pageX - elt_pos.left; |
|
var img_y = event.pageY - elt_pos.top; |
|
var canvas = $('#lightbox .colorsample').get(0); |
|
canvas.getContext('2d').drawImage(this, img_x, img_y, 1, 1, 0, 0, 1, 1); |
|
var pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data; |
|
var colorkey = pixelData[0] + ',' + pixelData[1] + ',' + pixelData[2]; |
|
var meaning = theapp.dissect.segcolors[colorkey]; |
|
$('#lightbox .seginfo').text(meaning); |
|
}); |
|
|
|
var theapp = new Vue({ |
|
el: '#app', |
|
data: { |
|
sort_order: 'unit', |
|
sort_fields: { |
|
label: [[], []], |
|
score: [['iou'], ['desc']], |
|
unit: [['unit'], ['asc']], |
|
}, |
|
selected_layer: null, |
|
dissect: null |
|
}, |
|
created: function() { |
|
var self = this; |
|
$.getJSON('dissect.json?' + Math.random(), function(d) { |
|
self.dissect = d; |
|
for (var layer of d.layers) { |
|
|
|
for (var rank of layer.rankings) { |
|
if (!('ranking' in rank)) { |
|
rank.ranking = rank.score.map((score, index) => [score, index]) |
|
.sort(([score1], [score2]) => score1 - score2) |
|
.map(([, index]) => index); |
|
} |
|
} |
|
} |
|
self.sort_order = d.default_ranking; |
|
self.hashchange(); |
|
}); |
|
$(window).on('hashchange', function() { self.hashchange(); }); |
|
}, |
|
methods: { |
|
hashchange: function() { |
|
this.selected_layer = +window.location.hash.substr(1) || 0; |
|
}, |
|
}, |
|
filters: { |
|
fixed: function(value, digits, truncate) { |
|
if (typeof value != 'number') return value; |
|
var fixed = value.toFixed(digits); |
|
return truncate ? +fixed : fixed; |
|
} |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|