Optimal Size of Electrocorticography Grids for Classification of Hand Movements.
The 9 matches
- [1] § Materials and Methods › Classification ↔ subopt/hillclimbing.py, lines 467–560 · score 0.86 · min max scaler, cross validation, scikit-learn, SVC, Machine, SVM
- [2] § Materials and Methods › Classification ↔ subopt/exhaustive.py, lines 132–211 · score 0.86 · min max scaler, cross validation, scikit-learn, SVC, Machine, SVM
- [3] § Materials and Methods › Preprocessing ↔ examples/synth_data.py, lines 131–197 · score 0.80 · power feature, high frequency band, epochs, amplitude, noise, Preprocessing
- [4] § Materials and Methods › Performance-area Trade-off ↔ subopt/base.py, lines 315–377 · score 0.71 · inter electrode distance, electrode diameter, surface area, IED, height, width
- [5] § Materials and Methods › Exhaustive Subgrid Search ↔ subopt/exhaustive.py, lines 132–211 · score 0.69 · cross validation splits, exhaustive search, SVM, model, fold, trained
- [6] § Materials and Methods › Exhaustive Subgrid Search ↔ subopt/hillclimbing.py, lines 467–560 · score 0.63 · cross validation splits, SVM, model, fold, trained, stochastic
- [7] § Materials and Methods › Exhaustive Subgrid Search ↔ subopt/exhaustive.py, lines 23–124 · score 0.60 · possible subgrid, exhaustive search, iteratively, space, rows, configuration
- [8] § Results › Performance-area Analysis ↔ subopt/base.py, lines 315–377 · score 0.59 · Inter Electrode Distance, Electrode Diameter, surface area, IED, class, score
- [9] § Materials and Methods › Participants ↔ examples/synth_data.py, lines 14–49 · score 0.50 · sensorimotor cortex, ECoG
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 314 lines · 11 KB · MIT · 3 matches
- # -------------------------------------------------------------
- # Subgrid Optimization
- # Copyright (c) 2026
- # Dirk Keller, Nick Ramsey's Lab, University Medical Center Utrecht, University Utrecht
- # Licensed under the MIT License [see LICENSE for detail]
- # -------------------------------------------------------------
- from copy import copy
- from typing import Tuple, Union, Any, Optional, Type, List
- import numpy as np
- import pandas as pd
- from sklearn.base import MetaEstimatorMixin, TransformerMixin
- from sklearn.model_selection import BaseCrossValidator
- from sklearn.pipeline import Pipeline
- from sklearn.utils.validation import check_is_fitted as sklearn_is_fitted
- from tqdm import tqdm
- from .base import BaseOptimizer
- from .utils import compute_subgrid_dimensions
- class ExhaustiveSearch(MetaEstimatorMixin, TransformerMixin):
- """
- Performs an exhaustive search over all possible subgrids in a given channel grid,
- evaluating each using a specified objective function.
- Parameters:
- -----------
- :param channel_grid: numpy.ndarray
- The grid of channel IDs.
- :param func: callable
- The objective function, which takes a mask and a list of included channels and returns a metric score.
- :param verbose: bool, default = False
- If set to True, outputs progress messages during the optimization process.
- Returns:
- --------
- :return: None
- """
- def __init__(self, channel_grid, func, verbose=False) -> None:
- self.channel_grid = channel_grid
- self.objective = func
- self.verbose = verbose
- def run(self) -> Tuple[int, np.ndarray]:
- """
- Executes the exhaustive search to find the best subgrid spatially constrained
- channel combination.
- Iteratively searches through the whole subgrid space.
- Records and returns the evaluation history.
- Returns:
- --------
- :return: tuple
- A tuple containing the best score achieved, the corresponding mask
- for the best subgrid, and a DataFrame detailing the evaluation results
- for each configuration.
- """
- best_score = 0.0
- best_mask = None
- # Main loop over the number of starting positions
- height, width = self.channel_grid.shape
- subgrids = self.generate_subgrids(height, width)
- mask_template = np.zeros_like(self.channel_grid, dtype=bool)
- pbar = range(len(subgrids))
- if self.verbose:
- pbar = tqdm(
- pbar, desc="Exhaustive Search", postfix={"score": f"{best_score:.6f}"}
- )
- for idx in pbar:
- start_row, start_col, end_row, end_col = subgrids[idx]
- mask = copy(mask_template)
- mask[start_row:end_row, start_col:end_col] = True
- # Calculate the score for the current subgrid
- score = self.objective(mask)
- # Check if this is the best score so far
- if score > best_score:
- best_score = score
- best_mask = mask
- if self.verbose:
- pbar.set_postfix({"score": f"{best_score:.6f}"})
- return best_score, best_mask
- @staticmethod
- def generate_subgrids(
- grid_height: int, grid_width: int
- ) -> List[Tuple[int, int, int, int]]:
- """
- Generates all possible subgrids within a given grid height and width.
- Each subgrid is defined by its starting and ending coordinates.
- Parameters:
- -----------
- :param grid_height: int
- The height of the grid.
- :param grid_width: int
- The width of the grid.
- Returns:
- --------
- :return: List[Tuple[int, int, int, int]]
- A list of tuples, where each tuple contains the coordinates of a subgrid in the format
- (start_row, start_col, end_row, end_col).
- """
- subgrids = []
- for start_row in range(grid_height + 1):
- for start_col in range(grid_width + 1):
- for end_row in range(start_row, grid_height + 1):
- for end_col in range(start_col, grid_width + 1):
- if start_row < end_row and start_col < end_col:
- subgrids.append((start_row, start_col, end_row, end_col))
- return subgrids
- ########################################################################################
- ###################### Scikit-Learn: SpatialStochasticHillClimbing #####################
- ########################################################################################
- class SpatialExhaustiveSearch(BaseOptimizer):
- """
- Implements a spatially constrained exhaustive search for finding
- global best channel combinations within a grid based on a given metric.
- Parameters:
- -----------
- :param grid: numpy.ndarray
- The grid structure specifying how channels are arranged.
- :param estimator: Union[Any, Pipeline]
- The machine learning estimator or pipeline to evaluate
- channel combinations.
- :param metric: str, default = 'f1_weighted'
- The metric to optimize, compatible with scikit-learn metrics.
- :param cv: Union[BaseCrossValidator, int], default = 10
- Cross-validation splitting strategy, can be a fold number
- or a scikit-learn cross-validator.
- :param n_jobs: int, default = 1
- Number of parallel jobs to run during cross-validation.
- '-1' uses all available cores.
- :param seed: Optional[int], default = None
- Seed for randomness, ensuring reproducibility.
- :param verbose: Union[bool, int], default = False
- Enables verbose output during the optimization process.
- Methods:
- --------
- - fit:
- Fit the model to the data, search through the spatial
- constrained channel combinations.
- - transform:
- Apply the mask obtained from the search to transform the data.
- - run:
- Execute the spatial exhaustive search.
- - evaluate_candidates:
- Evaluates the selected features using cross-validation or train-test split.
- - objective_function:
- Evaluate each candidate configuration and return their scores.
- - elimination_plot:
- Generate and save a plot visualizing the performance across different subgrid sizes.
- - importance_plot:
- Generate and save a heatmap visualizing the importance of each channel.
- Notes:
- ------
- This implementation is semi-compatible with the scikit-learn
- framework, which builds around two-dimensional feature matrices.
- To use this transformation within a scikit-learn Pipeline, the
- four dimensional data must eb flattened after the first dimension
- [samples, features]. For example, scikit-learn's FunctionTransformer can
- achieve this.
- Examples:
- ---------
- The following example shows how to retrieve a feature mask for
- a synthetic data set.
- # >>> import numpy as np
- # >>> from sklearn.svm import SVC
- # >>> from sklearn.pipeline import Pipeline
- # >>> from sklearn.preprocessing import MinMaxScaler
- # >>> from sklearn.datasets import make_classification
- # >>> from subopt.exhaustive import SpatialExhaustiveSearch
- # >>> X, y = make_classification(n_samples=100, n_features=8 * 4 * 100)
- # >>> X = X.reshape((100, 8, 4, 100))
- # >>> grid = np.arange(1, 33).reshape(X.shape[1:3])
- # >>> estimator = Pipeline([('scaler', MinMaxScaler()), ('svc', SVC())])
- # >>> shc = SpatialExhaustiveSearch(grid, estimator, verbose=True)
- # >>> shc.fit(X, y)
- # >>> print(shc.mask_)
- array([[False True False False], [False False False False], [ True True False False], [False False False True],
- [False False False False], [False False False False], [False False True False], [False False False False]])
- # >>> print(shc.score_)
- 26.966666666666672
- Returns:
- --------
- :return: None
- """
- def __init__(
- self,
- # General and Decoder
- grid: np.array,
- estimator: Union[Any, Pipeline],
- metric: str = "f1_weighted",
- cv: Union[BaseCrossValidator, int] = 10,
- # Misc
- n_jobs: int = 1,
- seed: Optional[int] = None,
- verbose: Union[bool, int] = False,
- ) -> None:
- super().__init__(grid, estimator, metric, cv, n_jobs, seed, verbose)
- def fit(
- self, X: np.ndarray, y: np.ndarray = None
- ) -> Type["SpatialExhaustiveSearch"]:
- """
- Fit method optimizes the channel combination with
- Spatial Exhaustive Search.
- Parameters:
- -----------
- :param X: numpy.ndarray
- Array-like with dimensions [samples, channel_height, channel_width, time]
- :param y: numpy.ndarray, default = None
- Array-like with dimensions [targets].
- Return:
- -----------
- :return: Type['SpatialExhaustiveSearch']
- """
- self.X_ = X
- self.y_ = y
- self.iter_ = int(0)
- self.result_grid_ = []
- self.solution_, self.mask_, self.score_ = self.run()
- # Conclude the result grid (Calculate the Size and Height)
- self.result_grid_ = pd.concat(self.result_grid_, axis=0, ignore_index=True)
- self.result_grid_[["Height", "Width"]] = self.result_grid_["Mask"].apply(
- lambda mask: pd.Series(compute_subgrid_dimensions(mask))
- )
- columns = list(self.result_grid_.columns)
- size_index = columns.index("Size")
- new_order = (
- columns[: size_index + 1]
- + ["Height", "Width"]
- + columns[size_index + 1 : -2]
- )
- self.result_grid_ = self.result_grid_[new_order]
- return self
- def transform(self, X: np.ndarray, y: np.ndarray = None) -> np.ndarray:
- """
- Transforms the input with the mask obtained from
- the solution of Spatial Exhaustive Search.
- Parameters:
- -----------
- :param X: numpy.ndarray
- Array-like with dimensions [samples, channel_height, channel_width, time]
- :param y: numpy.ndarray, default = None
- Array-like with dimensions [targets].
- Return:
- -----------
- :return: numpy.ndarray
- Returns a filtered array-like with dimensions
- [samples, channel_height, channel_width, time]
- """
- sklearn_is_fitted(self)
- return X[:, self.mask_, :]
- def run(self) -> Tuple[np.ndarray, np.ndarray, float]:
- """
- Executes the Spatial Exhaustive Search.
- Parameters:
- --------
- :return: Tuple[numpy.ndarray, float]
- The best channel configuration and its score.
- Returns:
- --------
- :return: Tuple[numpy.ndarray, numpy.ndarray, float, pandas.DataFrame]
- A tuple with the solution, mask, the evaluation scores and the optimization history.
- """
- # Initialize and run the SSHC optimizer
- es = ExhaustiveSearch(
- func=self.objective_function, channel_grid=self.grid, verbose=self.verbose
- )
- score, mask = es.run()
- solution = mask.reshape(-1).astype(float)
- best_state = mask
- best_score = score * 100
- return solution, best_state, best_score
exhaustive.py at commit d1d856f, under MIT · at the source
Overview
- Department of Neurology and Neurosurgery, University Medical Center Utrecht Brain Center, Utrecht University, Utrecht, The Netherlands
- Department of Information and Computing Sciences, Utrecht University, Utrecht, The Netherlands
- Donders Institute for Brain, Cognition and Behaviour, Radboud University, Nijmegen, The Netherlands
Abstract
Implanted Brain-Computer Interfaces (BCIs) hold significant promise as a replacement for conventional assistive technologies for people with extensive motor impairments. In recent years, significant progress has been made in enhancing BCI performance, bringing clinically viable systems within reach. Some of these developments rely on increasingly large numbers of high spatial-density electrocorticography (ECoG) electrodes, which enable the extraction of spatially detailed information from extensive brain areas, but may also elevate surgical burden, posing a challenge for the clinical adoption of implanted BCIs. To mitigate this risk, we conducted an exhaustive investigation of hand movement classification performance involving 4, 5, and 8 classes in nine individuals with epilepsy, exploring all possible rectangular ECoG subgrids within the 32-, 64-, and 128-channel grids that were implanted in these individuals. Our findings reveal that the surface area of ECoG grids can be substantially reduced by 75–94% compared to the original grids, without a meaningful decline in classification performance, if electrodes are placed over informative areas. Classification performance across datasets was stable for progressively smaller subgrids until a critical threshold of approximately 60mm2 was reached, below which performance declined substantially. We show that smallest subgrids with an area above the threshold achieved equally high classification F1 scores (range 81.64-99.71%) as the full grids with an area > 230mm2 (range 82.85-96.75%). We conclude that ECoG-based BCI can be used to accurately decode up to seven different hand movements from well-located grids with a small number of electrodes, paving the way for smaller and safer BCI implants.
Supplementary Information: The online version contains supplementary material available at 10.1007/
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 9 matches between paragraphs and lines of code.
UMCU-RIBS/Subgrid-Optimization
d1d856fc2fc4d457bebe8315c3dc93c13c4d5e1a, 4 August 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
9 files
- examples/
run_examples.py , Python, 114 lines - examples/
synth_data.py , Python, 346 lines, 2 matches - subopt/
__init__.py , Python, 10 lines - subopt/
base.py , Python, 839 lines, 2 matches - subopt/
exhaustive.py , Python, 314 lines, 3 matches - subopt/
hillclimbing.py , Python, 748 lines, 2 matches - subopt/
utils.py , Python, 122 lines - LICENSE, License, 21 lines
- readme.md, Text, 229 lines
Code Availability
The code describing the subgrid search algorithm (SpatialExhaustiveSearch
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 7 scripts, each with its path and the digest of its content;
- 9 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data Availability
The data that support the findings of this study are not publicly available due to privacy restrictions. However, data can be provided upon reasonable request and subject to legal data transfer agreement.
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 3, 28 September 2026
- Publisher: n/a → Springer Science+Business Media
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 5 keywords, 11 MeSH terms, 2 funders, 56 references.
Cite
This paper
Offenberg, E. C., Keller, D., Mehrkanoon, S., Berezutskaya, J., Vansteensel, M. J., & Branco, M. P. (2026). Optimal Size of Electrocorticography Grids for Classification of Hand Movements. Neuroinformatics, 24(3), 52. https://
BibTeX
@article{offenberg2026op
author = {Offenberg, Elena C and Keller, Dirk and Mehrkanoon, Siamak and Berezutskaya, Julia and Vansteensel, Mariska J and Branco, Mariana P},
title = {{Optimal Size of Electrocorticography Grids for Classification of Hand Movements}},
journal = {Neuroinformatics},
year = {2026},
month = aug,
volume = {24},
number = {3},
pages = {52},
publisher = {Springer Science+Business Media},
issn = {1539-2791},
doi = {10.1007/
url = {https://
pmid = {42563025},
pmcid = {PMC13447543}
}
RIS
TY - JOUR
AU - Offenberg, Elena C
AU - Keller, Dirk
AU - Mehrkanoon, Siamak
AU - Berezutskaya, Julia
AU - Vansteensel, Mariska J
AU - Branco, Mariana P
TI - Optimal Size of Electrocorticography Grids for Classification of Hand Movements
T2 - Neuroinformatics
J2 - Neuroinformatics
PY - 2026
DA - 2026/
VL - 24
IS - 3
SP - 52
SN - 1539-2791
PB - Springer Science+Business Media
DO - 10.1007/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1007/
"type": "article-journal",
"title": "Optimal Size of Electrocorticography Grids for Classification of Hand Movements",
"container-title": "Neuroinformatics",
"author": [
{
"family": "Offenberg",
"given": "Elena C"
},
{
"family": "Keller",
"given": "Dirk"
},
{
"family": "Mehrkanoon",
"given": "Siamak"
},
{
"family": "Berezutskaya",
"given": "Julia"
},
{
"family": "Vansteensel",
"given": "Mariska J"
},
{
"family": "Branco",
"given": "Mariana P"
}
],
"container-title-short":
"volume": "24",
"issue": "3",
"page": "52",
"DOI": "10.1007/
"PMID": "42563025",
"PMCID": "PMC13447543",
"ISSN": "1539-2791",
"publisher": "Springer Science+Business Media",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
7
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41591-026-04414-6 [code]
- Long-term independent use of an intracortical brain-computer interface for speech and cursor control.Journal: Nature medicineIn common: pandas, SciPy, Matplotlib, 1 other tool, 5 references, author Elena C Offenberg
- [2] doi:10.1016/j.neuroimage.2026.121837
- Optimal location for gesture decoding in the sensorimotor cortex and implications for brain-computer interface research.Journal: NeuroImageIn common: 9 references
- [3] doi:10.1038/s41467-026-71284-4 [code]
- Encoding of speech modes and loudness in ventral precentral gyrus.Journal: Nature communicationsIn common: seaborn, scikit-learn, pandas, 3 other tools, 5 references
- [4] doi:10.1038/s41586-026-10331-y [code]
- Active dissociation of intracortical spiking and high gamma activity.Journal: NatureIn common: scikit-learn, pandas, SciPy, 2 other tools, 5 references
- [5] doi:10.1038/s41467-026-75455-1 [code]
- Shared latent representations of speech production for cross-patient speech decoding.Journal: Nature communicationsIn common: seaborn, scikit-learn, pandas, 3 other tools, intracranial EEG (iEEG / ECoG / SEEG), 4 references
- [6] doi:10.1073/pnas.2516293123
- Distinct laminar origins of sensory-evoked high-gamma and low-frequency ECoG signals revealed by optogenetics.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: intracranial EEG (iEEG / ECoG / SEEG), 6 references
- [7] doi:10.1038/s41586-026-10653-x [code]
- A mosaic of whole-body representations on the human precentral gyrus.Journal: NatureIn common: scikit-learn, SciPy, Matplotlib, 1 other tool, 4 references
- [8] doi:10.1126/sciadv.adw3876 [code]
- Intracortical brain-computer interface for navigation in virtual reality in macaque monkeys.Journal: Science advancesIn common: seaborn, scikit-learn, pandas, 3 other tools, 2 references
- [9] doi:10.1038/s41467-026-76109-y [code]
- Assistive algorithms influence neural representations in motor brain-computer interfaces.Journal: Nature communicationsIn common: seaborn, scikit-learn, pandas, 3 other tools, 2 references
- [10] doi:10.1038/s41593-026-02258-4 [code]
- Laminar organization of cellular microcircuits modulating human interictal epileptiform discharges.Journal: Nature neuroscienceIn common: seaborn, scikit-learn, pandas, 3 other tools, epilepsy, 1 reference
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 7 scripts, and 9 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:4376cc3f9e420d47…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
