understanding_rl_vision/rl_clarity/svelte/interface.svelte (562 lines of code) (raw):
<script>
import Query from './query.svelte';
import TrajectoryDisplay from './trajectory_display.svelte';
import Navigator from './navigator.svelte';
import Screen from './screen.svelte';
import Graph from './graph.svelte';
import AttributionViewer from './attribution_viewer.svelte';
import Legend from './legend.svelte';
import FeatureViewer from './feature_viewer.svelte';
import { css_multiply } from './css_manipulate.js';
export let input_layer = "input";
export let layers = [];
export let features = [];
export let attributions = [];
export let attribution_policy = false;
export let attribution_single_channels = true;
export let attribution_totals = [];
export let colors = {
features: [],
actions: [],
graphs: {
v: "green",
action: "red",
action_group: "orange",
advantage: "blue"
},
trajectory: "blue"
};
export let action_combos = [];
export let action_groups = [];
export let trajectories = {
actions: [],
policy_logits: [],
values: [],
advantages: []
};
export let bookmarks = {
high: [],
low: []
};
export let vis_defaults = {
subdiv_mult: 0.5,
expand_mult: 4
};
export let subdirs = {
observations: "observations/",
trajectories: "trajectories/",
trajectories_scrub: "trajectories_scrub/",
features: "features/",
thumbnails: "thumbnails/",
attribution: "attribution/",
attribution_scrub: "attribution_scrub/",
features_grids: "features_grids/",
attribution_totals: "attribution_totals/"
};
export let formatting = {
video_height: "0px",
video_width: "0px",
video_speed: 25,
policy_display_height: "0px",
policy_display_width: "0px",
navigator_width: "0px",
scrubber_height: "0px",
scrubber_width: "0px",
scrubber_visible_duration: 128,
legend_item_height: "0px",
legend_item_width: "0px",
feature_viewer_height: "0px",
feature_viewer_width: "0px",
attribution_weight: 0.9
};
formatting.policy_display_width="40em";
formatting.navigator_width="24em";
export let init = {
layer: layers[Math.floor(layers.length / 2)],
attribution_kinds: [{type: "v", data: null}],
attribution_residual: false
/* attribution_options: [
* {direction: "non", channel: "all", show_trajectory: true},
* {direction: "pos", channel: "all", show_trajectory: true},
* {direction: "neg", channel: "all", show_trajectory: true}
* ] */
};
export let json_preloaded = {};
const action_htmls = action_combos.map(function(combo) {
let right = false;
let left = false;
let up = false;
let down = false;
let non_arrows = []
for (let button of combo) {
if (button.toUpperCase() === "RIGHT") {
right = true;
}
else if (button.toUpperCase() === "LEFT") {
left = true;
}
else if (button.toUpperCase() === "UP") {
up = true;
}
else if (button.toUpperCase() === "DOWN") {
down = true;
}
else {
non_arrows.push(button);
}
}
let arrows = [];
if (right && up) {
arrows.push("↗");
}
else if (left && up) {
arrows.push("↖");
}
else if (right && down) {
arrows.push("↘");
}
else if (left && down) {
arrows.push("↙");
}
else if (right) {
arrows.push("→");
}
else if (left) {
arrows.push("←");
}
else if (up) {
arrows.push("↑");
}
else if (down) {
arrows.push("↓");
}
if (arrows.length === 0 && non_arrows.length === 0) {
return "no-op";
}
else {
return arrows.concat(non_arrows).join("+");
}
});
const num_trajectories = Math.max.apply(
null, Object.values(trajectories).map(arr => arr.length));
let video_state = {
position: 0,
velocity_direction: 0
};
let selected_attribution_id = {
layer: init.layer,
trajectory: 0
};
const get_selected_attribution_item = function(selected_attribution_id, attribution_items) {
const default_item = {
layer: null,
trajectory: null,
};
for (let item of attribution_items) {
if (item.layer === selected_attribution_id.layer &&
item.trajectory === selected_attribution_id.trajectory) {
return item;
}
}
return default_item;
};
$: selected_attribution = get_selected_attribution_item(selected_attribution_id, attributions);
$: selected_attribution_totals = get_selected_attribution_item(selected_attribution_id, attribution_totals);
$: max_duration = (function() {
return Math.max.apply(null, Object.values(trajectories).map(
arr => arr[selected_attribution_id.trajectory].length));
})();
let attribution_residual = init.attribution_residual;
let attribution_kinds = [];
$: graphs = (function() {
let trajectory = selected_attribution.trajectory;
let graphs = [{
type: "advantage",
data: null,
title: "advantage",
series: trajectories.advantages[trajectory],
dones: trajectories.dones[trajectory],
}];
for (let attribution_kind of attribution_kinds) {
if (attribution_kind !== null) {
let graph = {
type: attribution_kind.type,
data: attribution_kind.data
};
let duplicate = false;
for (let existing_graph of graphs) {
if (graph.type === existing_graph.type && graph.data === existing_graph.data) {
duplicate = true;
}
}
if (!duplicate) {
if (graph.type === "v"){
graph.title = "value function";
graph.series = trajectories.values[trajectory];
}
else if (graph.type === "action") {
graph.title = action_htmls[graph.data] + " logit";
graph.series = trajectories.policy_logits[trajectory].map(
logits => logits[graph.data]);
}
else if (graph.type === "action_group") {
let actions = action_groups[graph.data];
graph.title = "[ " + actions.map(action => action_htmls[action]).join(" | ") + " ] logit sum";
graph.series = trajectories.policy_logits[trajectory].map(logits => logits.reduce(
(total, logit, action) => total + (actions.indexOf(action) === -1 ? 0 : logit), 0));
}
else {
graph = null;
}
if (graph !== null) {
graph.dones = trajectories.dones[trajectory];
graphs.push(graph);
}
}
}
}
return graphs;
})();
let attribution_single_channel = null;
$: {
if (selected_attribution.layer === input_layer) {
attribution_single_channel = null;
}
}
let selected_feature_id = null;
$: selected_feature = (function() {
const default_feature = {
layer: null,
number: null,
images: null,
overlay_grids: null,
metadata: null
};
if (selected_feature_id === null) {
return default_feature;
}
for (let feature of features) {
if (feature.layer === selected_feature_id.layer &&
feature.number === selected_feature_id.number) {
return feature;
}
}
return default_feature;
})();
let show_feature_viewer = false;
</script>
<style>
:global(.center-text) {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-flex-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
justify-content: center;
text-align: center;
}
:global(.grayscale) {
filter: gray;
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
:global(.opaque-hover:hover) {
filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'opaque\'><feComponentTransfer><feFuncA type=\'table\' tableValues=\'1 1\'/></feComponentTransfer></filter></svg>#opaque");
}
:global(.striped) {
background: repeating-linear-gradient(135deg, lightgray 0px, whitesmoke 10px, lightgray 20px);
}
:global(.underrule) {
border-bottom: 1px solid gray;
}
:global(.pixelated){
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
.flex {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.panel-slow {
-webkit-box-flex: 0;
-moz-box-flex: 0;
-webkit-flex-grow: 0;
-ms-flex: 0;
flex-grow: 0;
-webkit-flex-shrink: 0;
-moz-flex-shrink: 0;
-ms-flex: 0;
flex-shrink: 0;
z-index: 0;
padding: 0em 0.5em;
}
.panel-fast {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex: 1;
flex-grow: 1;
-webkit-flex-shrink: 1;
-moz-flex-shrink: 1;
-ms-flex: 1;
flex-shrink: 1;
z-index: 0;
padding: 0em 0.5em;
}
.panel-not-last {
border-right: 1px solid gray;
}
.trajectory-label {
display: inline-block;
white-space: nowrap;
width: 100%;
margin: 0.1em 0em;
border: 1px solid gray;
background-size: 100% 100%;
line-height: 2em;
font-weight: bold;
}
.layer-label {
white-space: nowrap;
padding: 0.5em;
border: 1px solid gray;
line-height: 3em;
}
.a {
text-decoration: underline;
color: #0000ee;
cursor: pointer;
}
.indicator{
margin: 0 auto;
border-width: 0px 6px;
border-color: #a3a3a3;
border-style: solid;
background: white;
}
</style>
<svelte:window on:click={() => show_feature_viewer = false}/>
<Query
bind:selected_attribution_id={selected_attribution_id}
bind:video_state={video_state}
max_duration={max_duration}
num_trajectories={num_trajectories}
/>
<div class="flex">
<div class="panel-slow panel-not-last">
<h3 class="underrule">Trajectories</h3>
<p>
{#each Array.from(Array(num_trajectories).keys()) as trajectory}
<label class="trajectory-label" style="background-image: url('{subdirs.trajectories_scrub + trajectory + '.png'}');">
<input type="radio" bind:group={selected_attribution_id.trajectory} value={trajectory}>
<span style="background-color: white;">{trajectory + 1}</span>
</label>
<br>
{/each}
</p>
<h3 class="underrule">Bookmarks</h3>
<p>Lowest advantage<br>episodes<br>(unexpected failures):</p>
<p>
{#each bookmarks.low as bookmark, bookmark_index}
<span class="a" on:click={() => {selected_attribution_id.trajectory = bookmark[0]; video_state.position = bookmark[1];}}>
trajectory {bookmark[0] + 1}, frame {bookmark[1] + 1}
</span>
<br>
{/each}
</p>
<p>Highest advantage<br>episodes<br>(unexpected successes):</p>
<p>
{#each bookmarks.high as bookmark, bookmark_index}
<span class="a" on:click={() => {selected_attribution_id.trajectory = bookmark[0]; video_state.position = bookmark[1];}}>
trajectory {bookmark[0] + 1}, frame {bookmark[1] + 1}
</span>
<br>
{/each}
</p>
</div>
<div class="panel-slow panel-not-last">
<h3 class="underrule">Layers</h3>
<p style="text-align: center;">
{#each layers as layer}
<label class="layer-label"><input type="radio" bind:group={selected_attribution_id.layer} value={layer}> {layer}</label><br>
{/each}
</p>
</div>
<div class="panel-fast panel-not-last">
<h3 class="underrule">Timeline</h3>
<TrajectoryDisplay
actions={trajectories.actions[selected_attribution.trajectory]}
rewards={trajectories.rewards[selected_attribution.trajectory]}
dones={trajectories.dones[selected_attribution.trajectory]}
policy_logits={trajectories.policy_logits[selected_attribution.trajectory]}
bind:state={video_state}
action_htmls={action_htmls}
action_colors={colors.actions}
bold_color={colors.trajectory}
policy_display_height={formatting.policy_display_height}
policy_display_width={formatting.policy_display_width}
/>
<Navigator
bind:state={video_state}
bind:speed={formatting.video_speed}
width={formatting.navigator_width}
max_duration={max_duration}
/>
<div style="width: {formatting.scrubber_width};
margin: 0 auto;
background: whitesmoke;
border: 1px solid gray;
border-radius: 0.5em;
box-shadow: inset 0 0 0.5em gray;">
<div style="height: {css_multiply(formatting.scrubber_height, 0.2)};
width: 2px;
margin: 0 auto;
border-width: 0px 6px;
border-color: black;
border-style: solid;
opacity: 0.4;"
></div>
<Screen
image_dir={subdirs.trajectories_scrub}
images={[selected_attribution.trajectory + ".png"]}
durations={[max_duration]}
bind:state={video_state}
height={formatting.scrubber_height}
width={formatting.scrubber_width}
visible_duration={formatting.scrubber_visible_duration}
/>
{#each graphs as graph}
<Graph
titles={[graph.title]}
series={[graph.series]}
dones={[graph.dones]}
colors={[colors.graphs[graph.type]]}
bind:state={video_state}
height={formatting.scrubber_height}
width={formatting.scrubber_width}
visible_duration={formatting.scrubber_visible_duration}
/>
{/each}
<div class="indicator" style="height: {css_multiply(formatting.scrubber_height, 0.05)}; width: 2px;"></div>
<div class="indicator" style="height: {css_multiply(formatting.scrubber_height, 0.05)}; width: 4px;"></div>
<div class="indicator" style="height: {css_multiply(formatting.scrubber_height, 0.05)}; width: 8px;"></div>
<div class="indicator" style="height: {css_multiply(formatting.scrubber_height, 0.05)}; width: 14px;"></div>
</div>
<div style="position: relative;
padding: 0.5em;
border: 1px solid black;
border-radius: 0.5em;">
<div style="position: absolute;
top: 0%;
left: 50%;
margin-top: -2px;
margin-left: -12px;
height: 4px;
width: 24px;
background-color: white;"
></div>
<h3 class="underrule">{#if selected_attribution.layer !== input_layer}Attribution{:else}Gradients{/if}</h3>
<AttributionViewer
layer={selected_attribution.layer}
trajectory={selected_attribution.trajectory}
subdirs={subdirs}
images={selected_attribution.images}
metadata={selected_attribution.metadata}
channel_totals_jsons={selected_attribution_totals.layer === null ? null : selected_attribution_totals.channels}
residual_totals_jsons={selected_attribution_totals.layer === null ? null : selected_attribution_totals.residuals}
totals_metadata={selected_attribution_totals.layer === null ? null : selected_attribution_totals.metadata}
json_preloaded={json_preloaded}
bind:state={video_state}
bind:attribution_kinds={attribution_kinds}
initial_attribution_kinds={init.attribution_kinds}
action_htmls={action_htmls}
action_groups={action_groups}
max_duration={max_duration}
video_height={formatting.video_height}
video_width={formatting.video_width}
attribution_weight={formatting.attribution_weight}
attribution_or_gradients={selected_attribution.layer !== input_layer ? "attribution" : "gradients"}
bind:attribution_residual={attribution_residual}
attribution_policy={attribution_policy}
attribution_single_channel={attribution_single_channel}
channel_colors={colors.features}
/>
</div>
</div>
<div class="panel-slow">
{#if selected_attribution.layer !== input_layer}
<h3 class="underrule">Attribution legend</h3>
<p>
Click to expand feature
{#if attribution_single_channels}
<br>Hover to isolate
{/if}
</p>
<Legend
image_dir={subdirs.thumbnails}
image={selected_attribution.layer.replace(/\//g, "").replace(/_/g, "") + ".png"}
colors={colors.features}
item_height={formatting.legend_item_height}
item_width={formatting.legend_item_width}
show_residual={attribution_residual}
bind:selected_channel={attribution_single_channel}
enable_hover={attribution_single_channels}
on:select={(event) => {selected_feature_id = { layer: selected_attribution.layer, number: event.detail }; show_feature_viewer = true;}}
/>
{:else}
<h3 class="underrule">Gradients legend</h3>
<p>Colors correspond<br>to input colors</p>
{/if}
<h3 class="underrule">Hotkeys</h3>
<p>
<button>←</button> go backwards<br>
<button>→</button> go forwards<br>
<button>space</button> toggle play/pause<br>
</p>
</div>
</div>
<div style="display: {show_feature_viewer ? 'block' : 'none'};
position: fixed;
overflow: auto;
z-index: 1;
top: 2%;
left: 50%;
max-height: 85%;
width: {formatting.feature_viewer_width};
margin-left: {css_multiply(formatting.feature_viewer_width, -0.5/0.92)};
padding: {css_multiply(formatting.feature_viewer_width, 0.04/0.92)};
z-index: 1;
background-color: white;
border: 1px solid black;
border-radius: 0.5em;"
on:click={(event) => event.stopPropagation()}
>
<FeatureViewer
layer={selected_feature.layer}
number={selected_feature.number}
image_dir={subdirs.features}
images={selected_feature.images}
overlay_image_dir={subdirs.observations}
overlay_image_grids_dir={subdirs.features_grids}
overlay_image_grids_jsons={selected_feature.overlay_grids}
metadata={selected_feature.metadata}
metadata_initial_values={vis_defaults}
json_preloaded={json_preloaded}
height={formatting.feature_viewer_height}
width={formatting.feature_viewer_width}
on:close={() => show_feature_viewer = false}
/>
</div>