bruAristimunha's picture
feat: OpenEEGBench submission flow (arena runs the eval)
4db3be0
Raw History Blame Contribute Delete
10.7 kB
import React, { useState, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Box, Paper, Typography, Button, Stack, Collapse } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
const DocLink = ({ href, children }) => (
<Button
variant="text"
size="small"
href={href}
target="_blank"
sx={{
fontFamily: "monospace",
textTransform: "none",
color: "primary.main",
fontSize: "0.875rem",
p: 0,
minWidth: "auto",
justifyContent: "flex-start",
"&:hover": {
color: "primary.dark",
backgroundColor: "transparent",
textDecoration: "underline",
},
}}
>
{children} {"\u2192"}
</Button>
);
const StepNumber = ({ number }) => (
<Box
sx={{
width: 32,
height: 32,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid",
borderColor: "primary.main",
color: "primary.main",
fontSize: "0.875rem",
fontWeight: 600,
flexShrink: 0,
bgcolor: "transparent",
}}
>
{number}
</Box>
);
const CITATION = `@software{open_eeg_bench,
title = {Open EEG Bench: Benchmarking parameter-efficient fine-tuning of EEG foundation models},
author = {Guetschel, Pierre and Aristimunha, Bruno and Truong, Dung and Kokate, Kuntal and Moreau, Thomas and Tangermann, Michael and Delorme, Arnaud},
year = {2026},
url = {https://github.com/braindecode/OpenEEGBench},
doi = {10.5281/zenodo.19698863}
}
@inproceedings{guetschel2026openeegbench,
title = {Toward {OpenEEG-Bench}: A Live Community-Driven Benchmark for {EEG} Foundation Models},
author = {Guetschel, Pierre and Aristimunha, Bruno and Truong, Dung and Kokate, Kuntal and Tangermann, Michael and Delorme, Arnaud},
booktitle = {Proceedings of the 34th European Signal Processing Conference (EUSIPCO 2026)},
year = {2026},
address = {Bruges, Belgium},
month = aug,
pages = {1--5},
organization = {EURASIP}
}`;
const TUTORIAL_STEPS = [
{
title: "Install & implement the model contract",
content: (
<Stack spacing={2}>
<Typography variant="body2" color="text.secondary">
Install the benchmark with{" "}
<strong>pip install open-eeg-bench</strong>. Your model must accept
input of shape <strong>(batch, n_chans, n_times)</strong> and return{" "}
<strong>(batch, n_outputs)</strong>, exposing a named classification
head module (default <strong>final_layer</strong>).
</Typography>
<Box
sx={{
p: 2,
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "grey.50" : "grey.900",
borderRadius: 1,
"& pre": {
m: 0,
p: 0,
fontFamily: "monospace",
fontSize: "0.875rem",
color: (theme) =>
theme.palette.mode === "dark" ? "grey.900" : "grey.50",
},
}}
>
<pre>
{`import open_eeg_bench as oeb
# Your model: (batch, n_chans, n_times) -> (batch, n_outputs)
results = oeb.benchmark(
model_cls="my_package.MyModel",
checkpoint_url="https://my-weights.pth",
head_module_name="final_layer",
)
print(results) # pd.DataFrame`}
</pre>
</Box>
<DocLink href="https://github.com/braindecode/OpenEEGBench">
OpenEEGBench repository
</DocLink>
</Stack>
),
},
{
title: "Publish your weights",
content: (
<Stack spacing={2}>
<Typography variant="body2" color="text.secondary">
Push your checkpoint to a <strong>public HuggingFace Hub repo</strong>{" "}
(with a <strong>license tag</strong>) or host it at a direct{" "}
<strong>checkpoint URL</strong>. The arena loads the weights to run
the evaluation.
</Typography>
<DocLink href="https://huggingface.co/docs/hub/models-uploading">
Model uploading guide
</DocLink>
</Stack>
),
},
{
title: "Sign in & fill the form",
content: (
<Stack spacing={2}>
<Typography variant="body2" color="text.secondary">
Provide your <strong>model class import path</strong>, the{" "}
<strong>weights source</strong>, a{" "}
<strong>fine-tuning strategy</strong> and{" "}
<strong>classification head</strong>, the{" "}
<strong>datasets</strong> (default: all 10), and the number of{" "}
<strong>seeds</strong>.
</Typography>
</Stack>
),
},
{
title: "Submit & wait",
content: (
<Stack spacing={2}>
<Typography variant="body2" color="text.secondary">
The arena queues your model and a worker runs{" "}
<strong>oeb.benchmark()</strong> to post official scores. Limit:{" "}
<strong>5 submissions per month</strong>.
</Typography>
</Stack>
),
},
{
title: "Privacy & help",
content: (
<Stack spacing={2}>
<Typography variant="body2" color="text.secondary">
Your <strong>contact email</strong> is stored privately. For help or
to request removal of a submission, open an issue.
</Typography>
<DocLink href="https://github.com/braindecode/OpenEEGBench/issues">
OpenEEGBench issues
</DocLink>
</Stack>
),
},
];
function SubmissionGuide() {
const location = useLocation();
const navigate = useNavigate();
// Initialize state directly with URL value
const initialExpanded = !new URLSearchParams(location.search).get("guide");
const [expanded, setExpanded] = useState(initialExpanded);
// Sync expanded state with URL changes after initial render
useEffect(() => {
const guideOpen = !new URLSearchParams(location.search).get("guide");
if (guideOpen !== expanded) {
setExpanded(guideOpen);
}
}, [location.search, expanded]);
const handleAccordionChange = () => {
const newExpanded = !expanded;
setExpanded(newExpanded);
const params = new URLSearchParams(location.search);
if (newExpanded) {
params.delete("guide");
} else {
params.set("guide", "closed");
}
navigate({ search: params.toString() }, { replace: true });
};
return (
<Paper
elevation={0}
sx={{
mb: 3,
borderRadius: "8px !important",
border: "1px solid",
borderColor: (theme) =>
theme.palette.mode === "dark" ? "grey.800" : "grey.200",
overflow: "hidden",
}}
>
<Box
onClick={handleAccordionChange}
sx={{
p: 2,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "grey.900" : "grey.50",
borderBottom: "1px solid",
borderColor: (theme) =>
expanded
? theme.palette.mode === "dark"
? "grey.800"
: "grey.200"
: "transparent",
}}
>
<Typography
variant="h6"
sx={{ fontWeight: 600, color: "text.primary" }}
>
Submission Guide
</Typography>
<ExpandMoreIcon
sx={{
transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.3s",
}}
/>
</Box>
<Collapse in={expanded} appear={false}>
<Box sx={{ py: 4 }}>
<Stack spacing={4}>
{TUTORIAL_STEPS.map((step, index) => (
<Box key={step.title}>
<Stack spacing={3}>
<Stack
direction="row"
spacing={2}
alignItems="center"
sx={{ px: 4 }}
>
<StepNumber number={index + 1} />
<Typography
variant="subtitle1"
sx={{
fontWeight: 600,
color: "text.primary",
letterSpacing: "-0.01em",
}}
>
{step.title}
</Typography>
</Stack>
<Box sx={{ px: 4, pl: 7 }}>{step.content}</Box>
</Stack>
{index < TUTORIAL_STEPS.length - 1 && (
<Box
sx={{
mt: 4,
borderTop: "1px solid",
borderColor: (theme) =>
theme.palette.mode === "dark" ? "grey.800" : "grey.100",
}}
/>
)}
</Box>
))}
<Box
sx={{
mt: 4,
pt: 4,
px: 4,
borderTop: "1px solid",
borderColor: (theme) =>
theme.palette.mode === "dark" ? "grey.800" : "grey.100",
}}
>
<Stack spacing={2}>
<Typography
variant="subtitle1"
sx={{
fontWeight: 600,
color: "text.primary",
letterSpacing: "-0.01em",
}}
>
Citation
</Typography>
<Typography variant="body2" color="text.secondary">
If you use OpenEEGBench in your research, please cite it.
</Typography>
<Box
sx={{
p: 2,
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "grey.50" : "grey.900",
borderRadius: 1,
overflowX: "auto",
"& pre": {
m: 0,
p: 0,
fontFamily: "monospace",
fontSize: "0.8125rem",
whiteSpace: "pre",
color: (theme) =>
theme.palette.mode === "dark" ? "grey.900" : "grey.50",
},
}}
>
<pre>{CITATION}</pre>
</Box>
</Stack>
</Box>
</Stack>
</Box>
</Collapse>
</Paper>
);
}
export default SubmissionGuide;