Spaces:
Running
Running
<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/lodash@4.17.10/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> <!-- end unit --> | |
</div> <!-- end unit grid --> | |
</div> <!-- end container --> | |
</div> <!-- end app --> | |
<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) { | |
// Preprocess ranking records to sort them. | |
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> | |