Skip to content

Commit 7298932

Browse files
Merge branch 'develop' into doc-comprehensive-documentation
2 parents c0f3d04 + bb94115 commit 7298932

File tree

5 files changed

+155
-7
lines changed

5 files changed

+155
-7
lines changed

docs/source/_static/custom.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,3 +624,39 @@ html.dataset-summary-page .bd-main .bd-content .bd-article-container {
624624
max-width: 100%;
625625
}
626626
}
627+
628+
/* Inline reading time badge for tutorials */
629+
.eegdash-reading-time {
630+
display: inline-flex;
631+
align-items: baseline;
632+
gap: 0.4rem;
633+
margin: 0.5rem 0 1.25rem;
634+
padding: 0.35rem 0.75rem;
635+
border-radius: 0.65rem;
636+
background: var(--sd-color-surface-secondary, rgba(10, 111, 182, 0.08));
637+
color: var(--pst-color-text-muted, #4b5563);
638+
font-size: 0.95rem;
639+
font-weight: 500;
640+
}
641+
642+
.eegdash-reading-time__label {
643+
text-transform: uppercase;
644+
letter-spacing: 0.08em;
645+
font-size: 0.75rem;
646+
font-weight: 600;
647+
color: var(--sd-color-primary, #0a6fb6);
648+
}
649+
650+
.eegdash-reading-time__value {
651+
font-weight: 700;
652+
color: var(--pst-color-text, #111827);
653+
}
654+
655+
html[data-theme="dark"] .eegdash-reading-time {
656+
background: rgba(15, 118, 184, 0.22);
657+
color: var(--pst-color-text-muted, #d1d5db);
658+
}
659+
660+
html[data-theme="dark"] .eegdash-reading-time__value {
661+
color: var(--pst-color-text, #f9fafb);
662+
}

docs/source/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from sphinx_gallery.sorting import ExplicitOrder, FileNameSortKey
1313
from tabulate import tabulate
1414

15+
sys.path.insert(0, os.path.abspath(".."))
16+
1517
import eegdash
1618

1719
# -- Project information -----------------------------------------------------
@@ -44,6 +46,7 @@
4446
"sphinx_sitemap",
4547
"sphinx_copybutton",
4648
"sphinx.ext.graphviz",
49+
"sphinx_time_estimation",
4750
]
4851

4952
templates_path = ["_templates"]

docs/sphinx_time_estimation.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from __future__ import annotations
2+
3+
import math
4+
import re
5+
6+
from docutils import nodes
7+
8+
SKIP_CONTAINER_CLASSES = {
9+
"sphx-glr-script-out",
10+
"sphx-glr-single-img",
11+
"sphx-glr-thumbnail",
12+
"sphx-glr-horizontal",
13+
}
14+
15+
16+
class TextExtractor(nodes.NodeVisitor):
17+
def __init__(self, document):
18+
super().__init__(document)
19+
self.text = []
20+
21+
def visit_Text(self, node):
22+
self.text.append(node.astext())
23+
24+
def visit_literal_block(self, node):
25+
# Don't visit the children of literal blocks (i.e., code blocks)
26+
raise nodes.SkipNode
27+
28+
def visit_figure(self, node):
29+
raise nodes.SkipNode
30+
31+
def visit_image(self, node):
32+
raise nodes.SkipNode
33+
34+
def visit_container(self, node):
35+
classes = set(node.get("classes", ()))
36+
if classes & SKIP_CONTAINER_CLASSES:
37+
raise nodes.SkipNode
38+
39+
def unknown_visit(self, node):
40+
"""Pass for all other nodes."""
41+
pass
42+
43+
44+
EXAMPLE_PREFIX = "generated/auto_examples/"
45+
46+
47+
def _should_calculate(pagename: str) -> bool:
48+
if not pagename:
49+
return False
50+
if not pagename.startswith(EXAMPLE_PREFIX):
51+
return False
52+
if pagename.endswith("/sg_execution_times"):
53+
return False
54+
if pagename == "generated/auto_examples/index":
55+
return False
56+
return True
57+
58+
59+
def html_page_context(app, pagename, templatename, context, doctree):
60+
"""Add estimated reading time directly under tutorial titles."""
61+
if not doctree or not _should_calculate(pagename):
62+
context.pop("reading_time", None)
63+
return
64+
65+
visitor = TextExtractor(doctree)
66+
doctree.walk(visitor)
67+
68+
full_text = " ".join(visitor.text)
69+
word_count = len(re.findall(r"\w+", full_text))
70+
71+
wpm = 200 # Median reading speed
72+
reading_time = math.ceil(word_count / wpm) if wpm > 0 else 0
73+
74+
if reading_time <= 0:
75+
context.pop("reading_time", None)
76+
return
77+
78+
context["reading_time"] = reading_time
79+
80+
body = context.get("body")
81+
if not isinstance(body, str) or "</h1>" not in body:
82+
return
83+
84+
minutes_label = "minute" if reading_time == 1 else "minutes"
85+
badge_html = (
86+
'<div class="eegdash-reading-time" role="note">'
87+
'<span class="eegdash-reading-time__label">Estimated reading time:</span>'
88+
f'<span class="eegdash-reading-time__value">{reading_time} {minutes_label}</span>'
89+
"</div>"
90+
)
91+
92+
insert_at = body.find("</h1>")
93+
if insert_at == -1:
94+
return
95+
96+
context["body"] = body[: insert_at + 5] + badge_html + body[insert_at + 5 :]
97+
98+
99+
def setup(app):
100+
"""Setup the Sphinx extension."""
101+
app.connect("html-page-context", html_page_context)
102+
return {
103+
"version": "0.1",
104+
"parallel_read_safe": True,
105+
"parallel_write_safe": True,
106+
}

examples/eeg2025/tutorial_challenge_1.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
# Note: For simplicity purposes, we will only show how to do the decoding
6868
# directly in our target task, and it is up to the teams to think about
6969
# how to use the passive task to perform the pre-training.
70-
#
71-
######################################################################
70+
71+
#######################################################################
7272
# Install dependencies
7373
# --------------------
7474
# For the challenge, we will need two significant dependencies:
@@ -132,7 +132,7 @@
132132
#
133133
######################################################################
134134
# The brain decodes the problem
135-
# =============================
135+
# -----------------------------
136136
#
137137
# Broadly speaking, here *brain decoding* is the following problem:
138138
# given brain time-series signals :math:`X \in \mathbb{R}^{C \times T}` with
@@ -155,7 +155,6 @@
155155
# is the temporal window length/epoch size over the interval of interest.
156156
# Here, :math:`\theta` denotes the parameters learned by the neural network.
157157
#
158-
# ------------------------------------------------------------------------------
159158
# Input/Output definition
160159
# ---------------------------
161160
# For the competition, the HBN-EEG (Healthy Brain Network EEG Datasets)
@@ -194,8 +193,10 @@
194193
# * The **ramp onset**, the **button press**, and the **feedback** are **time-locked events** that yield ERP-like components.
195194
#
196195
# Your task (**label**) is to predict the response time for the subject during this windows.
197-
######################################################################
196+
#
197+
#######################################################################
198198
# In the figure below, we have the timeline representation of the cognitive task:
199+
#
199200
# .. image:: https://eeg2025.github.io/assets/img/image-2.jpg
200201

201202
######################################################################

examples/eeg2025/tutorial_challenge_2.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,11 @@ def __getitem__(self, index):
319319
# All the braindecode models expect the input to be of shape (batch_size, n_channels, n_times)
320320
# and have a test coverage about the behavior of the model.
321321
# However, you can use any pytorch model you want.
322-
########################################################################
322+
#
323+
######################################################################
323324
# Initialize model
324-
# ----------------
325+
# -----------------
326+
325327
model = EEGNeX(n_chans=129, n_outputs=1, n_times=2 * SFREQ).to(device)
326328

327329
# Specify optimizer

0 commit comments

Comments
 (0)