/ concept-collection / benchcompress
Sign in
concept-collection / benchcompress
view dataset data
Jeremy Magland <jmagland@flatironinstitute.org> committed commit be099ef42127 parent a09013d Browse files
9 changed files+693−14
web-ui/src/components/dataset/TimeseriesView.tsxadded+273−0View file
@@ -0,0 +1,273 @@
1+import { useEffect, useState, useMemo, useRef, useReducer } from "react";
2+import { useTimeseriesData } from "../../hooks/useTimeseriesData";
3+import { Dataset } from "../../types";
4+import { Margins, Range, WorkerMessage } from "./WorkerTypes";
5+import { timeseriesViewReducer, initialState } from "./timeseriesViewReducer";
6+
7+interface TimeseriesViewProps {
8+ width: number;
9+ height: number;
10+ dataset: Dataset;
11+}
12+
13+const TimeseriesView: React.FC<TimeseriesViewProps> = ({
14+ width,
15+ height,
16+ dataset,
17+}) => {
18+ const { data, error } = useTimeseriesData(dataset);
19+
20+ const [canvasElement, setCanvasElement] = useState<HTMLCanvasElement | null>(
21+ null,
22+ );
23+ const [overlayCanvasElement, setOverlayCanvasElement] =
24+ useState<HTMLCanvasElement | null>(null);
25+ const [state, dispatch] = useReducer(timeseriesViewReducer, initialState);
26+ const { selectedIndex, isDragging, lastDragX, xRange } = state;
27+
28+ const containerRef = useRef<HTMLDivElement>(null);
29+ const [worker, setWorker] = useState<Worker | null>(null);
30+ const [margins] = useState<Margins>({
31+ left: 50,
32+ right: 20,
33+ top: 20,
34+ bottom: 50,
35+ });
36+
37+ // Update xRange when data changes
38+ useEffect(() => {
39+ if (data) {
40+ dispatch({
41+ type: "SET_X_RANGE",
42+ range: { min: 0, max: data.length - 1 },
43+ });
44+ }
45+ }, [data]);
46+
47+ // Set up wheel event listener
48+ useEffect(() => {
49+ const container = containerRef.current;
50+ if (!container) return;
51+
52+ const handleWheel = (e: WheelEvent) => {
53+ if (!data) return;
54+ e.preventDefault();
55+
56+ const rect = container.getBoundingClientRect();
57+ const x = e.clientX - rect.left;
58+ const xRatio =
59+ (x - margins.left) / (width - margins.left - margins.right);
60+
61+ // Calculate zoom center in data coordinates
62+ const zoomCenter = xRange.min + (xRange.max - xRange.min) * xRatio;
63+
64+ // Calculate new range
65+ const zoomFactor = e.deltaY > 0 ? 1.1 : 0.9;
66+
67+ // Ensure we don't zoom out beyond data bounds
68+ const newMin = Math.max(
69+ 0,
70+ zoomCenter - (zoomCenter - xRange.min) * zoomFactor,
71+ );
72+ const newMax = Math.min(
73+ data.length - 1,
74+ zoomCenter + (xRange.max - zoomCenter) * zoomFactor,
75+ );
76+
77+ dispatch({ type: "SET_X_RANGE", range: { min: newMin, max: newMax } });
78+ };
79+
80+ container.addEventListener("wheel", handleWheel, { passive: false });
81+ return () => {
82+ container.removeEventListener("wheel", handleWheel);
83+ };
84+ }, [data, width, margins, xRange]);
85+
86+ // Set up mouse event listeners for panning
87+ useEffect(() => {
88+ const container = containerRef.current;
89+ if (!container) return;
90+
91+ const handleMouseDown = (e: MouseEvent) => {
92+ dispatch({ type: "SET_IS_DRAGGING", isDragging: true });
93+ dispatch({ type: "SET_LAST_DRAG_X", x: e.clientX });
94+ };
95+
96+ const handleMouseMove = (e: MouseEvent) => {
97+ if (!isDragging || lastDragX === 0 || !data) return;
98+
99+ const deltaX = e.clientX - lastDragX;
100+ const xRatio = deltaX / (width - margins.left - margins.right);
101+ const dataDelta = (xRange.max - xRange.min) * xRatio;
102+
103+ if (xRange.min - dataDelta < 0) return;
104+ if (xRange.max - dataDelta > data.length - 1) return;
105+
106+ const newMin = xRange.min - dataDelta;
107+ const newMax = xRange.max - dataDelta;
108+
109+ // Only update if we're still within bounds
110+ if (newMin >= 0 && newMax <= data.length - 1) {
111+ dispatch({ type: "SET_X_RANGE", range: { min: newMin, max: newMax } });
112+ }
113+
114+ dispatch({ type: "SET_LAST_DRAG_X", x: e.clientX });
115+ };
116+
117+ const handleMouseUp = () => {
118+ dispatch({ type: "SET_IS_DRAGGING", isDragging: false });
119+ dispatch({ type: "SET_LAST_DRAG_X", x: 0 });
120+ };
121+
122+ container.addEventListener("mousedown", handleMouseDown);
123+ window.addEventListener("mousemove", handleMouseMove);
124+ window.addEventListener("mouseup", handleMouseUp);
125+
126+ return () => {
127+ container.removeEventListener("mousedown", handleMouseDown);
128+ window.removeEventListener("mousemove", handleMouseMove);
129+ window.removeEventListener("mouseup", handleMouseUp);
130+ };
131+ }, [data, width, margins, xRange, isDragging, lastDragX]);
132+
133+ // Set worker
134+ useEffect(() => {
135+ if (!canvasElement) return;
136+ const worker = new Worker(
137+ new URL("./TimeseriesViewWorker", import.meta.url),
138+ {
139+ type: "module",
140+ },
141+ );
142+ let offscreenCanvas: OffscreenCanvas;
143+ try {
144+ offscreenCanvas = canvasElement.transferControlToOffscreen();
145+ } catch (err) {
146+ console.warn(err);
147+ console.warn(
148+ "Unable to transfer control to offscreen canvas (expected during dev)",
149+ );
150+ return;
151+ }
152+ const msg: WorkerMessage = {
153+ type: "initialize",
154+ canvas: offscreenCanvas,
155+ };
156+ worker.postMessage(msg, [offscreenCanvas]);
157+
158+ setWorker(worker);
159+
160+ return () => {
161+ worker.terminate();
162+ };
163+ }, [canvasElement]);
164+
165+ // Calculate yRange from data
166+ const yRange = useMemo<Range>(() => {
167+ if (!data) return { min: 0, max: 1 };
168+ return {
169+ min: Math.min(...data),
170+ max: Math.max(...data),
171+ };
172+ }, [data]);
173+
174+ // Handle dimension changes
175+ useEffect(() => {
176+ if (!worker) return;
177+ if (!data) return;
178+
179+ const msg: WorkerMessage = {
180+ type: "render",
181+ timeseries: data,
182+ width,
183+ height,
184+ margins,
185+ xRange,
186+ yRange,
187+ };
188+ worker.postMessage(msg);
189+ }, [width, height, data, worker, margins, xRange, yRange]);
190+
191+ // Render cursor on overlay canvas
192+ useEffect(() => {
193+ if (!overlayCanvasElement || selectedIndex === null || !data) return;
194+ const ctx = overlayCanvasElement.getContext("2d");
195+ if (!ctx) return;
196+
197+ // Clear overlay canvas
198+ ctx.clearRect(0, 0, width, height);
199+
200+ // Draw cursor line
201+ const xRatio = (selectedIndex - xRange.min) / (xRange.max - xRange.min);
202+ const x = margins.left + xRatio * (width - margins.left - margins.right);
203+ ctx.beginPath();
204+ ctx.strokeStyle = "#ff0000";
205+ ctx.lineWidth = 1;
206+ ctx.setLineDash([4, 4]);
207+ ctx.moveTo(x, margins.top);
208+ ctx.lineTo(x, height - margins.bottom);
209+ ctx.stroke();
210+ }, [
211+ selectedIndex,
212+ overlayCanvasElement,
213+ width,
214+ height,
215+ margins,
216+ data,
217+ xRange,
218+ ]);
219+
220+ if (error) {
221+ return <div>Error loading data: {error}</div>;
222+ }
223+
224+ const handleCanvasClick = (e: React.MouseEvent<HTMLDivElement>) => {
225+ if (!overlayCanvasElement || !data || isDragging) return;
226+ const rect = overlayCanvasElement.getBoundingClientRect();
227+ const x = e.clientX - rect.left;
228+ const xRatio = (x - margins.left) / (width - margins.left - margins.right);
229+ const index = Math.round(xRange.min + xRatio * (xRange.max - xRange.min));
230+ if (index >= 0 && index < data.length) {
231+ dispatch({ type: "SET_SELECTED_INDEX", index });
232+ }
233+ };
234+
235+ return (
236+ <div style={{ position: "relative", width, height: height + 30 }}>
237+ <div
238+ ref={containerRef}
239+ style={{ position: "relative", width, height }}
240+ onClick={handleCanvasClick}
241+ >
242+ <canvas
243+ ref={setCanvasElement}
244+ width={width}
245+ height={height}
246+ style={{
247+ position: "absolute",
248+ width: "100%",
249+ height: "100%",
250+ }}
251+ />
252+ <canvas
253+ ref={setOverlayCanvasElement}
254+ width={width}
255+ height={height}
256+ style={{
257+ position: "absolute",
258+ width: "100%",
259+ height: "100%",
260+ pointerEvents: "none",
261+ }}
262+ />
263+ </div>
264+ {selectedIndex !== -1 && data && (
265+ <div style={{ height: 30, padding: "5px 0", color: "#666" }}>
266+ Index: {selectedIndex}, Value: {data[selectedIndex].toFixed(3)}
267+ </div>
268+ )}
269+ </div>
270+ );
271+};
272+
273+export default TimeseriesView;
web-ui/src/components/dataset/TimeseriesViewWorker.tsadded+185−0View file
@@ -0,0 +1,185 @@
1+// Web worker for rendering timeseries data to canvas
2+
3+import { Margins, Range, WorkerMessage } from "./WorkerTypes";
4+
5+// Helper function to find a nice integer tick interval
6+function getNiceTickInterval(range: number, maxTicks: number): number {
7+ const minInterval = Math.ceil(range / maxTicks);
8+ if (minInterval <= 1) return 1;
9+
10+ const magnitude = Math.pow(10, Math.floor(Math.log10(minInterval)));
11+ const niceIntervals = [1, 2, 5, 10];
12+
13+ for (const interval of niceIntervals) {
14+ const tickInterval = interval * magnitude;
15+ if (tickInterval >= minInterval) {
16+ return Math.ceil(tickInterval);
17+ }
18+ }
19+ return Math.ceil(niceIntervals[niceIntervals.length - 1] * magnitude * 10);
20+}
21+
22+// Helper function to get tick positions
23+function getTickPositions(
24+ range: Range,
25+ width: number,
26+): { value: number; x: number }[] {
27+ const pixelsPerTick = 20; // Minimum pixels between ticks
28+ const maxTicks = Math.floor(width / pixelsPerTick);
29+ const tickInterval = getNiceTickInterval(range.max - range.min, maxTicks);
30+
31+ const firstTick = Math.ceil(range.min);
32+ const lastTick = Math.floor(range.max);
33+
34+ const ticks: { value: number; x: number }[] = [];
35+ for (let value = firstTick; value <= lastTick; value += tickInterval) {
36+ const x = (value - range.min) / (range.max - range.min);
37+ if (Number.isInteger(value)) {
38+ ticks.push({ value, x });
39+ }
40+ }
41+
42+ return ticks;
43+}
44+
45+let canvas: OffscreenCanvas | null = null;
46+let ctx: OffscreenCanvasRenderingContext2D | null = null;
47+
48+function renderTimeseries(
49+ timeseries: number[],
50+ width: number,
51+ height: number,
52+ margins: Margins,
53+ xRange: Range,
54+ yRange: Range,
55+) {
56+ if (!ctx || !canvas) return;
57+
58+ const context = ctx; // Create a stable reference to satisfy TypeScript
59+
60+ // Clear canvas
61+ context.clearRect(0, 0, width, height);
62+
63+ // Draw axes
64+ context.strokeStyle = "#666666";
65+ context.lineWidth = 1;
66+ context.beginPath();
67+
68+ // Y axis
69+ context.moveTo(margins.left, margins.top);
70+ context.lineTo(margins.left, height - margins.bottom);
71+
72+ // X axis
73+ context.moveTo(margins.left, height - margins.bottom);
74+ context.lineTo(width - margins.right, height - margins.bottom);
75+
76+ context.stroke();
77+
78+ // Calculate the drawing area dimensions
79+ const drawingWidth = width - margins.left - margins.right;
80+ const drawingHeight = height - margins.top - margins.bottom;
81+
82+ // Set up clipping region for timeseries
83+ context.save();
84+ context.beginPath();
85+ context.rect(margins.left, margins.top, drawingWidth, drawingHeight);
86+ context.clip();
87+
88+ // Set up drawing style for timeseries
89+ context.strokeStyle = "#2196f3";
90+ context.lineWidth = 2;
91+ context.beginPath();
92+
93+ // Calculate scaling factors
94+ const xScale = drawingWidth / (xRange.max - xRange.min);
95+ const yScale = drawingHeight / (yRange.max - yRange.min);
96+
97+ // Draw the path
98+ let isFirst = true;
99+ for (let i = Math.floor(xRange.min); i <= Math.ceil(xRange.max); i++) {
100+ if (i < 0 || i >= timeseries.length) continue;
101+ const value = timeseries[i];
102+ const x = margins.left + (i - xRange.min) * xScale;
103+ const y = margins.top + drawingHeight - (value - yRange.min) * yScale;
104+ if (isFirst) {
105+ context.moveTo(x, y);
106+ isFirst = false;
107+ } else {
108+ context.lineTo(x, y);
109+ }
110+ }
111+
112+ context.stroke();
113+
114+ // Remove clipping before drawing ticks
115+ context.restore();
116+
117+ // Draw Y-axis ticks and labels
118+ const yTicks = getTickPositions(yRange, drawingHeight);
119+
120+ context.textAlign = "right";
121+ context.textBaseline = "middle";
122+ context.fillStyle = "#666666";
123+ context.font = "12px Arial";
124+
125+ yTicks.forEach((tick) => {
126+ const y = margins.top + drawingHeight - tick.x * drawingHeight;
127+
128+ // Draw tick mark
129+ context.beginPath();
130+ context.moveTo(margins.left - 6, y);
131+ context.lineTo(margins.left, y);
132+ context.stroke();
133+
134+ // Draw label
135+ context.fillText(tick.value.toString(), margins.left - 8, y);
136+ });
137+
138+ // Draw X-axis ticks and labels
139+ const ticks = getTickPositions(xRange, drawingWidth);
140+
141+ context.textAlign = "center";
142+ context.textBaseline = "top";
143+ context.fillStyle = "#666666";
144+ context.font = "12px Arial";
145+
146+ ticks.forEach((tick) => {
147+ const x = margins.left + tick.x * drawingWidth;
148+
149+ // Draw tick mark
150+ context.beginPath();
151+ context.moveTo(x, height - margins.bottom);
152+ context.lineTo(x, height - margins.bottom + 6);
153+ context.stroke();
154+
155+ // Draw label
156+ context.fillText(tick.value.toString(), x, height - margins.bottom + 8);
157+ });
158+}
159+
160+self.onmessage = (evt: MessageEvent) => {
161+ const message = evt.data as WorkerMessage;
162+
163+ if (message.type === "initialize") {
164+ canvas = message.canvas;
165+ ctx = canvas.getContext("2d");
166+ if (!ctx) {
167+ self.postMessage({
168+ type: "error",
169+ error: "Failed to get canvas context",
170+ });
171+ return;
172+ }
173+ self.postMessage({ type: "initialized" });
174+ return;
175+ }
176+
177+ if (message.type === "render") {
178+ const { timeseries, width, height, margins, xRange, yRange } = message;
179+ renderTimeseries(timeseries, width, height, margins, xRange, yRange);
180+ self.postMessage({ type: "render_complete" });
181+ return;
182+ }
183+};
184+
185+export {}; // Needed for TypeScript modules
web-ui/src/components/dataset/WorkerTypes.tsadded+23−0View file
@@ -0,0 +1,23 @@
1+export interface Range {
2+ min: number;
3+ max: number;
4+}
5+
6+export interface Margins {
7+ left: number;
8+ right: number;
9+ top: number;
10+ bottom: number;
11+}
12+
13+export type WorkerMessage =
14+ | { type: "initialize"; canvas: OffscreenCanvas }
15+ | {
16+ type: "render";
17+ timeseries: number[];
18+ width: number;
19+ height: number;
20+ margins: Margins;
21+ xRange: Range;
22+ yRange: Range;
23+ };
web-ui/src/components/dataset/timeseriesViewReducer.tsadded+55−0View file
@@ -0,0 +1,55 @@
1+import { Range } from "./WorkerTypes";
2+
3+// State Type
4+export interface TimeseriesViewState {
5+ selectedIndex: number;
6+ isDragging: boolean;
7+ lastDragX: number;
8+ xRange: Range;
9+}
10+
11+// Initial State
12+export const initialState: TimeseriesViewState = {
13+ selectedIndex: -1,
14+ isDragging: false,
15+ lastDragX: 0,
16+ xRange: { min: 0, max: 1 },
17+};
18+
19+// Action Types Union
20+type TimeseriesViewAction =
21+ | { type: "SET_SELECTED_INDEX"; index: number }
22+ | { type: "SET_IS_DRAGGING"; isDragging: boolean }
23+ | { type: "SET_LAST_DRAG_X"; x: number }
24+ | { type: "SET_X_RANGE"; range: Range };
25+
26+// Reducer
27+export const timeseriesViewReducer = (
28+ state: TimeseriesViewState = initialState,
29+ action: TimeseriesViewAction,
30+): TimeseriesViewState => {
31+ switch (action.type) {
32+ case "SET_SELECTED_INDEX":
33+ return {
34+ ...state,
35+ selectedIndex: action.index,
36+ };
37+ case "SET_IS_DRAGGING":
38+ return {
39+ ...state,
40+ isDragging: action.isDragging,
41+ };
42+ case "SET_LAST_DRAG_X":
43+ return {
44+ ...state,
45+ lastDragX: action.x,
46+ };
47+ case "SET_X_RANGE":
48+ return {
49+ ...state,
50+ xRange: action.range,
51+ };
52+ default:
53+ return state;
54+ }
55+};
web-ui/src/hooks/useTimeseriesData.tsadded+89−0View file
@@ -0,0 +1,89 @@
1+import { useEffect, useState } from "react";
2+import { Dataset } from "../types";
3+
4+const getDtypeSize = (dtype: string): number => {
5+ switch (dtype) {
6+ case "uint8":
7+ return 1;
8+ case "uint16":
9+ return 2;
10+ case "uint32":
11+ return 4;
12+ case "int16":
13+ return 2;
14+ case "int32":
15+ return 4;
16+ default:
17+ throw new Error(`Unsupported dtype: ${dtype}`);
18+ }
19+};
20+
21+const createTypedArray = (buffer: ArrayBuffer, dtype: string): number[] => {
22+ switch (dtype) {
23+ case "uint8":
24+ return Array.from(new Uint8Array(buffer));
25+ case "uint16":
26+ return Array.from(new Uint16Array(buffer));
27+ case "uint32":
28+ return Array.from(new Uint32Array(buffer));
29+ case "int16":
30+ return Array.from(new Int16Array(buffer));
31+ case "int32":
32+ return Array.from(new Int32Array(buffer));
33+ default:
34+ throw new Error(`Unsupported dtype: ${dtype}`);
35+ }
36+};
37+
38+export const useTimeseriesData = (dataset: Dataset) => {
39+ const [data, setData] = useState<number[] | null>(null);
40+ const [error, setError] = useState<string | null>(null);
41+
42+ useEffect(() => {
43+ const fetchData = async () => {
44+ if (!dataset.data_url_raw) {
45+ setError("No raw data URL available");
46+ return;
47+ }
48+
49+ const metaJsonUrl = dataset.data_url_json;
50+ if (!metaJsonUrl) {
51+ setError("No JSON metadata URL available");
52+ return;
53+ }
54+
55+ try {
56+ const metaResponse = await fetch(metaJsonUrl);
57+ if (!metaResponse.ok) {
58+ throw new Error(`HTTP error! status: ${metaResponse.status}`);
59+ }
60+
61+ const metaJson = await metaResponse.json();
62+
63+ const dtype = metaJson.dtype;
64+ const bytesPerElement = getDtypeSize(dtype);
65+
66+ const numBytes = bytesPerElement * 1000;
67+
68+ const response = await fetch(dataset.data_url_raw, {
69+ headers: {
70+ Range: `bytes=0-${numBytes - 1}`, // First 1000 elements
71+ },
72+ });
73+
74+ if (!response.ok) {
75+ throw new Error(`HTTP error! status: ${response.status}`);
76+ }
77+
78+ const buffer = await response.arrayBuffer();
79+ const data = createTypedArray(buffer, dtype);
80+ setData(data);
81+ } catch (err) {
82+ setError(err instanceof Error ? err.message : "Failed to fetch data");
83+ }
84+ };
85+ fetchData();
86+ }, [dataset]);
87+
88+ return { data, error };
89+};
web-ui/src/pages/Dataset.tsxmodified+65−9View file
@@ -1,5 +1,6 @@
1-import { useParams } from 'react-router-dom';
2-import { Dataset as DatasetType } from '../types';
1+import { useParams } from "react-router-dom";
2+import { Dataset as DatasetType } from "../types";
3+import TimeseriesView from "../components/dataset/TimeseriesView";
34
45 interface DatasetProps {
56 datasets: DatasetType[];
@@ -7,7 +8,7 @@ interface DatasetProps {
78
89 function Dataset({ datasets }: DatasetProps) {
910 const { datasetName } = useParams<{ datasetName: string }>();
10- const dataset = datasets.find(d => d.name === datasetName);
11+ const dataset = datasets.find((d) => d.name === datasetName);
1112
1213 if (!dataset) {
1314 return <div>Dataset not found</div>;
@@ -27,15 +28,54 @@ function Dataset({ datasets }: DatasetProps) {
2728 </h1>
2829 <div style={{ maxWidth: "800px", margin: "0 auto" }}>
2930 <div style={{ marginBottom: "1.5rem" }}>
30- <h2 style={{ fontSize: "1.2rem", fontWeight: "bold", marginBottom: "0.5rem" }}>Description</h2>
31- <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>{dataset.description}</p>
31+ <h2
32+ style={{
33+ fontSize: "1.2rem",
34+ fontWeight: "bold",
35+ marginBottom: "0.5rem",
36+ }}
37+ >
38+ Description
39+ </h2>
40+ <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>
41+ {dataset.description}
42+ </p>
3243 </div>
3344 <div style={{ marginBottom: "1.5rem" }}>
34- <h2 style={{ fontSize: "1.2rem", fontWeight: "bold", marginBottom: "0.5rem" }}>Version</h2>
45+ <div
46+ style={{
47+ width: "100%",
48+ height: "300px",
49+ backgroundColor: "#f5f5f5",
50+ borderRadius: "4px",
51+ padding: "1rem",
52+ }}
53+ >
54+ <TimeseriesView width={700} height={250} dataset={dataset} />
55+ </div>
56+ </div>
57+ <div style={{ marginBottom: "1.5rem" }}>
58+ <h2
59+ style={{
60+ fontSize: "1.2rem",
61+ fontWeight: "bold",
62+ marginBottom: "0.5rem",
63+ }}
64+ >
65+ Version
66+ </h2>
3567 <p style={{ fontSize: "0.9rem" }}>{dataset.version}</p>
3668 </div>
3769 <div style={{ marginBottom: "1.5rem" }}>
38- <h2 style={{ fontSize: "1.2rem", fontWeight: "bold", marginBottom: "0.5rem" }}>Tags</h2>
70+ <h2
71+ style={{
72+ fontSize: "1.2rem",
73+ fontWeight: "bold",
74+ marginBottom: "0.5rem",
75+ }}
76+ >
77+ Tags
78+ </h2>
3979 <div>
4080 {dataset.tags.map((tag) => (
4181 <span
@@ -55,7 +95,15 @@ function Dataset({ datasets }: DatasetProps) {
5595 </div>
5696 </div>
5797 <div style={{ marginBottom: "1.5rem" }}>
58- <h2 style={{ fontSize: "1.2rem", fontWeight: "bold", marginBottom: "0.5rem" }}>Downloads</h2>
98+ <h2
99+ style={{
100+ fontSize: "1.2rem",
101+ fontWeight: "bold",
102+ marginBottom: "0.5rem",
103+ }}
104+ >
105+ Downloads
106+ </h2>
59107 <div style={{ display: "flex", gap: "1rem" }}>
60108 {dataset.data_url_npy && (
61109 <a
@@ -93,7 +141,15 @@ function Dataset({ datasets }: DatasetProps) {
93141 </div>
94142 {dataset.source_file && (
95143 <div style={{ marginBottom: "1.5rem" }}>
96- <h2 style={{ fontSize: "1.2rem", fontWeight: "bold", marginBottom: "0.5rem" }}>Source</h2>
144+ <h2
145+ style={{
146+ fontSize: "1.2rem",
147+ fontWeight: "bold",
148+ marginBottom: "0.5rem",
149+ }}
150+ >
151+ Source
152+ </h2>
97153 <a
98154 href={dataset.source_file}
99155 target="_blank"
web-ui/src/types.tsmodified+1−0View file
@@ -32,6 +32,7 @@ export interface Dataset {
3232 source_file?: string;
3333 data_url_npy?: string; // URL to download the dataset as .npy
3434 data_url_raw?: string; // URL to download the raw dataset as .dat
35+ data_url_json?: string; // URL to download the dataset info as .json (dtype and shape)
3536 }
3637
3738 export interface BenchmarkData {
zia_benchmark/src/zia_benchmark/_memobin.pymodified+1−1View file
@@ -120,7 +120,7 @@ def upload_to_memobin(
120120 data_bytes = data
121121 size = len(data_bytes)
122122
123- upload_url = create_signed_upload_url(url, size, 'zia', memobin_api_key)
123+ upload_url = create_signed_upload_url(url, size, "zia", memobin_api_key)
124124
125125 response = requests.put(
126126 upload_url, data=data_bytes, headers={"Content-Type": content_type}
zia_benchmark/src/zia_benchmark/run_benchmarks.pymodified+1−4View file
@@ -168,10 +168,7 @@ def run_benchmarks(
168168 if not exists_in_memobin(dataset_url_json):
169169 if verbose:
170170 print(" Uploading dataset metadata to memobin...")
171- metadata = {
172- "dtype": str(data.dtype),
173- "shape": data.shape
174- }
171+ metadata = {"dtype": str(data.dtype), "shape": data.shape}
175172 upload_to_memobin(
176173 metadata,
177174 dataset_url_json,
moveopenescclose