Skip to content

Add Python DataFilter.rereference_channels for EEG/EXG - #870

Open
OgarkovAnton wants to merge 5 commits into
brainflow-dev:masterfrom
OgarkovAnton:feature/python-rereference-channels
Open

OgarkovAnton wants to merge 5 commits into
brainflow-dev:masterfrom
OgarkovAnton:feature/python-rereference-channels

Conversation

@OgarkovAnton

Copy link
Copy Markdown

Implements #619 with a Python-only helper that subtracts the mean of selected reference channels from non-excluded channels, plus unit tests and CI coverage.

Implements brainflow-dev#619 with a Python-only helper that subtracts the mean of
selected reference channels from non-excluded channels, plus unit tests
and CI coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Andrey1994
Andrey1994 self-requested a review November 2, 2026 23:05

@Andrey1994 Andrey1994 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi and thanks!

https://brainflow.readthedocs.io/en/stable/BrainFlowDev.html should be a good starting point about how c++ methods are called from other languages in brainflow, this should follow the same pattern instead of implementing it in python directly

Comment thread python_package/brainflow/data_filter.py Outdated
:type excluded_channels: list or tuple or ndarray or None
:return: new 2d array with the same shape as ``data``
:rtype: NDArray[Shape["*, *"], Float64]
:raises ValueError: if ``data`` is not a non-empty 2d array, ``reference`` is empty,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of implementing it in the python layer directly it should follow the workflow as for other methods(should be done in c++ and other languages should call it from there)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in c++

Move channel re-referencing from a Python-only helper into the native
DataHandler C API with an in-place channels/reference_channels API, then
expose it through C++ and the other language bindings per review on brainflow-dev#619.

Co-authored-by: Cursor <cursoragent@cursor.com>

@Andrey1994 Andrey1994 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed f1957c1.

The earlier architectural feedback is addressed: rereferencing now lives in native C++, with wrappers across all nine language bindings. There are still correctness issues in Python channel-index handling and a Node/native validation mismatch that should be fixed before merging. Inline comments include reproductions and suggested fixes, plus smaller Java validation and documentation corrections.

Validation: the native/C++ build and signal-processing regression passed; all 10 new Python rereferencing tests and 17 existing binding-validation tests passed; TypeScript compiled. Additional runtime checks reproduced the two Python issues and the Node discrepancy below. Please add regression coverage for these cases, since the existing tests pass despite them.

if channels_arr.ndim != 1 or reference_arr.ndim != 1:
raise BrainFlowError('channel index arrays must be 1d',
BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value)
res = DataHandlerDLL.get_instance().rereference_channels(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Make both channel-index buffers contiguous before the native call

numpy.asarray(..., dtype=numpy.int32) preserves strides when its input is already an int32 array, while the native function reads contiguous int* buffers. Reproduced with channels = np.arange(4, dtype=np.int32)[::2]: the caller selects [0, 2], but rereference_channels(data, channels, [3]) modifies rows 0 and 1. Using the same sliced array as reference_channels also computes the wrong reference. Normalize both arrays with numpy.ascontiguousarray after validating them, and enforce contiguous channel buffers in the ctypes declarations. Please test sliced target and reference arrays.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread python_package/brainflow/data_filter.py Outdated
if data.dtype != numpy.float64 or not data.flags.writeable:
raise BrainFlowError('data must be a writable float64 array',
BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value)
channels_arr = numpy.asarray(channels, dtype=numpy.int32)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Validate channel types and bounds before narrowing to int32

Casting first destroys the information needed to reject invalid indices. Reproduced: [0.9] and [-0.5] both become row 0; np.array([4294967296], dtype=np.int64) wraps to row 0 as well. All three calls succeed and modify data instead of raising INVALID_ARGUMENTS_ERROR. Validate that both selections contain integers within the original array's row bounds before converting them to contiguous int32 buffers. Add regression tests for fractional, negative fractional, and overflowing integer indices.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread nodejs_package/brainflow/data_filter.ts Outdated
public static rereferenceChannels(
data: number[][], channels: number[], referenceChannels: number[]): void
{
const columns = DataFilter.checkMatrix(data, true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Restrict finiteness validation to target and reference channels

checkMatrix(data, true) rejects NaNs in every row, including rows that are neither updated nor used as references. For example, data = [[1, 2], [3, 4], [NaN, NaN]], channels = [0], and referenceChannels = [1] throws here, whereas the native implementation and Python correctly produce [-2, -2] in row 0 and preserve the unrelated NaN row. Keep the matrix shape checks, but only require finite values in the selected target/reference rows, as the two checkChannels calls already do. Add a test with a non-finite unselected channel.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

throw new BrainFlowError ("invalid args for rereference_channels",
BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ());
}
int cols = data[0].length;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Check the first row for null before reading its length

data[0].length is evaluated before the loop's null-row validation. An input whose first row is null therefore throws NullPointerException, while null rows later in the matrix produce the intended BrainFlowError. Check data[0] before this dereference so malformed matrices consistently return INVALID_ARGUMENTS_ERROR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread docs/UserAPI.rst Outdated

``DataFilter.rereference_channels`` subtracts the element-wise mean of
``reference_channels`` from the selected ``channels`` of a ``(channels, samples)``
array in place. Channel indices are zero-based. Overlap between the two index lists

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Document the MATLAB and Julia indexing and mutation conventions

This shared API description says channel indices are zero-based, but the MATLAB and Julia wrappers accept one-based indices and subtract 1 before calling native code. Please make that distinction explicit so users do not select the wrong channels or get errors when following the documentation. MATLAB also returns the updated matrix, so callers need to assign the result rather than relying on the blanket in-place wording.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants