| const DATASET = |
| "akankshanc/inception-v1-microscope-data"; |
|
|
| const LAYERS = [ |
| { name: "conv2d0", channels: 64 }, |
| { name: "conv2d1", channels: 64 }, |
| { name: "conv2d2", channels: 192 }, |
| { name: "mixed3a", channels: 256 }, |
| { name: "mixed3b", channels: 480 }, |
| { name: "mixed4a", channels: 508 }, |
| { name: "mixed4b", channels: 512 }, |
| { name: "mixed4c", channels: 512 }, |
| { name: "mixed4d", channels: 528 }, |
| { name: "mixed4e", channels: 832 }, |
| { name: "mixed5a", channels: 832 }, |
| { name: "mixed5b", channels: 1024 }, |
| ]; |
|
|
| let previewLayer = null; |
| let exampleDisplayMode = "paired"; |
|
|
| const channelPanel = |
| document.querySelector(".channel-panel"); |
|
|
| const previousNeuronButton = |
| document.querySelector("#previous-neuron"); |
| const nextNeuronButton = |
| document.querySelector("#next-neuron"); |
| const copyNeuronLinkButton = |
| document.querySelector("#copy-neuron-link"); |
|
|
| const randomNeuronButton = |
| document.querySelector("#random-neuron"); |
|
|
| const layerList = document.querySelector(".layer-list"); |
| const channelHeading = |
| document.querySelector(".channel-heading"); |
| const channelSearch = |
| document.querySelector(".channel-search"); |
| const channelGrid = |
| document.querySelector(".channel-grid"); |
| const neuronTitle = |
| document.querySelector(".neuron-header h2"); |
| const neuronId = |
| document.querySelector(".neuron-header p"); |
| const imageFrame = |
| document.querySelector(".image-frame"); |
| const examplesPanel = |
| document.querySelector(".examples-panel"); |
| const imageModal = |
| document.querySelector("#image-modal"); |
| const modalImage = |
| document.querySelector("#modal-image"); |
| const modalCaption = |
| document.querySelector("#modal-caption"); |
| const modalOriginal = |
| document.querySelector("#modal-original"); |
| const modalClose = |
| document.querySelector("#modal-close"); |
|
|
| const rowCache = new Map(); |
|
|
| const aboutButton = |
| document.querySelector("#about-button"); |
| const aboutDialog = |
| document.querySelector("#about-dialog"); |
| const aboutClose = |
| document.querySelector("#about-close"); |
|
|
| let previouslyFocusedElement = null; |
| let selectedNeuronLoadTimer = null; |
| let selectedLayer = LAYERS[0]; |
| let selectedChannel = 0; |
| let searchTerm = ""; |
| let requestNumber = 0; |
|
|
|
|
| function formatChannel(channel) { |
| return String(channel).padStart(4, "0"); |
| } |
|
|
| function buildDatasetUrl(config, split, where, length) { |
| const parameters = [ |
| ["dataset", DATASET], |
| ["config", config], |
| ["split", split], |
| ["where", where], |
| ["offset", "0"], |
| ["length", String(length)], |
| ]; |
|
|
| const query = parameters |
| .map(([key, value]) => { |
| return ( |
| `${encodeURIComponent(key)}=` + |
| `${encodeURIComponent(value)}` |
| ); |
| }) |
| .join("&"); |
|
|
| return ( |
| "https://datasets-server.huggingface.co/filter?" + |
| query |
| ); |
| } |
|
|
| async function fetchRows(config, split, where, length) { |
| const url = buildDatasetUrl( |
| config, |
| split, |
| where, |
| length |
| ); |
|
|
| const response = await fetch(url); |
|
|
| if (!response.ok) { |
| throw new Error( |
| `${config}/${split} returned ${response.status}` |
| ); |
| } |
|
|
| const data = await response.json(); |
|
|
| return (data.rows || []).map((item) => item.row); |
| } |
|
|
| const pendingRequests = new Map(); |
|
|
| function wait(milliseconds) { |
| return new Promise((resolve) => { |
| window.setTimeout(resolve, milliseconds); |
| }); |
| } |
|
|
| async function fetchRowsByOffset( |
| config, |
| split, |
| offset, |
| length |
| ) { |
| const cacheKey = |
| `${config}/${split}/${offset}/${length}`; |
|
|
| |
| if (rowCache.has(cacheKey)) { |
| return rowCache.get(cacheKey); |
| } |
|
|
| |
| if (pendingRequests.has(cacheKey)) { |
| return pendingRequests.get(cacheKey); |
| } |
|
|
| const parameters = [ |
| ["dataset", DATASET], |
| ["config", config], |
| ["split", split], |
| ["offset", String(offset)], |
| ["length", String(length)], |
| ]; |
|
|
| const query = parameters |
| .map(([key, value]) => { |
| return ( |
| `${encodeURIComponent(key)}=` + |
| `${encodeURIComponent(value)}` |
| ); |
| }) |
| .join("&"); |
|
|
| const url = |
| "https://datasets-server.huggingface.co/rows?" + |
| query; |
|
|
| const request = (async () => { |
| const maximumAttempts = 4; |
|
|
| for ( |
| let attempt = 1; |
| attempt <= maximumAttempts; |
| attempt += 1 |
| ) { |
| try { |
| const response = await fetch(url); |
|
|
| if (!response.ok) { |
| throw new Error( |
| `${config}/${split} returned ${response.status}` |
| ); |
| } |
|
|
| const data = await response.json(); |
|
|
| const rows = (data.rows || []).map( |
| (item) => item.row |
| ); |
|
|
| rowCache.set(cacheKey, rows); |
|
|
| return rows; |
| } catch (error) { |
| if (attempt === maximumAttempts) { |
| throw error; |
| } |
|
|
| |
| const delay = |
| 1500 * (2 ** (attempt - 1)) + |
| Math.random() * 500; |
|
|
| console.warn( |
| `Request failed; retrying in ${Math.round(delay)} ms`, |
| error |
| ); |
|
|
| await wait(delay); |
| } |
| } |
|
|
| return []; |
| })(); |
|
|
| pendingRequests.set(cacheKey, request); |
|
|
| try { |
| return await request; |
| } finally { |
| pendingRequests.delete(cacheKey); |
| } |
| } |
|
|
| function renderLayers() { |
| layerList.innerHTML = ""; |
|
|
| for (const layer of LAYERS) { |
| const button = document.createElement("button"); |
|
|
| button.type = "button"; |
| button.className = "layer-button"; |
|
|
| const isSelected = |
| layer.name === selectedLayer.name; |
| |
| button.classList.toggle( |
| "active", |
| isSelected |
| ); |
| |
| button.setAttribute( |
| "aria-pressed", |
| String(isSelected) |
| ); |
| |
| button.setAttribute( |
| "aria-label", |
| `${layer.name}, ${layer.channels} channels` |
| ); |
|
|
| const name = document.createElement("span"); |
| name.textContent = layer.name; |
|
|
| const count = document.createElement("span"); |
| count.className = "layer-count"; |
| count.textContent = layer.channels; |
|
|
| button.append(name, count); |
| |
| button.addEventListener("mouseenter", () => { |
| if (previewLayer?.name === layer.name) { |
| return; |
| } |
| |
| previewLayer = layer; |
| searchTerm = ""; |
| channelSearch.value = ""; |
| |
| renderChannels(); |
| }); |
| |
| button.addEventListener("click", () => { |
| selectLayer(layer); |
| }); |
|
|
| layerList.appendChild(button); |
| } |
| } |
|
|
| function renderChannels() { |
| const visibleLayer = |
| previewLayer || selectedLayer; |
|
|
| channelHeading.textContent = |
| `${visibleLayer.name} channels`; |
|
|
| channelGrid.innerHTML = ""; |
|
|
| let matches = 0; |
|
|
| for ( |
| let channel = 0; |
| channel < visibleLayer.channels; |
| channel += 1 |
| ) { |
| const paddedChannel = formatChannel(channel); |
|
|
| if ( |
| searchTerm && |
| !String(channel).includes(searchTerm) && |
| !paddedChannel.includes(searchTerm) |
| ) { |
| continue; |
| } |
|
|
| matches += 1; |
|
|
| const button = document.createElement("button"); |
|
|
| button.type = "button"; |
| button.className = "channel-button"; |
| button.textContent = paddedChannel; |
|
|
| const isSelected = |
| visibleLayer.name === selectedLayer.name && |
| channel === selectedChannel; |
| |
| button.classList.toggle( |
| "active", |
| isSelected |
| ); |
| |
| button.setAttribute( |
| "aria-pressed", |
| String(isSelected) |
| ); |
| |
| button.setAttribute( |
| "aria-label", |
| `${visibleLayer.name}, channel ${channel}` |
| ); |
|
|
| button.addEventListener("click", () => { |
| selectChannel(channel, visibleLayer); |
| }); |
|
|
| channelGrid.appendChild(button); |
| } |
|
|
| if (matches === 0) { |
| channelGrid.innerHTML = ` |
| <p class="placeholder"> |
| No matching channels. |
| </p> |
| `; |
| } |
| } |
|
|
| function updateNeuronHeading() { |
| neuronTitle.textContent = |
| `${selectedLayer.name} · Channel ${selectedChannel}`; |
|
|
| neuronId.textContent = |
| `Neuron ID: ${selectedLayer.name}_` + |
| formatChannel(selectedChannel); |
| |
| document.title = |
| `${selectedLayer.name} · Channel ${selectedChannel} ` + |
| "— Inception V1 Microscope"; |
| } |
|
|
| function showLoadingState() { |
| imageFrame.innerHTML = ` |
| <div |
| class="skeleton activation-skeleton" |
| aria-hidden="true" |
| ></div> |
| |
| <p class="sr-only" role="status"> |
| Loading activation image |
| </p> |
| `; |
|
|
| examplesPanel.innerHTML = ` |
| <h3 class="examples-heading"> |
| Top dataset examples |
| </h3> |
| |
| <div class="example-skeleton-list"> |
| ${createExampleSkeleton()} |
| ${createExampleSkeleton()} |
| ${createExampleSkeleton()} |
| </div> |
| |
| <p class="sr-only" role="status"> |
| Loading ranked dataset examples |
| </p> |
| `; |
| } |
|
|
| function createExampleSkeleton() { |
| return ` |
| <div class="example-card skeleton-card"> |
| <div class="skeleton skeleton-line"></div> |
| |
| <div class="example-images"> |
| <div class="skeleton skeleton-image"></div> |
| <div class="skeleton skeleton-image"></div> |
| </div> |
| </div> |
| `; |
| } |
|
|
| function openImageModal(src, caption) { |
| previouslyFocusedElement = |
| document.activeElement; |
|
|
| modalImage.src = src; |
| modalImage.alt = caption; |
| modalCaption.textContent = caption; |
| modalOriginal.href = src; |
|
|
| imageModal.hidden = false; |
| document.body.classList.add("modal-open"); |
| modalClose.focus(); |
| } |
|
|
| function closeImageModal() { |
| imageModal.hidden = true; |
| modalImage.src = ""; |
| document.body.classList.remove("modal-open"); |
|
|
| previouslyFocusedElement?.focus(); |
| } |
|
|
| function enableImageEnlargement(image, caption) { |
| image.classList.add("enlargeable-image"); |
| image.tabIndex = 0; |
| image.setAttribute("role", "button"); |
| image.setAttribute( |
| "aria-label", |
| `Enlarge ${caption}` |
| ); |
|
|
| image.addEventListener("click", () => { |
| openImageModal(image.src, caption); |
| }); |
|
|
| image.addEventListener("keydown", (event) => { |
| if ( |
| event.key === "Enter" || |
| event.key === " " |
| ) { |
| event.preventDefault(); |
| openImageModal(image.src, caption); |
| } |
| }); |
| } |
|
|
| function renderActivation(record) { |
| if (!record?.image?.src) { |
| imageFrame.innerHTML = ` |
| <p class="placeholder"> |
| Activation image is not available. |
| </p> |
| `; |
| return; |
| } |
|
|
| const image = document.createElement("img"); |
|
|
| image.src = record.image.src; |
| image.alt = |
| `${record.layer}, channel ${record.channel} ` + |
| "activation maximization"; |
| enableImageEnlargement( |
| image, |
| `${record.layer} · Channel ${record.channel} ` + |
| "activation maximization" |
| ); |
| imageFrame.innerHTML = ""; |
| imageFrame.appendChild(image); |
| } |
|
|
| function createExampleCard(example) { |
| const card = document.createElement("article"); |
| card.className = "example-card"; |
|
|
| const header = document.createElement("header"); |
| header.className = "example-header"; |
|
|
| const rank = document.createElement("strong"); |
| rank.textContent = |
| `Rank ${String(example.rank).padStart(2, "0")}`; |
|
|
| const score = document.createElement("span"); |
| score.textContent = |
| `Activation: ${Number( |
| example.activation_score |
| ).toFixed(3)}`; |
|
|
| header.append(rank, score); |
|
|
| const imagePair = document.createElement("div"); |
| imagePair.className = "example-images"; |
|
|
| const fullFigure = createExampleFigure( |
| "Full image", |
| example.full_image?.src, |
| `Rank ${example.rank} full image` |
| ); |
|
|
| fullFigure.classList.add("full-example"); |
|
|
| const cropFigure = createExampleFigure( |
| "Triggering crop", |
| example.crop_image?.src, |
| `Rank ${example.rank} triggering crop` |
| ); |
|
|
| cropFigure.classList.add("crop-example"); |
|
|
| imagePair.append(fullFigure, cropFigure); |
|
|
| const source = document.createElement("p"); |
| source.className = "example-source"; |
| source.textContent = |
| `Source: ${example.source_image_id}`; |
|
|
| card.append(header, imagePair, source); |
|
|
| return card; |
| } |
|
|
| function createExampleFigure(label, src, alt) { |
| const figure = document.createElement("figure"); |
|
|
| const caption = document.createElement("figcaption"); |
| caption.textContent = label; |
|
|
| figure.appendChild(caption); |
|
|
| if (src) { |
| const image = document.createElement("img"); |
|
|
| image.src = src; |
| image.alt = alt; |
| image.loading = "lazy"; |
| enableImageEnlargement(image, alt); |
| figure.appendChild(image); |
| } else { |
| const missing = document.createElement("p"); |
| missing.className = "missing-image"; |
| missing.textContent = "Image unavailable"; |
|
|
| figure.appendChild(missing); |
| } |
|
|
| return figure; |
| } |
|
|
| function configureDisplayControls() { |
| const buttons = examplesPanel.querySelectorAll( |
| "[data-display-mode]" |
| ); |
|
|
| for (const button of buttons) { |
| button.addEventListener("click", () => { |
| exampleDisplayMode = |
| button.dataset.displayMode; |
|
|
| applyExampleDisplayMode(); |
| }); |
| } |
| } |
|
|
| function applyExampleDisplayMode() { |
| examplesPanel.dataset.displayMode = |
| exampleDisplayMode; |
|
|
| const buttons = examplesPanel.querySelectorAll( |
| "[data-display-mode]" |
| ); |
|
|
| for (const button of buttons) { |
| const isActive = |
| button.dataset.displayMode === |
| exampleDisplayMode; |
|
|
| button.classList.toggle( |
| "active", |
| isActive |
| ); |
|
|
| button.setAttribute( |
| "aria-pressed", |
| String(isActive) |
| ); |
| } |
| } |
|
|
| function renderExamples(examples) { |
| examplesPanel.innerHTML = ` |
| <div class="examples-title-row"> |
| <h3 class="examples-heading"> |
| Top dataset examples |
| </h3> |
| |
| <div |
| class="display-controls" |
| role="group" |
| aria-label="Dataset example display mode" |
| > |
| <button |
| type="button" |
| data-display-mode="paired" |
| > |
| Paired |
| </button> |
| |
| <button |
| type="button" |
| data-display-mode="full" |
| > |
| Full |
| </button> |
| |
| <button |
| type="button" |
| data-display-mode="crop" |
| > |
| Crops |
| </button> |
| </div> |
| </div> |
| `; |
|
|
| if (examples.length === 0) { |
| examplesPanel.insertAdjacentHTML( |
| "beforeend", |
| ` |
| <div class="example-placeholder"> |
| Dataset examples are not currently available |
| for this neuron. |
| </div> |
| ` |
| ); |
| return; |
| } |
|
|
| examples.sort((a, b) => a.rank - b.rank); |
|
|
| for (const example of examples) { |
| examplesPanel.appendChild( |
| createExampleCard(example) |
| ); |
| } |
|
|
| configureDisplayControls(); |
| applyExampleDisplayMode(); |
| } |
|
|
| function showDataError(error) { |
| console.error(error); |
|
|
| imageFrame.innerHTML = ` |
| <p class="placeholder"> |
| Unable to load this activation image. |
| </p> |
| `; |
|
|
| examplesPanel.innerHTML = ` |
| <h3 class="examples-heading"> |
| Top dataset examples |
| </h3> |
| |
| <div class="example-placeholder"> |
| Unable to load this neuron’s dataset records. |
| Please try refreshing the page. |
| </div> |
| `; |
| } |
|
|
| function getActivationOffset(layer, channel) { |
| const layerIndex = LAYERS.findIndex( |
| (item) => item.name === layer.name |
| ); |
|
|
| const layerStartOffset = LAYERS |
| .slice(0, layerIndex) |
| .reduce( |
| (total, item) => total + item.channels, |
| 0 |
| ); |
|
|
| |
| |
| |
| |
| const channelOrder = Array.from( |
| { length: layer.channels }, |
| (_, channelNumber) => channelNumber |
| ).sort((first, second) => { |
| const firstText = String(first); |
| const secondText = String(second); |
|
|
| if (firstText < secondText) { |
| return -1; |
| } |
|
|
| if (firstText > secondText) { |
| return 1; |
| } |
|
|
| return 0; |
| }); |
|
|
| const channelPosition = |
| channelOrder.indexOf(channel); |
|
|
| return layerStartOffset + channelPosition; |
| } |
|
|
| function addRetryButton(container) { |
| const button = document.createElement("button"); |
|
|
| button.type = "button"; |
| button.className = "retry-button"; |
| button.textContent = "Try again"; |
|
|
| button.addEventListener("click", () => { |
| |
| rowCache.clear(); |
| loadSelectedNeuron(); |
| }); |
|
|
| container.appendChild(button); |
| } |
|
|
| function loadSelectedNeuron() { |
| window.clearTimeout(selectedNeuronLoadTimer); |
|
|
| selectedNeuronLoadTimer = window.setTimeout(() => { |
| selectedNeuronLoadTimer = null; |
| performSelectedNeuronLoad(); |
| }, 500); |
| } |
|
|
| async function performSelectedNeuronLoad() { |
| const currentRequest = ++requestNumber; |
|
|
| showLoadingState(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const activationOffset = |
| getActivationOffset( |
| selectedLayer, |
| selectedChannel |
| ); |
| |
| const activationRequest = fetchRowsByOffset( |
| "activation_maximization", |
| "train", |
| activationOffset, |
| 1 |
| ) |
| .then((rows) => { |
| if (currentRequest !== requestNumber) { |
| return; |
| } |
| |
| const record = rows[0]; |
| |
| if ( |
| !record || |
| record.layer !== selectedLayer.name || |
| record.channel !== selectedChannel |
| ) { |
| throw new Error( |
| "Activation offset returned the wrong neuron." |
| ); |
| } |
| |
| renderActivation(record); |
| }) |
| |
| .catch((error) => { |
| if (currentRequest !== requestNumber) { |
| return; |
| } |
|
|
| console.error( |
| "Activation request failed:", |
| error |
| ); |
|
|
| imageFrame.innerHTML = ` |
| <p class="placeholder"> |
| Unable to load the activation image. |
| </p> |
| `; |
| addRetryButton(imageFrame); |
| }); |
|
|
| const exampleOffset = selectedChannel * 10; |
|
|
| const examplesRequest = fetchRowsByOffset( |
| "dataset_examples", |
| selectedLayer.name, |
| exampleOffset, |
| 10 |
| ) |
| .then((rows) => { |
| if (currentRequest !== requestNumber) { |
| return; |
| } |
| |
| const wrongRecord = rows.some((record) => { |
| return ( |
| record.layer !== selectedLayer.name || |
| record.channel !== selectedChannel |
| ); |
| }); |
| |
| const returnedRanks = rows |
| .map((record) => record.rank) |
| .sort((a, b) => a - b); |
| |
| const expectedRanks = [ |
| 1, 2, 3, 4, 5, |
| 6, 7, 8, 9, 10, |
| ]; |
| |
| const ranksAreCorrect = |
| JSON.stringify(returnedRanks) === |
| JSON.stringify(expectedRanks); |
| |
| if ( |
| rows.length !== 10 || |
| wrongRecord || |
| !ranksAreCorrect |
| ) { |
| throw new Error( |
| "Example offset returned unexpected records." |
| ); |
| } |
| |
| renderExamples(rows); |
| }) |
| |
| .catch((error) => { |
| if (currentRequest !== requestNumber) { |
| return; |
| } |
|
|
| console.error( |
| "Dataset-examples request failed:", |
| error |
| ); |
|
|
| examplesPanel.innerHTML = ` |
| <h3 class="examples-heading"> |
| Top dataset examples |
| </h3> |
| |
| <div class="example-placeholder"> |
| Unable to load the dataset examples. |
| Please try again. |
| </div> |
| `; |
| const examplePlaceholder = |
| examplesPanel.querySelector( |
| ".example-placeholder" |
| ); |
|
|
| addRetryButton(examplePlaceholder); |
| }); |
|
|
| await Promise.allSettled([ |
| activationRequest, |
| examplesRequest, |
| ]); |
| } |
|
|
| function getAdjacentNeuron(direction) { |
| let layerIndex = LAYERS.findIndex( |
| (layer) => layer.name === selectedLayer.name |
| ); |
|
|
| let channel = selectedChannel + direction; |
|
|
| if (channel < 0) { |
| layerIndex -= 1; |
|
|
| if (layerIndex < 0) { |
| return null; |
| } |
|
|
| const layer = LAYERS[layerIndex]; |
|
|
| return { |
| layer, |
| channel: layer.channels - 1, |
| }; |
| } |
|
|
| if (channel >= selectedLayer.channels) { |
| layerIndex += 1; |
|
|
| if (layerIndex >= LAYERS.length) { |
| return null; |
| } |
|
|
| return { |
| layer: LAYERS[layerIndex], |
| channel: 0, |
| }; |
| } |
|
|
| return { |
| layer: selectedLayer, |
| channel, |
| }; |
| } |
|
|
| async function prefetchNeuronRecords( |
| layer, |
| channel |
| ) { |
| const activationOffset = |
| getActivationOffset(layer, channel); |
|
|
| const exampleOffset = channel * 10; |
|
|
| await Promise.allSettled([ |
| fetchRowsByOffset( |
| "activation_maximization", |
| "train", |
| activationOffset, |
| 1 |
| ), |
|
|
| fetchRowsByOffset( |
| "dataset_examples", |
| layer.name, |
| exampleOffset, |
| 10 |
| ), |
| ]); |
| } |
|
|
| function prefetchAdjacentNeurons() { |
| const previous = getAdjacentNeuron(-1); |
| const next = getAdjacentNeuron(1); |
|
|
| if (previous) { |
| prefetchNeuronRecords( |
| previous.layer, |
| previous.channel |
| ); |
| } |
|
|
| if (next) { |
| prefetchNeuronRecords( |
| next.layer, |
| next.channel |
| ); |
| } |
| } |
|
|
| function updateNavigationControls() { |
| const selectedLayerIndex = LAYERS.findIndex( |
| (layer) => layer.name === selectedLayer.name |
| ); |
|
|
| const isFirstNeuron = |
| selectedLayerIndex === 0 && |
| selectedChannel === 0; |
|
|
| const isLastNeuron = |
| selectedLayerIndex === LAYERS.length - 1 && |
| selectedChannel === selectedLayer.channels - 1; |
|
|
| previousNeuronButton.disabled = isFirstNeuron; |
| nextNeuronButton.disabled = isLastNeuron; |
| } |
|
|
| function navigateNeuron(direction) { |
| previewLayer = null; |
| let layerIndex = LAYERS.findIndex( |
| (layer) => layer.name === selectedLayer.name |
| ); |
|
|
| let newChannel = selectedChannel + direction; |
|
|
| if (newChannel < 0) { |
| layerIndex -= 1; |
|
|
| if (layerIndex < 0) { |
| return; |
| } |
|
|
| selectedLayer = LAYERS[layerIndex]; |
| newChannel = selectedLayer.channels - 1; |
| } else if ( |
| newChannel >= selectedLayer.channels |
| ) { |
| layerIndex += 1; |
|
|
| if (layerIndex >= LAYERS.length) { |
| return; |
| } |
|
|
| selectedLayer = LAYERS[layerIndex]; |
| newChannel = 0; |
| } |
|
|
| selectedChannel = newChannel; |
| searchTerm = ""; |
| channelSearch.value = ""; |
|
|
| renderLayers(); |
| renderChannels(); |
| updateNeuronHeading(); |
| updateNavigationControls(); |
| updateUrl(); |
| loadSelectedNeuron(); |
|
|
| const activeChannel = |
| channelGrid.querySelector( |
| ".channel-button.active" |
| ); |
|
|
| activeChannel?.scrollIntoView({ |
| block: "nearest", |
| }); |
| } |
|
|
| function updateUrl(historyMode = "push") { |
| const url = new URL(window.location.href); |
|
|
| url.searchParams.set( |
| "layer", |
| selectedLayer.name |
| ); |
|
|
| url.searchParams.set( |
| "channel", |
| String(selectedChannel) |
| ); |
|
|
| if (historyMode === "replace") { |
| window.history.replaceState({}, "", url); |
| } else { |
| window.history.pushState({}, "", url); |
| } |
| } |
|
|
| function selectLayer(layer) { |
| previewLayer = null; |
| selectedLayer = layer; |
| selectedChannel = 0; |
| searchTerm = ""; |
| channelSearch.value = ""; |
|
|
| renderLayers(); |
| renderChannels(); |
| updateNeuronHeading(); |
| updateNavigationControls(); |
| updateUrl(); |
| loadSelectedNeuron(); |
| } |
|
|
| function selectChannel( |
| channel, |
| layer = selectedLayer |
| ) { |
| selectedLayer = layer; |
| selectedChannel = channel; |
| previewLayer = null; |
|
|
| renderLayers(); |
| renderChannels(); |
| updateNeuronHeading(); |
| updateNavigationControls(); |
| updateUrl(); |
| loadSelectedNeuron(); |
| } |
|
|
| function restoreSelectionFromUrl() { |
| const parameters = |
| new URLSearchParams(window.location.search); |
|
|
| const requestedLayer = |
| parameters.get("layer"); |
|
|
| const requestedChannel = |
| Number(parameters.get("channel")); |
|
|
| const matchingLayer = LAYERS.find( |
| (layer) => layer.name === requestedLayer |
| ); |
|
|
| if (matchingLayer) { |
| selectedLayer = matchingLayer; |
| } |
|
|
| if ( |
| Number.isInteger(requestedChannel) && |
| requestedChannel >= 0 && |
| requestedChannel < selectedLayer.channels |
| ) { |
| selectedChannel = requestedChannel; |
| } |
| } |
|
|
| async function copyNeuronLink() { |
| const url = new URL(window.location.href); |
|
|
| url.searchParams.set( |
| "layer", |
| selectedLayer.name |
| ); |
|
|
| url.searchParams.set( |
| "channel", |
| String(selectedChannel) |
| ); |
|
|
| const link = url.toString(); |
|
|
| try { |
| await navigator.clipboard.writeText(link); |
| } catch (error) { |
| const temporaryInput = |
| document.createElement("textarea"); |
|
|
| temporaryInput.value = link; |
| temporaryInput.setAttribute("readonly", ""); |
| temporaryInput.style.position = "fixed"; |
| temporaryInput.style.opacity = "0"; |
|
|
| document.body.appendChild(temporaryInput); |
| temporaryInput.select(); |
| document.execCommand("copy"); |
| temporaryInput.remove(); |
| } |
|
|
| copyNeuronLinkButton.textContent = "Copied!"; |
|
|
| window.setTimeout(() => { |
| copyNeuronLinkButton.textContent = |
| "Copy link"; |
| }, 1600); |
| } |
|
|
| function selectRandomNeuron() { |
| const totalChannels = LAYERS.reduce( |
| (total, layer) => total + layer.channels, |
| 0 |
| ); |
|
|
| let randomIndex = Math.floor( |
| Math.random() * totalChannels |
| ); |
|
|
| for (const layer of LAYERS) { |
| if (randomIndex < layer.channels) { |
| selectChannel(randomIndex, layer); |
| return; |
| } |
|
|
| randomIndex -= layer.channels; |
| } |
| } |
|
|
|
|
| channelSearch.addEventListener("input", (event) => { |
| searchTerm = event.target.value.trim(); |
| renderChannels(); |
| }); |
| modalClose.addEventListener( |
| "click", |
| closeImageModal |
| ); |
|
|
| channelSearch.addEventListener( |
| "keydown", |
| (event) => { |
| const visibleLayer = |
| previewLayer || selectedLayer; |
|
|
| if (event.key === "Escape") { |
| searchTerm = ""; |
| channelSearch.value = ""; |
| renderChannels(); |
| return; |
| } |
|
|
| if (event.key !== "Enter") { |
| return; |
| } |
|
|
| const enteredValue = |
| channelSearch.value.trim(); |
|
|
| if (!/^\d+$/.test(enteredValue)) { |
| return; |
| } |
|
|
| const channel = Number(enteredValue); |
|
|
| if ( |
| channel < 0 || |
| channel >= visibleLayer.channels |
| ) { |
| return; |
| } |
|
|
| selectChannel(channel, visibleLayer); |
| } |
| ); |
|
|
| imageModal.addEventListener("click", (event) => { |
| if (event.target === imageModal) { |
| closeImageModal(); |
| } |
| }); |
|
|
| document.addEventListener("keydown", (event) => { |
| |
| |
| |
| if (!imageModal.hidden) { |
| if (event.key === "Escape") { |
| closeImageModal(); |
| } |
|
|
| return; |
| } |
|
|
| |
| |
| |
| |
| const activeElement = document.activeElement; |
|
|
| const isTyping = |
| activeElement?.tagName === "INPUT" || |
| activeElement?.tagName === "TEXTAREA" || |
| activeElement?.tagName === "SELECT" || |
| activeElement?.isContentEditable; |
|
|
| if (isTyping) { |
| return; |
| } |
|
|
| if (event.key === "ArrowLeft") { |
| event.preventDefault(); |
| navigateNeuron(-1); |
| } |
|
|
| if (event.key === "ArrowRight") { |
| event.preventDefault(); |
| navigateNeuron(1); |
| } |
| }); |
|
|
| channelPanel.addEventListener( |
| "mouseleave", |
| () => { |
| if (!previewLayer) { |
| return; |
| } |
|
|
| previewLayer = null; |
| searchTerm = ""; |
| channelSearch.value = ""; |
|
|
| renderChannels(); |
| } |
| ); |
|
|
| previousNeuronButton.addEventListener( |
| "click", |
| () => navigateNeuron(-1) |
| ); |
|
|
| nextNeuronButton.addEventListener( |
| "click", |
| () => navigateNeuron(1) |
| ); |
|
|
| copyNeuronLinkButton.addEventListener( |
| "click", |
| copyNeuronLink |
| ); |
|
|
| randomNeuronButton.addEventListener( |
| "click", |
| selectRandomNeuron |
| ); |
|
|
| aboutButton.addEventListener("click", () => { |
| aboutDialog.showModal(); |
| }); |
|
|
| aboutClose.addEventListener("click", () => { |
| aboutDialog.close(); |
| }); |
|
|
| aboutDialog.addEventListener("click", (event) => { |
| const bounds = |
| aboutDialog.getBoundingClientRect(); |
|
|
| const clickedOutside = |
| event.clientX < bounds.left || |
| event.clientX > bounds.right || |
| event.clientY < bounds.top || |
| event.clientY > bounds.bottom; |
|
|
| if (clickedOutside) { |
| aboutDialog.close(); |
| } |
| }); |
|
|
|
|
| window.addEventListener("popstate", () => { |
| previewLayer = null; |
| searchTerm = ""; |
| channelSearch.value = ""; |
|
|
| restoreSelectionFromUrl(); |
| renderLayers(); |
| renderChannels(); |
| updateNeuronHeading(); |
| updateNavigationControls(); |
| loadSelectedNeuron(); |
| }); |
|
|
| restoreSelectionFromUrl(); |
| renderLayers(); |
| renderChannels(); |
| updateNeuronHeading(); |
| updateNavigationControls(); |
| updateUrl("replace"); |
| loadSelectedNeuron(); |