diff options
Diffstat (limited to 'puzzle_visualizer.html')
| -rw-r--r-- | puzzle_visualizer.html | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/puzzle_visualizer.html b/puzzle_visualizer.html new file mode 100644 index 0000000..bcefdf1 --- /dev/null +++ b/puzzle_visualizer.html @@ -0,0 +1,426 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8" /> + <title>ARC‐Converted Dataset Visualizer (Upload Local Folder)</title> + <style> + body { + font-family: sans-serif; + margin: 16px; + } + .selector-area { + margin-bottom: 1rem; + } + .grid-canvas { + margin: 4px; + border: 1px solid #ccc; + } + .example-container { + display: inline-block; + margin: 0 16px 16px 0; + vertical-align: top; + } + .puzzle-display { + margin-top: 1rem; + } + .puzzle-id { + font-weight: bold; + margin-bottom: 0.5rem; + } + #groupList, #puzzleList { + margin: 1rem 0; + } + .group-item, .puzzle-item { + cursor: pointer; + margin: 4px 8px 4px 0; + padding: 2px 6px; + border: 1px solid #aaa; + display: inline-block; + } + .group-item:hover, .puzzle-item:hover { + background: #eef; + } + </style> +</head> +<body> +<h1>ARC‐Converted Dataset Visualizer (Local Directory)</h1> + +<div class="selector-area"> + <!-- 1) Directory input with webkitdirectory, mozdirectory --> + <label>Upload ARC Folder:</label> + <input type="file" id="folderInput" + webkitdirectory mozdirectory multiple + onchange="onFolderSelected(event)" /> + <br><br> + + <!-- 2) We'll enable set/subset selection after user chooses a folder and data is validated --> + <label>Set:</label> + <select id="setSelect" disabled> + <option value="train">train</option> + <option value="test">test</option> + </select> + + <label> Subset:</label> + <select id="subsetSelect" disabled> + <option value="all">all</option> + </select> + + <button id="loadBtn" disabled>Load</button> +</div> + +<div> + <div id="groupList"></div> + <div id="puzzleList"></div> + <div class="puzzle-display" id="puzzleView"></div> +</div> + +<!-- + 3) Use local 'assets/npyjs.js' from your project folder instead of a CDN. + Make sure 'assets/npyjs.js' is the unbundled or UMD version that doesn't + contain "import" statements. +--> +<script src="assets/npyjs.js"></script> + +<script> +/*************************************************************************** + * Global Maps & Variables + ***************************************************************************/ + +// Map from "train/all__inputs.npy" => File, etc. +let filesByPath = {}; + +// Once loaded, we store typed arrays for the chosen set/subset +let inputsArr, labelsArr; +let puzzleIndicesArr, groupIndicesArr, puzzleIdentifiersArr; +let identifiersJson; + +// The shape of inputs is [N_examples, seqLen], so we discover seqLen & gridSize +let seqLen = 0; +let gridSize = 0; + + +/*************************************************************************** + * 1) Handle folder selection: read all files, find identifiers.json, + * remove topmost folder from each file path, validate. + ***************************************************************************/ +function onFolderSelected(event) { + filesByPath = {}; + const fileList = event.target.files; + if (!fileList || fileList.length === 0) { + alert("No files selected!"); + return; + } + + // We'll gather all webkitRelativePaths + const paths = []; + for (let i = 0; i < fileList.length; i++) { + // Typically "arc-aug-10/train/all__inputs.npy", etc. + const file = fileList[i]; + const relPath = file.webkitRelativePath || file.mozRelativePath || file.name; + paths.push(relPath); + } + + // 1. Check if we have "identifiers.json" somewhere. + const idPath = paths.find(p => p.endsWith("identifiers.json")); + if (!idPath) { + alert("Error: No 'identifiers.json' found in the uploaded folder."); + return; + } + + // 2. Derive the top-level directory from that file's path + // e.g. if idPath = "arc-aug-10/identifiers.json", topDir = "arc-aug-10" + // If there's no slash, topDir = "" => do nothing + let topDir = ""; + const lastSlash = idPath.lastIndexOf("/"); + if (lastSlash >= 0) { + topDir = idPath.substring(0, lastSlash); + } + + // 3. Rebuild filesByPath with the top folder removed. + // For example, if topDir = "arc-aug-10", then "arc-aug-10/train/all__inputs.npy" + // becomes "train/all__inputs.npy" + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + let relPath = file.webkitRelativePath || file.mozRelativePath || file.name; + // If relPath starts with "arc-aug-10/", remove that prefix + if (topDir && relPath.startsWith(topDir + "/")) { + relPath = relPath.substring(topDir.length + 1); + } + filesByPath[relPath] = file; + } + + // Enable set/subset selection and "Load" + document.getElementById("setSelect").disabled = false; + document.getElementById("subsetSelect").disabled = false; + document.getElementById("loadBtn").disabled = false; +} + +// When user clicks "Load," parse the .npy for the chosen set/subset +document.getElementById("loadBtn").addEventListener("click", async () => { + document.getElementById("groupList").innerHTML = ""; + document.getElementById("puzzleList").innerHTML = ""; + document.getElementById("puzzleView").innerHTML = ""; + + const setName = document.getElementById("setSelect").value; // e.g. "train" + const subsetName = document.getElementById("subsetSelect").value; // e.g. "all" + + try { + await loadDataset(setName, subsetName); + buildGroupList(); // show groups + } catch (err) { + console.error(err); + alert("Error while loading dataset: " + err); + } +}); + + +/*************************************************************************** + * 2) Load .npy from local files using Npyjs + FileReader (ArrayBuffer) + ***************************************************************************/ +async function loadDataset(setName, subsetName) { + const prefix = `${setName}/${subsetName}__`; + // e.g. "train/all__inputs.npy" + const inputsPath = prefix + "inputs.npy"; + const labelsPath = prefix + "labels.npy"; + const pIdxPath = prefix + "puzzle_indices.npy"; + const gIdxPath = prefix + "group_indices.npy"; + const pIdsPath = prefix + "puzzle_identifiers.npy"; + const identifiersPath = "identifiers.json"; + + // Check existence + const needed = [inputsPath, labelsPath, pIdxPath, gIdxPath, pIdsPath, identifiersPath]; + for (const f of needed) { + if (!filesByPath[f]) { + throw new Error(`Missing file: ${f}`); + } + } + + // parseNpy => read from File -> ArrayBuffer -> Npyjs => typed array + const inputsNpy = await parseNpy(filesByPath[inputsPath]); + const labelsNpy = await parseNpy(filesByPath[labelsPath]); + const puzzleIndicesNpy= await parseNpy(filesByPath[pIdxPath]); + const groupIndicesNpy = await parseNpy(filesByPath[gIdxPath]); + const puzzleIdsNpy = await parseNpy(filesByPath[pIdsPath]); + + inputsArr = inputsNpy.data; + labelsArr = labelsNpy.data; + puzzleIndicesArr = puzzleIndicesNpy.data; + groupIndicesArr = groupIndicesNpy.data; + puzzleIdentifiersArr = puzzleIdsNpy.data; + + // shape e.g. [N_examples, seqLen] + seqLen = inputsNpy.shape[1]; + gridSize = Math.sqrt(seqLen); + + // read JSON + identifiersJson = await readJsonFile(filesByPath[identifiersPath]); +} + +/*************************************************************************** + * parseNpy => read a File as ArrayBuffer, parse with npyjs + ***************************************************************************/ +function parseNpy(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async () => { + try { + const arrayBuffer = reader.result; + const npy = new npyjs(); + resolve(await npy.parse(arrayBuffer)); + } catch (err) { + reject(err); + } + }; + reader.onerror = err => reject(err); + reader.readAsArrayBuffer(file); + }); +} + +/*************************************************************************** + * readJsonFile => read a local JSON file into object + ***************************************************************************/ +function readJsonFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + try { + const obj = JSON.parse(reader.result); + resolve(obj); + } catch (err) { + reject(err); + } + }; + reader.onerror = (err) => reject(err); + reader.readAsText(file); + }); +} + +/*************************************************************************** + * 3) Build group list in UI + ***************************************************************************/ +function buildGroupList() { + document.getElementById("groupList").innerHTML = "<h3>Groups</h3>"; + const groupListDiv = document.getElementById("groupList"); + + const nGroups = groupIndicesArr.length - 1; + for (let g = 0; g < nGroups; g++) { + const div = document.createElement("span"); + div.className = "group-item"; + div.textContent = `Group ${g}`; + div.onclick = () => onSelectGroup(g); + groupListDiv.appendChild(div); + } +} + +/*************************************************************************** + * onSelectGroup => show puzzles in that group + ***************************************************************************/ +function onSelectGroup(groupIndex) { + document.getElementById("puzzleList").innerHTML = ""; + document.getElementById("puzzleView").innerHTML = ""; + + const puzzleListDiv = document.getElementById("puzzleList"); + puzzleListDiv.innerHTML = `<h4>Puzzles in Group ${groupIndex}</h4>`; + + const firstPuzzle = groupIndicesArr[groupIndex]; + const lastPuzzle = groupIndicesArr[groupIndex + 1]; + + for (let p = firstPuzzle; p < lastPuzzle; p++) { + const puzzleIntId = puzzleIdentifiersArr[p]; + const puzzleStrId = (puzzleIntId < identifiersJson.length) + ? identifiersJson[puzzleIntId] + : "<unknown>"; + + const div = document.createElement("span"); + div.className = "puzzle-item"; + div.textContent = `Puzzle #${p} [ID=${puzzleIntId}: ${puzzleStrId}]`; + div.onclick = () => onSelectPuzzle(p); + puzzleListDiv.appendChild(div); + } +} + +/*************************************************************************** + * onSelectPuzzle => show each example + ***************************************************************************/ +function onSelectPuzzle(puzzleIndex) { + const puzzleView = document.getElementById("puzzleView"); + puzzleView.innerHTML = ""; + + // puzzle ID + const puzzleIntId = puzzleIdentifiersArr[puzzleIndex]; + const puzzleStrId = (puzzleIntId < identifiersJson.length) + ? identifiersJson[puzzleIntId] + : "<unknown>"; + + const titleDiv = document.createElement("div"); + titleDiv.className = "puzzle-id"; + titleDiv.textContent = `Puzzle #${puzzleIndex} — ID: ${puzzleStrId}`; + puzzleView.appendChild(titleDiv); + + // Examples are [puzzleIndicesArr[p], puzzleIndicesArr[p+1]) + const firstExample = puzzleIndicesArr[puzzleIndex]; + const lastExample = puzzleIndicesArr[puzzleIndex + 1]; + + for (let e = firstExample; e < lastExample; e++) { + const inputSeq = slice1D(inputsArr, e*seqLen, (e+1)*seqLen); + const outputSeq = slice1D(labelsArr, e*seqLen, (e+1)*seqLen); + + const inputGrid = decodeGrid(inputSeq); + const outputGrid = decodeGrid(outputSeq); + + const exDiv = document.createElement("div"); + exDiv.className = "example-container"; + exDiv.appendChild(document.createTextNode(`Example ${e}`)); + exDiv.appendChild(document.createElement("br")); + + exDiv.appendChild(renderGrid(inputGrid)); + exDiv.appendChild(renderGrid(outputGrid)); + + puzzleView.appendChild(exDiv); + } +} + +/*************************************************************************** + * slice1D => typed array slicing + ***************************************************************************/ +function slice1D(arr, start, end) { + const result = new Uint32Array(end - start); + for (let i = start; i < end; i++) { + result[i - start] = Number(arr[i]); + } + return result; +} + +/*************************************************************************** + * decodeGrid => turn the flattened seq of length=gridSize^2 into 2D + ***************************************************************************/ +function decodeGrid(seq) { + const grid = []; + let idx = 0; + for (let r = 0; r < gridSize; r++) { + const row = []; + for (let c = 0; c < gridSize; c++) { + row.push(seq[idx]); + idx++; + } + grid.push(row); + } + return grid; +} + +/*************************************************************************** + * renderGrid => draws a 2D grid to <canvas> + ***************************************************************************/ +function renderGrid(grid2d) { + const rows = grid2d.length; + const cols = grid2d[0].length; + const scale = 10; + + const canvas = document.createElement("canvas"); + canvas.width = cols * scale; + canvas.height = rows * scale; + canvas.className = "grid-canvas"; + const ctx = canvas.getContext("2d"); + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const val = grid2d[r][c]; + ctx.fillStyle = indexToColor(val); + ctx.fillRect(c * scale, r * scale, scale, scale); + } + } + return canvas; +} + +/*************************************************************************** + * indexToColor => color palette: + * 0 => pad => white + * 1 => eos => light gray + * 2..11 => original color(0..9) + ***************************************************************************/ +function indexToColor(value) { + if (value === 0) return "#FFFFFF"; // pad => white + if (value === 1) return "#DDDDDD"; // eos => light gray + + // shift by 2 => original color in [0..9] + const colorIdx = value - 2; + const palette = [ + "#000000", // color0 => black + "#FF0000", // color1 => red + "#00FF00", // color2 => green + "#0000FF", // color3 => blue + "#FFFF00", // color4 => yellow + "#FFA500", // color5 => orange + "#800080", // color6 => purple + "#00FFFF", // color7 => cyan + "#FFC0CB", // color8 => pink + "#808080" // color9 => gray + ]; + if (colorIdx >= 0 && colorIdx < palette.length) { + return palette[colorIdx]; + } + return "#FFFFFF"; // fallback +} +</script> +</body> +</html> |
