| """EEGDash Dataset Catalog — Hugging Face Space. |
| |
| Design system (kept in sync with ``style.css``): |
| |
| * Typography: Inter for UI (14px base, 600 for headings), JetBrains Mono for |
| code snippets. Hierarchy: hero title > section titles > labels > meta. |
| * Palette: Okabe-Ito (colorblind-safe). Brand is #0072B2 (EEG-blue). One warm |
| accent #E69F00 reserved for the ``on 🤗`` flag — never decorative. Neutral |
| ramp is slate (#f8fafc → #0f172a). |
| * Encoding: categorical modality gets one Okabe-Ito hue per value. Continuous |
| (dataset size) is never encoded by color. |
| * Annotation: the hero, modality strip and detail panel each carry one |
| sentence of prose so the page reads as an argument, not a data dump. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import ast |
| import html as _html |
| import json |
| import logging |
| import os |
| from functools import lru_cache |
| from pathlib import Path |
|
|
| import gradio as gr |
| import pandas as pd |
| from huggingface_hub import HfApi |
| from huggingface_hub.utils import HfHubHTTPError |
|
|
| HF_ORG = "EEGDash" |
| ROOT = Path(__file__).parent |
| CSV_PATH = ROOT / "dataset_summary.csv" |
| CSS_PATH = ROOT / "style.css" |
| ASSETS_DIR = ROOT / "assets" |
| EEGDASH_URL = "https://eegdash.org" |
| GITHUB_URL = "https://github.com/eegdash/EEGDash" |
| PYPI_URL = "https://pypi.org/project/eegdash/" |
| DISCORD_URL = "https://discord.gg/eegdash" |
|
|
|
|
| def _read_svg(name: str) -> str: |
| """Read an SVG asset and strip the XML prolog so it inlines cleanly. |
| |
| Inlining lets us color icons via ``currentColor`` and avoids the file |
| endpoint for tiny assets that would otherwise cost an extra round-trip. |
| """ |
| path = ASSETS_DIR / name |
| if not path.exists(): |
| return "" |
| raw = path.read_text(encoding="utf-8") |
| |
| for marker in ("?>", "-->"): |
| idx = raw.rfind(marker) |
| if idx != -1 and idx < 300: |
| raw = raw[idx + len(marker):].lstrip() |
| return raw |
|
|
|
|
| ICON_GITHUB = _read_svg("github.svg") |
| ICON_PYPI = _read_svg("pypi.svg") |
| ICON_DISCORD = _read_svg("discord.svg") |
| SVG_MARK = _read_svg("mark.svg") |
| SVG_BIDS = _read_svg("bids.svg") |
|
|
|
|
| def _plot_iframe(name: str, *, height: int, title: str) -> str: |
| """Embed a plotly plot in a sandboxed iframe. |
| |
| Gradio's ``gr.HTML`` strips ``<script>`` tags for XSS safety, which |
| would leave plotly fragments inert. An iframe is a clean boundary: |
| scripts inside the child document run normally and the host page stays |
| safe from them. |
| """ |
| path = ASSETS_DIR / "plots" / f"{name}.html" |
| if not path.exists(): |
| return "" |
| src = f"/gradio_api/file=assets/plots/{name}.html" |
| return ( |
| f'<iframe class="eeg-plot__iframe" src="{src}" ' |
| f'title="{title}" loading="lazy" ' |
| f'sandbox="allow-scripts allow-same-origin allow-popups" ' |
| f'style="width:100%;height:{height}px;border:0;display:block;"></iframe>' |
| ) |
|
|
| |
| |
| LOGO_URL = "/gradio_api/file=assets/logo.svg" |
| FAVICON_URL = "/gradio_api/file=assets/favicon.ico" |
| RECORDING_ICON = { |
| "eeg": "/gradio_api/file=assets/recording/eeg.png", |
| "ieeg": "/gradio_api/file=assets/recording/ieeg.png", |
| "meg": "/gradio_api/file=assets/recording/meg.png", |
| } |
|
|
| |
| |
| |
| MODALITY_HUES: dict[str, str] = { |
| "Visual": "#0072B2", |
| "Auditory": "#009E73", |
| "Motor": "#D55E00", |
| "Tactile": "#CC79A7", |
| "Multisensory": "#E69F00", |
| "Resting State": "#56B4E9", |
| "Sleep": "#F0E442", |
| "Anesthesia": "#999999", |
| "Other": "#555555", |
| "Unknown": "#cbd5e1", |
| } |
| DEFAULT_HUE = "#64748b" |
|
|
| TABLE_COLUMNS = [ |
| "dataset", |
| "author_year", |
| "source", |
| "record_modality", |
| "Type Subject", |
| "modality of exp", |
| "type of exp", |
| "n_subjects", |
| "n_records", |
| "n_tasks", |
| "nchans", |
| "sfreq", |
| "size", |
| "license", |
| "on_hf", |
| ] |
|
|
| DISPLAY_HEADERS = { |
| "dataset": "Dataset", |
| "author_year": "Author (year)", |
| "source": "Source", |
| "record_modality": "Recording", |
| "Type Subject": "Pathology", |
| "modality of exp": "Modality", |
| "type of exp": "Type", |
| "n_subjects": "Subjects", |
| "n_records": "Records", |
| "n_tasks": "Tasks", |
| "nchans": "Channels", |
| "sfreq": "Hz", |
| "size": "Size", |
| "license": "License", |
| "on_hf": "🤗", |
| } |
|
|
| log = logging.getLogger(__name__) |
|
|
|
|
| |
|
|
|
|
| def _parse_mode_from_json_col(cell: object) -> str: |
| if not isinstance(cell, str) or not cell.strip(): |
| return "" |
| try: |
| parsed = json.loads(cell) |
| except json.JSONDecodeError: |
| try: |
| parsed = ast.literal_eval(cell) |
| except (SyntaxError, ValueError): |
| return "" |
| if not parsed: |
| return "" |
| top = max(parsed, key=lambda d: d.get("count", 0)) |
| val = top.get("val", "") |
| if isinstance(val, float) and val.is_integer(): |
| val = int(val) |
| return str(val) |
|
|
|
|
| @lru_cache(maxsize=1) |
| def _hf_repos() -> set[str]: |
| try: |
| api = HfApi() |
| repos = api.list_datasets(author=HF_ORG, limit=2000) |
| return {r.id.split("/", 1)[-1] for r in repos} |
| except (HfHubHTTPError, Exception): |
| return set() |
|
|
|
|
| def _load_catalog() -> pd.DataFrame: |
| df = pd.read_csv(CSV_PATH) |
| df["nchans"] = df["nchans_set"].apply(_parse_mode_from_json_col) |
| df["sfreq"] = df["sampling_freqs"].apply(_parse_mode_from_json_col) |
| |
| |
| on_hub = {s.lower() for s in _hf_repos()} |
| df["on_hf"] = df["dataset"].apply( |
| lambda s: "✓" if str(s).lower() in on_hub else "" |
| ) |
| for col in ("n_subjects", "n_records", "n_tasks"): |
| df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0).astype(int) |
| extra = ["dataset_title", "doi", "duration_hours_total"] |
| for col in TABLE_COLUMNS + extra: |
| if col not in df.columns: |
| df[col] = "" |
| df = df[TABLE_COLUMNS + extra].fillna("") |
| return df |
|
|
|
|
| |
|
|
|
|
| def _unique_sorted(series: pd.Series) -> list[str]: |
| return sorted({str(v).strip() for v in series if str(v).strip()}) |
|
|
|
|
| def _filter( |
| df: pd.DataFrame, |
| query: str, |
| modalities: list[str], |
| subject_types: list[str], |
| sources: list[str], |
| licenses: list[str], |
| min_subjects: int, |
| only_on_hf: bool, |
| ) -> pd.DataFrame: |
| out = df |
| if query: |
| q = query.lower().strip() |
| hay = ( |
| out["dataset"].str.lower() |
| + " " |
| + out["author_year"].str.lower() |
| + " " |
| + out["dataset_title"].astype(str).str.lower() |
| ) |
| out = out[hay.str.contains(q, regex=False, na=False)] |
| if modalities: |
| out = out[out["modality of exp"].isin(modalities)] |
| if subject_types: |
| out = out[out["Type Subject"].isin(subject_types)] |
| if sources: |
| out = out[out["source"].isin(sources)] |
| if licenses: |
| out = out[out["license"].isin(licenses)] |
| if min_subjects > 0: |
| out = out[out["n_subjects"] >= min_subjects] |
| if only_on_hf: |
| out = out[out["on_hf"] == "✓"] |
| return out |
|
|
|
|
| def _render_table(df: pd.DataFrame) -> pd.DataFrame: |
| return df[TABLE_COLUMNS].rename(columns=DISPLAY_HEADERS) |
|
|
|
|
| |
|
|
|
|
| def _fmt_num(n: float) -> str: |
| if n >= 1_000_000: |
| return f"{n / 1_000_000:.1f}M" |
| if n >= 1_000: |
| return f"{n / 1_000:.1f}k" |
| return f"{int(n):,}" |
|
|
|
|
| def _hero_html(df: pd.DataFrame, total_all: int) -> str: |
| """Hero banner — the one thing a first-time visitor reads. |
| |
| Four stat cards answer: "how big is this catalog?" at a glance. The |
| count is filter-aware so the banner tracks what's currently visible. |
| """ |
| subjects = int(df["n_subjects"].sum()) |
| records = int(df["n_records"].sum()) |
| hours_series = pd.to_numeric(df["duration_hours_total"], errors="coerce").fillna(0) |
| hours = float(hours_series.sum()) |
| on_hf = int((df["on_hf"] == "✓").sum()) |
| viewing = len(df) |
|
|
| return f""" |
| <section class="eeg-hero"> |
| <div class="eeg-hero__left"> |
| <img class="eeg-hero__logo" src="{LOGO_URL}" alt="EEGDash" /> |
| <p class="eeg-hero__lede"> |
| Open catalog of {total_all} EEG / MEG datasets. Search, filter, and |
| load any of them with a single line of Python — streamed from NEMAR |
| or mirrored to <a href="https://huggingface.co/{HF_ORG}">🤗 {HF_ORG}</a>. |
| </p> |
| <div class="eeg-hero__cta"> |
| <a class="eeg-btn eeg-btn--primary" href="{EEGDASH_URL}" target="_blank" rel="noopener">eegdash.org <span aria-hidden="true">→</span></a> |
| <a class="eeg-btn eeg-btn--icon" href="{GITHUB_URL}" target="_blank" rel="noopener" aria-label="GitHub">{ICON_GITHUB}<span>GitHub</span></a> |
| <a class="eeg-btn eeg-btn--icon" href="{PYPI_URL}" target="_blank" rel="noopener" aria-label="PyPI">{ICON_PYPI}<span>PyPI</span></a> |
| </div> |
| </div> |
| <div class="eeg-hero__stats" role="group" aria-label="Catalog totals"> |
| <div class="eeg-stat"><div class="eeg-stat__n">{_fmt_num(viewing)}</div><div class="eeg-stat__l">datasets <span class="eeg-stat__meta">of {total_all}</span></div></div> |
| <div class="eeg-stat"><div class="eeg-stat__n">{_fmt_num(subjects)}</div><div class="eeg-stat__l">subjects</div></div> |
| <div class="eeg-stat"><div class="eeg-stat__n">{_fmt_num(records)}</div><div class="eeg-stat__l">recordings</div></div> |
| <div class="eeg-stat eeg-stat--accent"><div class="eeg-stat__n">{on_hf}</div><div class="eeg-stat__l">on <span aria-label="Hugging Face">🤗</span></div></div> |
| </div> |
| </section> |
| """ |
|
|
|
|
| def _modality_strip_html(df: pd.DataFrame) -> str: |
| """Horizontal bar of dataset counts by modality — quick shape check. |
| |
| Effectiveness via length (bars), expressiveness via categorical hue. |
| One stacked row is enough because we're answering a single question: |
| which experimental paradigms dominate the catalog? |
| """ |
| counts = ( |
| df["modality of exp"] |
| .replace("", "Unknown") |
| .value_counts() |
| .sort_values(ascending=False) |
| ) |
| if counts.empty: |
| return "" |
| total = int(counts.sum()) |
| segments = [] |
| legend = [] |
| for name, n in counts.items(): |
| hue = MODALITY_HUES.get(str(name), DEFAULT_HUE) |
| pct = (n / total) * 100 |
| |
| |
| segments.append( |
| f'<span class="eeg-bar__seg" style="width:{pct:.2f}%;background:{hue};min-width:2px" ' |
| f'title="{_html.escape(str(name))}: {n}"></span>' |
| ) |
| legend.append( |
| f'<span class="eeg-legend__item">' |
| f'<span class="eeg-legend__swatch" style="background:{hue}"></span>' |
| f"{_html.escape(str(name))} <span class='eeg-legend__n'>{n}</span>" |
| f"</span>" |
| ) |
| return f""" |
| <section class="eeg-modality" aria-label="Datasets by experimental modality"> |
| <div class="eeg-modality__head"> |
| <span class="eeg-modality__title">By modality</span> |
| <span class="eeg-modality__meta">{total} datasets · {len(counts)} modalities</span> |
| </div> |
| <div class="eeg-bar" role="img" aria-label="Stacked breakdown of datasets by modality">{''.join(segments)}</div> |
| <div class="eeg-legend">{''.join(legend)}</div> |
| </section> |
| """ |
|
|
|
|
| |
|
|
|
|
| def _e(v: object) -> str: |
| return _html.escape(str(v)) if v is not None else "" |
|
|
|
|
| def _snippet_block(label: str, code: str) -> str: |
| return ( |
| f'<div class="eeg-snippet"><div class="eeg-snippet__hd">{_e(label)}</div>' |
| f'<pre class="eeg-snippet__code">{_e(code)}</pre></div>' |
| ) |
|
|
|
|
| def _detail_html(df: pd.DataFrame, slug: str) -> str: |
| if not slug: |
| return _empty_detail() |
| match = df[df["dataset"] == slug] |
| if match.empty: |
| return _empty_detail() |
| row = match.iloc[0] |
| on_hf = row["on_hf"] == "✓" |
| title = str(row.get("dataset_title", "") or slug) |
| doi = str(row.get("doi", "") or "").strip() |
| author = str(row.get("author_year", "") or "").strip() |
| license_ = str(row.get("license", "") or "—").strip() or "—" |
| modality = str(row.get("modality of exp", "") or "").strip() or "—" |
| pathology = str(row.get("Type Subject", "") or "").strip() or "—" |
| modality_hue = MODALITY_HUES.get(modality, DEFAULT_HUE) |
|
|
| doi_link = ( |
| f'<a class="eeg-tag" href="https://doi.org/{_e(doi)}" target="_blank" rel="noopener">doi:{_e(doi)}</a>' |
| if doi |
| else "" |
| ) |
| hf_link = ( |
| f'<a class="eeg-tag eeg-tag--accent" href="https://huggingface.co/datasets/{HF_ORG}/{_e(slug)}" target="_blank" rel="noopener">🤗 on Hub</a>' |
| if on_hf |
| else '<span class="eeg-tag eeg-tag--muted">not mirrored yet</span>' |
| ) |
| rec_type = str(row.get("record_modality", "") or "").strip().lower() |
| rec_icon = RECORDING_ICON.get(rec_type) |
| rec_badge = ( |
| f'<img class="eeg-card__rec" src="{rec_icon}" alt="{_e(rec_type.upper())} recording" title="{_e(rec_type.upper())} recording"/>' |
| if rec_icon |
| else "" |
| ) |
|
|
| stats = [ |
| ("Subjects", _fmt_num(int(row.get("n_subjects", 0) or 0))), |
| ("Recordings", _fmt_num(int(row.get("n_records", 0) or 0))), |
| ("Tasks", _fmt_num(int(row.get("n_tasks", 0) or 0))), |
| ("Channels", str(row.get("nchans", "") or "—")), |
| ("Sampling", f"{row.get('sfreq', '') or '—'} Hz"), |
| ("Size", str(row.get("size", "") or "—")), |
| ] |
| stat_cards = "".join( |
| f'<div class="eeg-kv"><div class="eeg-kv__n">{_e(v)}</div><div class="eeg-kv__l">{_e(k)}</div></div>' |
| for k, v in stats |
| ) |
|
|
| native_snippet = ( |
| "from eegdash import EEGDashDataset\n\n" |
| f'ds = EEGDashDataset(dataset="{slug}", cache_dir="./cache")\n' |
| 'print(len(ds), "recordings")' |
| ) |
| if on_hf: |
| hub_snippet = ( |
| "from braindecode.datasets import BaseConcatDataset\n\n" |
| f'ds = BaseConcatDataset.pull_from_hub("{HF_ORG}/{slug}")' |
| ) |
| hub_block = _snippet_block("From 🤗 Hub (braindecode, Zarr)", hub_snippet) |
| else: |
| hub_snippet = ( |
| "from eegdash import EEGDashDataset\n\n" |
| f'ds = EEGDashDataset(dataset="{slug}", cache_dir="./cache")\n' |
| f'ds.push_to_hub("{HF_ORG}/{slug}")' |
| ) |
| hub_block = ( |
| '<div class="eeg-note">This dataset isn’t mirrored on 🤗 yet. ' |
| f'<a href="{GITHUB_URL}/issues">Open an issue</a> to request it ' |
| "or push it yourself:</div>" |
| + _snippet_block("Push to the Hub", hub_snippet) |
| ) |
|
|
| return f""" |
| <article class="eeg-card" aria-labelledby="eeg-card-title"> |
| <header class="eeg-card__hd"> |
| <div class="eeg-card__id"> |
| <span class="eeg-card__slug">{_e(slug)}</span> |
| <span class="eeg-card__modality" style="--hue:{modality_hue}">{_e(modality)}</span> |
| {rec_badge} |
| </div> |
| <h2 id="eeg-card-title" class="eeg-card__title">{_e(title)}</h2> |
| <div class="eeg-card__meta"> |
| {f'<span class="eeg-tag">{_e(author)}</span>' if author else ''} |
| <span class="eeg-tag">{_e(license_)}</span> |
| <span class="eeg-tag">{_e(pathology)}</span> |
| {doi_link} |
| {hf_link} |
| </div> |
| </header> |
| |
| <div class="eeg-card__kvs">{stat_cards}</div> |
| |
| <section class="eeg-card__body"> |
| <h3 class="eeg-card__h3">Load it</h3> |
| {_snippet_block("Native EEGDash (streams from S3 / NEMAR)", native_snippet)} |
| {hub_block} |
| </section> |
| </article> |
| """ |
|
|
|
|
| def _empty_detail() -> str: |
| return f""" |
| <article class="eeg-card eeg-card--empty"> |
| <div class="eeg-card__ghost"> |
| <div class="eeg-card__ghost-mark">{SVG_MARK}</div> |
| <div class="eeg-card__ghost-title">Pick a dataset</div> |
| <p>Click any row in the table to see its metadata, load snippet, and 🤗 mirror status.</p> |
| </div> |
| </article> |
| """ |
|
|
|
|
| |
|
|
|
|
| CATALOG = _load_catalog() |
| TOTAL_ALL = len(CATALOG) |
| MODALITY_CHOICES = _unique_sorted(CATALOG["modality of exp"]) |
| SUBJECT_CHOICES = _unique_sorted(CATALOG["Type Subject"]) |
| SOURCE_CHOICES = _unique_sorted(CATALOG["source"]) |
| LICENSE_CHOICES = _unique_sorted(CATALOG["license"]) |
|
|
|
|
| def _on_select(evt: gr.SelectData, df) -> str: |
| if evt is None or evt.index is None: |
| return _empty_detail() |
| row_idx = evt.index[0] if isinstance(evt.index, (list, tuple)) else evt.index |
| if df is None: |
| return _empty_detail() |
| if isinstance(df, pd.DataFrame): |
| if df.empty or row_idx >= len(df): |
| return _empty_detail() |
| slug = str(df.iloc[row_idx, 0]) |
| elif isinstance(df, dict) and "data" in df: |
| rows = df["data"] |
| if not rows or row_idx >= len(rows): |
| return _empty_detail() |
| slug = str(rows[row_idx][0]) |
| else: |
| try: |
| slug = str(df[row_idx][0]) |
| except (IndexError, TypeError, KeyError): |
| return _empty_detail() |
| return _detail_html(CATALOG, slug) |
|
|
|
|
| def _on_filter( |
| query, modalities, subject_types, sources, licenses, min_subjects, only_on_hf |
| ): |
| filtered = _filter( |
| CATALOG, |
| query, |
| modalities, |
| subject_types, |
| sources, |
| licenses, |
| min_subjects, |
| only_on_hf, |
| ) |
| return ( |
| _render_table(filtered), |
| _hero_html(filtered, TOTAL_ALL), |
| _modality_strip_html(filtered), |
| ) |
|
|
|
|
| |
|
|
| CSS = CSS_PATH.read_text(encoding="utf-8") if CSS_PATH.exists() else "" |
|
|
| THEME = gr.themes.Base( |
| primary_hue=gr.themes.colors.blue, |
| secondary_hue=gr.themes.colors.slate, |
| neutral_hue=gr.themes.colors.slate, |
| font=(gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"), |
| font_mono=( |
| gr.themes.GoogleFont("JetBrains Mono"), |
| "ui-monospace", |
| "SFMono-Regular", |
| "monospace", |
| ), |
| ).set( |
| body_background_fill="#f8fafc", |
| body_background_fill_dark="#0b1220", |
| background_fill_primary="#ffffff", |
| background_fill_primary_dark="#111827", |
| border_color_primary="#e2e8f0", |
| border_color_primary_dark="#1f2937", |
| button_primary_background_fill="#0072B2", |
| button_primary_background_fill_hover="#005A8F", |
| button_primary_text_color="#ffffff", |
| block_radius="14px", |
| input_radius="10px", |
| body_text_color="#0f172a", |
| body_text_color_dark="#e2e8f0", |
| ) |
|
|
|
|
| HEAD = f""" |
| <link rel="icon" type="image/x-icon" href="{FAVICON_URL}" /> |
| <meta name="description" content="Search {TOTAL_ALL} open EEG / MEG datasets — EEGDash catalog." /> |
| <meta property="og:title" content="EEGDash — EEG/MEG dataset catalog" /> |
| <meta property="og:description" content="Open catalog of EEG/MEG datasets, loadable with one line of Python." /> |
| <script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script> |
| """ |
|
|
| with gr.Blocks( |
| title="EEGDash — EEG/MEG dataset catalog", |
| css=CSS, |
| theme=THEME, |
| analytics_enabled=False, |
| head=HEAD, |
| ) as demo: |
| hero = gr.HTML(_hero_html(CATALOG, TOTAL_ALL), elem_classes=["eeg-hero-wrap"]) |
| modality_strip = gr.HTML( |
| _modality_strip_html(CATALOG), elem_classes=["eeg-modality-wrap"] |
| ) |
|
|
| with gr.Accordion( |
| "Catalog views", |
| open=True, |
| elem_classes=["eeg-overview"], |
| ): |
| with gr.Tabs(elem_classes=["eeg-tabs"]): |
| with gr.Tab("Flow"): |
| gr.HTML( |
| '<p class="eeg-overview__lede">The catalog as a navigation ' |
| 'map. Every dataset flows from its <em>experimental ' |
| 'modality</em> (left) to its <em>clinical population</em> ' |
| '(middle) to its <em>repository</em> (right). Ribbon ' |
| 'thickness is the dataset count along that path — follow ' |
| 'any color to see where a paradigm of interest lives.</p>' |
| + _plot_iframe("dataset_sankey", height=640, title="Catalog flow (sankey)"), |
| elem_classes=["eeg-plot"], |
| ) |
| with gr.Tab("Bubbles"): |
| gr.HTML( |
| '<p class="eeg-overview__lede">Every dataset as an ' |
| 'individual mark. Bubble size is recording count, color is ' |
| 'experimental modality, axes span subjects × duration. ' |
| 'Hover to find a specific dataset; use the filter below to ' |
| 'narrow the field.</p>' |
| + _plot_iframe("dataset_bubble", height=780, title="Dataset bubble chart"), |
| elem_classes=["eeg-plot"], |
| ) |
| with gr.Tab("Treemap"): |
| gr.HTML( |
| '<p class="eeg-overview__lede">Nested rectangles grouped by ' |
| 'modality. Area is proportional to recording count — the ' |
| 'biggest tiles are the heaviest contributors.</p>' |
| + _plot_iframe("dataset_treemap", height=820, title="Dataset treemap"), |
| elem_classes=["eeg-plot"], |
| ) |
| with gr.Tab("Growth"): |
| gr.HTML( |
| '<p class="eeg-overview__lede">New datasets added to the ' |
| 'catalog over time, colored by source. The slope tells you ' |
| 'how fast the archive has expanded.</p>' |
| + _plot_iframe("dataset_growth", height=520, title="Catalog growth"), |
| elem_classes=["eeg-plot"], |
| ) |
| with gr.Tab("Clinical"): |
| gr.HTML( |
| '<p class="eeg-overview__lede">Clinical populations ' |
| 'represented in the catalog — from healthy controls to ' |
| 'neurodegenerative and psychiatric conditions.</p>' |
| + _plot_iframe("dataset_clinical", height=520, title="Clinical breakdown"), |
| elem_classes=["eeg-plot"], |
| ) |
|
|
| with gr.Row(elem_classes=["eeg-toolbar"]): |
| query = gr.Textbox( |
| label="Search", |
| placeholder="Type a dataset id, author, or keyword…", |
| show_label=False, |
| elem_classes=["eeg-search"], |
| scale=4, |
| ) |
| only_on_hf = gr.Checkbox( |
| label="Only 🤗-mirrored", |
| value=False, |
| elem_classes=["eeg-toggle"], |
| scale=1, |
| ) |
|
|
| with gr.Accordion("Filters", open=False, elem_classes=["eeg-filters"]): |
| with gr.Row(): |
| modalities = gr.CheckboxGroup( |
| label="Modality", choices=MODALITY_CHOICES, value=[] |
| ) |
| subject_types = gr.CheckboxGroup( |
| label="Pathology / population", choices=SUBJECT_CHOICES, value=[] |
| ) |
| with gr.Row(): |
| sources = gr.CheckboxGroup( |
| label="Source", choices=SOURCE_CHOICES, value=[] |
| ) |
| licenses = gr.Dropdown( |
| label="License", |
| choices=LICENSE_CHOICES, |
| multiselect=True, |
| value=[], |
| ) |
| min_subjects = gr.Slider( |
| label="Minimum subjects", |
| minimum=0, |
| maximum=500, |
| step=10, |
| value=0, |
| ) |
|
|
| with gr.Row(elem_classes=["eeg-main"]): |
| with gr.Column(scale=3, elem_classes=["eeg-main__table"]): |
| table = gr.Dataframe( |
| value=_render_table(CATALOG), |
| interactive=False, |
| wrap=False, |
| column_widths=[ |
| "140px", "140px", "90px", "90px", "120px", "110px", |
| "140px", "85px", "85px", "60px", "80px", "70px", |
| "85px", "110px", "50px", |
| ], |
| label=None, |
| show_label=False, |
| elem_classes=["eeg-table"], |
| max_height=640, |
| ) |
| with gr.Column(scale=2, elem_classes=["eeg-main__detail"]): |
| detail = gr.HTML(_empty_detail(), elem_classes=["eeg-detail"]) |
|
|
| gr.HTML( |
| f""" |
| <footer class="eeg-foot"> |
| <span> |
| EEGDash is open source · BSD-3-Clause · data licenses follow their origin. |
| <a href="{EEGDASH_URL}">eegdash.org</a> · |
| <a href="{GITHUB_URL}">github</a> · |
| <a href="https://huggingface.co/{HF_ORG}">🤗 {HF_ORG}</a> |
| </span> |
| </footer> |
| """, |
| elem_classes=["eeg-foot-wrap"], |
| ) |
|
|
| filter_inputs = [ |
| query, modalities, subject_types, sources, licenses, min_subjects, only_on_hf, |
| ] |
| for w in filter_inputs: |
| w.change(_on_filter, filter_inputs, [table, hero, modality_strip]) |
|
|
| table.select(_on_select, [table], [detail]) |
|
|
|
|
| if __name__ == "__main__": |
| demo.queue().launch( |
| ssr_mode=False, |
| allowed_paths=[str(ASSETS_DIR)], |
| ) |
|
|