Skip to content

Commit b3b47d3

Browse files
Merge pull request #171 from sccn/develop
sinc
2 parents 3d84796 + a8e3ce9 commit b3b47d3

File tree

14 files changed

+742
-268
lines changed

14 files changed

+742
-268
lines changed

.github/workflows/tests.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ on:
1414
jobs:
1515
test:
1616
runs-on: ${{ matrix.os }}
17+
# TODO: This is a temporary measure to reduce costs.
18+
# The matrix should be expanded back to multiple OS and Python versions
19+
# once the cost issue is resolved.
1720
strategy:
1821
fail-fast: false
1922
matrix:
20-
os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
21-
python-version: ["3.10", "3.12"] # first and last supported Python version
23+
os: [ "ubuntu-latest" ]
24+
python-version: ["3.12"]
2225
steps:
2326
## Install Braindecode
2427
- name: Checking Out Repository

docs/plot_dataset/bubble.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def generate_dataset_bubble(
176176
log_y=True,
177177
)
178178

179+
# ---------- Reference line, OLS fit, and arrow (all robust in log space)
179180
numeric_x = pd.to_numeric(data[x_field], errors="coerce")
180181
numeric_y = pd.to_numeric(data[y_field], errors="coerce")
181182
mask = (
@@ -190,9 +191,29 @@ def generate_dataset_bubble(
190191
log_x = np.log10(numeric_x[mask])
191192
log_y = np.log10(numeric_y[mask])
192193
ss_tot = np.sum((log_y - log_y.mean()) ** 2)
194+
195+
# Draw 1:1 line as an underlying shape, clipped to 10^0..10^4 and data bounds
196+
lx_min = max(log_x.min(), log_y.min(), 0.0) # >= 10^0
197+
lx_max = min(log_x.max(), log_y.max(), 4.0) # <= 10^4
198+
if lx_min < lx_max:
199+
x0 = 10**lx_min
200+
x1 = 10**lx_max
201+
fig.add_shape(
202+
type="line",
203+
x0=x0,
204+
y0=x0,
205+
x1=x1,
206+
y1=x1,
207+
xref="x",
208+
yref="y",
209+
layer="below",
210+
line=dict(color="#9ca3af", width=1.5, dash="dash"),
211+
)
212+
213+
# Red dotted OLS line (computed in log space), clipped to same bounds
193214
if np.ptp(log_x) > 0 and np.ptp(log_y) > 0 and ss_tot > 0:
194215
slope, intercept = np.polyfit(log_x, log_y, 1)
195-
line_log_x = np.linspace(log_x.min(), log_x.max(), 200)
216+
line_log_x = np.linspace(max(log_x.min(), 0.0), min(log_x.max(), 4.0), 200)
196217
line_x = 10**line_log_x
197218
line_y = 10 ** (slope * line_log_x + intercept)
198219
fig.add_trace(
@@ -201,15 +222,40 @@ def generate_dataset_bubble(
201222
y=line_y,
202223
mode="lines",
203224
name="log-log fit",
204-
line=dict(color="#111827", width=2, dash="dot"),
225+
line=dict(color="#dc2626", width=2, dash="dot"),
205226
hoverinfo="skip",
206227
showlegend=False,
228+
opacity=0.35,
207229
)
208230
)
209231
residuals = log_y - (slope * log_x + intercept)
210232
r_squared = 1 - np.sum(residuals**2) / ss_tot
211-
fit_annotation_text = f"log-log OLS fit R² = {r_squared:.3f}"
233+
fit_annotation_text = f"<span style='color:#dc2626'>Red dotted line: log-log OLS fit R² = {r_squared:.3f}</span>"
234+
235+
# Arrow label ~60% along the 1:1 segment for stable placement
236+
if lx_min < lx_max:
237+
t = 0.82 # control the position along the line
238+
annot_log = (1 - t) * lx_min + t * lx_max
239+
annot_xy = np.log10(10**annot_log)
240+
fig.add_annotation(
241+
x=annot_xy,
242+
y=annot_xy,
243+
text="One record per subject",
244+
showarrow=True,
245+
arrowhead=3,
246+
arrowsize=2,
247+
arrowwidth=2,
248+
arrowcolor="#6b7280",
249+
ax=110,
250+
ay=90,
251+
axref="pixel",
252+
ayref="pixel",
253+
font=dict(size=20, color="#374151"),
254+
align="left",
255+
)
212256

257+
# ---------- Hover and styling ----------
258+
x_hover, y_hover = _build_hover_template(x_field, y_field)
213259
hover_template = (
214260
"<b>%{customdata[0]}</b>"
215261
f"<br>{x_hover}"
@@ -244,12 +290,14 @@ def generate_dataset_bubble(
244290
margin=dict(l=60, r=40, t=80, b=60),
245291
template="plotly_white",
246292
legend=dict(
247-
title="Modality",
293+
title="Modality 🖱️ (click to toggle)",
248294
orientation="h",
249295
yanchor="bottom",
250296
y=1.02,
251297
xanchor="right",
252298
x=0.99,
299+
itemclick="toggle",
300+
itemdoubleclick="toggleothers",
253301
),
254302
font=dict(
255303
family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",

docs/prepare_summary_tables.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def wrap_dataset_name(name: str):
145145
}
146146
147147
// 2) Initialize DataTable with SearchPanes button
148-
const FILTER_COLS = [1,2,3,4,5,6];
148+
const FILTER_COLS = [1,2,3,4,5,6,7];
149149
// Detect the index of the size column by header text
150150
const sizeIdx = (function(){
151151
let idx = -1;
@@ -191,14 +191,14 @@ def wrap_dataset_name(name: str):
191191
192192
// 3) UX: click a header to open the relevant filter pane
193193
$table.find('thead th').each(function (i) {
194-
if ([1,2,3,4].indexOf(i) === -1) return;
194+
if ([1,2,3,4,5].indexOf(i) === -1) return;
195195
window.jQuery(this)
196196
.css('cursor','pointer')
197197
.attr('title','Click to filter this column')
198198
.on('click', function () {
199199
dataTable.button('.buttons-searchPanes').trigger();
200200
window.setTimeout(function () {
201-
const idx = [1,2,3,4].indexOf(i);
201+
const idx = [1,2,3,4,5].indexOf(i);
202202
const $container = window.jQuery(dataTable.searchPanes.container());
203203
const $pane = $container.find('.dtsp-pane').eq(idx);
204204
const $title = $pane.find('.dtsp-title');
@@ -234,6 +234,7 @@ def prepare_table(df: pd.DataFrame):
234234
df = df[
235235
[
236236
"dataset",
237+
"record_modality",
237238
"n_records",
238239
"n_subjects",
239240
"n_tasks",
@@ -253,6 +254,7 @@ def prepare_table(df: pd.DataFrame):
253254
"modality of exp": "modality",
254255
"type of exp": "type",
255256
"Type Subject": "pathology",
257+
"record_modality": "record modality",
256258
}
257259
)
258260
# number of subject are always int
@@ -270,6 +272,7 @@ def prepare_table(df: pd.DataFrame):
270272
pathology_normalizer = _tag_normalizer("pathology")
271273
modality_normalizer = _tag_normalizer("modality")
272274
type_normalizer = _tag_normalizer("type")
275+
record_modality_normalizer = _tag_normalizer("record_modality")
273276

274277
df["pathology"] = df["pathology"].apply(
275278
lambda value: wrap_tags(
@@ -292,6 +295,13 @@ def prepare_table(df: pd.DataFrame):
292295
normalizer=type_normalizer,
293296
)
294297
)
298+
df["record modality"] = df["record modality"].apply(
299+
lambda value: wrap_tags(
300+
value,
301+
kind="dataset-record-modality",
302+
normalizer=record_modality_normalizer,
303+
)
304+
)
295305

296306
# Creating the total line
297307
df.loc["Total"] = df.sum(numeric_only=True)
@@ -301,6 +311,7 @@ def prepare_table(df: pd.DataFrame):
301311
df.loc["Total", "pathology"] = ""
302312
df.loc["Total", "modality"] = ""
303313
df.loc["Total", "type"] = ""
314+
df.loc["Total", "record modality"] = ""
304315
df.loc["Total", "size"] = human_readable_size(df.loc["Total", "size_bytes"])
305316
df = df.drop(columns=["size_bytes"])
306317
# arrounding the hours
@@ -361,11 +372,13 @@ def main(source_dir: str, target_dir: str):
361372
"pathology": "Pathology",
362373
"modality": "Modality",
363374
"type": "Type",
375+
"record modality": "Record modality",
364376
}
365377
)
366378
df = df[
367379
[
368380
"Dataset",
381+
"Record modality",
369382
"Pathology",
370383
"Modality",
371384
"Type",

docs/source/_static/eeg.png

173 KB
Loading

docs/source/_static/emg.png

168 KB
Loading

docs/source/_static/ieeg.png

151 KB
Loading

0 commit comments

Comments
 (0)