understanding_rl_vision/rl_clarity/svelte/feature_viewer.svelte (239 lines of code) (raw):
<script>
import { json_load } from './json_load.js';
import { createEventDispatcher } from 'svelte';
export let layer = null;
export let number = null;
export let image_dir = "";
export let images = null;
export let overlay_image_dir = null;
export let overlay_image_fn = (overlay_image => overlay_image.toString() + ".png");
export let overlay_image_grids_dir = null;
export let overlay_image_grids_jsons = null;
export let metadata = null;
export let metadata_initial_values = {};
export let json_preloaded = {};
export let height;
export let width;
const dispatch = createEventDispatcher();
const compareFunction = function(a, b) {
let a_type = typeof(a);
let b_type = typeof(b);
if (a_type != b_type) {
return a_type.localeCompare(b_type);
}
if (typeof(a) === "number") {
return a - b;
}
return (a + "").localeCompare((b + "").toString());
};
$: metadata_configs = (function() {
let configs = [{
"key": "expand_mult",
"text": ["zoom in", "zoom out"],
"initial_value": 4,
"scrollable": true,
}, {
"key": "subdiv_mult",
"text": ["fewer patches", "more patches"],
"initial_value": 0.5,
"scrollable": false,
}];
for (let config_index = 0; config_index < configs.length; config_index++) {
let config = configs[config_index];
if (Object.prototype.hasOwnProperty.call(metadata_initial_values, config.key)) {
config.initial_value = metadata_initial_values[config.key];
}
if (metadata === null) {
config.values = []
}
else {
config.values = Array.from(new Set(metadata[config.key])).sort(compareFunction);
}
let current_value;
if (typeof(metadata_configs) !== "undefined") {
let current_index = parseInt(metadata_configs[config_index].current_index);
current_value = metadata_configs[config_index].values[current_index];
}
if (typeof(current_value) === "undefined") {
current_value = config.initial_value;
}
config.current_index = config.values.indexOf(current_value);
config.current_index = Math.max(0, config.current_index).toString();
}
return configs;
})();
$: selected_index = (function() {
if (images !== null && metadata !== null) {
for (let index = 0; index < images.length; index++) {
let found = true;
for (let config of metadata_configs) {
if (metadata[config.key][index] !== config.values[parseInt(config.current_index)]) {
found = false;
}
}
if (found) {
return index;
}
}
}
return null;
})();
const scroll = (function() {
const lockout = 100;
let prev_update_time = performance.now();
return function(event) {
let curr_time = performance.now();
if (curr_time - prev_update_time > lockout) {
for (let config of metadata_configs) {
if (config.scrollable) {
let old_value = parseInt(config.current_index);
let new_value = old_value;
new_value = old_value + Math.sign(event.deltaY);
new_value = Math.max(0, Math.min(config.values.length - 1, new_value));
new_value = new_value.toString();
if (new_value !== old_value) {
config.current_index = new_value;
metadata_configs = (x => x)(metadata_configs);
prev_update_time = curr_time;
}
}
}
}
};
})();
let overlay_position = null;
$: overlay_image_grids = (function() {
if (overlay_image_grids_jsons !== null) {
let urls = overlay_image_grids_jsons.map(json => overlay_image_grids_dir + json);
json_load(urls, "features_grids", json_preloaded).then((grids) => {
overlay_image_grids = grids;
overlay_preload_secondary();
});
}
return null;
})();
$: overlay_image = (function() {
if (overlay_image_grids === null || selected_index === null || overlay_position === null) {
return null;
}
else {
let image_grid = overlay_image_grids[selected_index];
let y_index = Math.floor(overlay_position.y * image_grid.length);
y_index = Math.min(image_grid.length - 1, y_index);
let image_array = image_grid[y_index];
let x_index = Math.floor(overlay_position.x * image_array.length);
x_index = Math.min(image_array.length - 1, x_index);
return image_array[x_index];
}
})();
const overlay_preload = function() {
if (overlay_image_grids !== null && selected_index !== null) {
let image_grid = overlay_image_grids[selected_index];
for (let image_array of image_grid) {
for (let image of image_array) {
(new Image()).src = overlay_image_dir + overlay_image_fn(image);
}
}
}
};
const overlay_preload_secondary = function() {
if (overlay_position !== null) {
overlay_preload();
}
};
const overlay_update = function(event) {
if (event.buttons === undefined ? event.which === 1 : event.buttons === 1) {
if (overlay_position === null) {
overlay_preload();
}
overlay_position = {
"x": Math.max(0, Math.min(1, event.offsetX / event.target.scrollWidth)),
"y": Math.max(0, Math.min(1, event.offsetY / event.target.scrollHeight))
}
}
else {
overlay_position = null;
}
};
</script>
<style>
.image {
position: absolute;
background-size: 100% 100%;
image-rendering: pixelated;
height: 100%;
width: 100%;
}
.label {
white-space: nowrap;
}
td {
vertical-align: top;
}
</style>
<svelte:window on:mouseup={overlay_update}/>
<div
class="striped"
style="position: relative;
overflow: hidden;
height: {height};
width: {width};
border: 1px solid gray;"
on:wheel|preventDefault={scroll}
on:mouseover={overlay_update}
on:mouseout={overlay_update}
on:mousedown|preventDefault={overlay_update}
on:mousemove={overlay_update}
>
{#if images === null}
<div class="center-text" style="height: 100%; width: 100%;">Select a feature</div>
{:else}
{#each images as image, index}
<div
class="image opaque-hover"
style="background-image: url('{image_dir + image}');
visibility: {index === selected_index && overlay_image === null ? 'visible' : 'hidden'};
cursor: pointer;"
></div>
{/each}
<div
class="image"
style="background-image: {overlay_image === null ? 'none' : 'url(\'' + overlay_image_dir + overlay_image_fn(overlay_image) + '\')'};
visibility: {overlay_image === null ? 'hidden' : 'visible'};
cursor: pointer;"
></div>
{/if}
</div>
<h3 class="underrule">Feature visualization</h3>
<table style="width: 100%;">
{#each metadata_configs as config, config_index}
<tr>
{#if config_index === 0 && layer !== null && number !== null}
<td rowspan={metadata_configs.length + 1}>
Layer {layer}, feature {number + 1}<br>
Dataset examples by spatial position<br>
Click to view example, scroll to zoom<br>
</td>
{/if}
<td class="label" style="text-align: right;">{config.text[0]}</td>
<td>
<input
type="range"
min="0"
max={Math.max(0, config.values.length - 1)}
step="1"
value={config.current_index}
on:change={(event) => config.current_index = event.currentTarget.value}
on:input={(event) => config.current_index = event.currentTarget.value}
>
</td>
<td class="label">{config.text[1]}</td>
<!-- <td>{#if config.scrollable}<small>[scrollable]</small>{/if}</td> -->
</tr>
{/each}
<tr>
<td colspan="3" style="text-align: right;">
<button on:click={() => dispatch('close')}>close</button>
</td>
</tr>
</table>