DocsUsing the ML Suite
BCILattice Documentation

Using the ML Suite

How Pipelines and Workflows fit together — build a reusable pipeline, drop it into a workflow, and run it against real recordings.

v1.0BCINexus Platform · 2026-05-20[email protected]

The Three Steps

The ML Suite sidebar is numbered for a reason — it is a pipeline of its own, and each step consumes what the one before it produced.

  1 · Pipeline                   2 · Workflow                 3 · Execute
  ┌──────────────────┐         ┌──────────────────┐         ┌──────────────────┐
  │ build a REUSABLE │ compile │ wire real DATA   │ compile │ run it, per      │
  │ processing/model │────────▶│ into one or more │────────▶│ subject, and see │
  │ unit             │  block  │ compiled units   │         │ metrics + graphs │
  └──────────────────┘         └──────────────────┘         └──────────────────┘
   no files here                Select Files lives here      results land here

Where everything is

Every path below starts from the main sidebar on the left of the app.

WhatWhere
Build a pipelineML Suite → 1 · Pipeline
Build a workflowML Suite → 2 · Workflow
Run itML Suite → 3 · Execute
Block paletteLeft panel of either canvas
CompileCanvas toolbar, the ⚡ Compile button
Build with AIBottom of the block palette
Import from CommunityBottom of the block palette
New tab / new pipelineThe “+” tab at the end of the tab bar
Rename a tabDouble-click the tab
Block settingsSelect a block — the inspector opens on the right
Load recordings firstLoad Dataset (sidebar), before either canvas
Results and reportsResults, and Experiment Hub for run history

Load your data first

Both canvases read the subject and session lists from the Data Manager. If Select Files shows nothing to pick, go to Load Dataset and import the recordings before returning.

Which canvas do I want?

The split confuses people at first, and the rule is simpler than it looks:

QuestionAnswer
Am I defining what to do to some data?Pipeline
Am I choosing which recordings it runs on?Workflow
Do I want this reusable across several studies?Pipeline — compile it once, drop it in many workflows
Do I just want to run something once, now?Workflow alone is enough
Is my model a single block (KNN, SVM, Random Forest)?Workflow alone — a Pipeline adds nothing
Is it a layer stack, parallel branches, or preprocessing + model?Pipeline — that is what it is for
I already built it — or imported it — on the wrong one.Right-click the tab → Move to…. See Moving a tab to the other canvas.

Start in the Workflow

A Workflow does not require a Pipeline, and for a simple model you should skip the Pipeline canvas entirely: drop Select Files → your model → BCILattice Trainer → Output Block straight onto the Workflow canvas. Four blocks, one compile.

Use a Pipeline for complex structure — a deep-learning layer stack, parallel branches, a preprocessing chain feeding a model — especially when you want that structure as one reusable block across several workflows. Building a one-block model as a Pipeline just adds a second canvas and a second compile for no gain.

1 · Pipeline

A Pipeline is a reusable unit: a named box with named input and output ports. It deliberately has no file picker — it does not know or care which recordings it will see. That is what makes it reusable.

Building a pipeline

1

Mark the inputs — if it takes any

Drop one Input Block for each data stream the pipeline accepts, and set its port_name (for example Signal and Labels). The port drawn on the canvas is labelled with that name, and it is exactly the port you will wire to later — so name them for what they carry, not In_1.

A pipeline that only defines a model needs none. Layer blocks do not take data on the canvas — the trainer feeds them — so skip this step entirely and the compiled block will have no input port to wire.
2

Do the work

Wire your processing between them — Channel Selection, Signal Projection, Time-Frequency Features, a model block, a BCILattice Trainer, whatever the pipeline is for. Anything in the palette is available here.
3

Mark the output

Finish with an Output Block, and set its port_name too. A pipeline needs exactly one final output — see below.
4

Compile

Press Compile. That validates the graph and, on success, publishes it as a single reusable block for the Workflow canvas.

Building a model: the wires are forward

If you know PyTorch, the mapping is one table and everything follows from it:

PyTorchML Suite
__init__the layer blocks you drop
forward(x)the wires between them
xwhatever the trainer feeds in
returnthe last block before the trainer’s Model port
the training loopthe BCILattice Trainer block

Chain layer blocks and they compose into one nn.Module, in the order you wired them. Recurrent layers chain too — LSTM → Flatten → Linear works, even though nn.Sequential cannot express it.

You never wire data into a layer. A layer with nothing feeding it is a root and receives the trainer's input. That is what makes parallel branches simply a matter of drawing them:

  Conv1d(k=3) → ReLU ─┐
  Conv1d(k=5) → ReLU ─┼─▶ Model Concat ─▶ Linear ─▶ Trainer.Model
  Conv1d(k=7) → ReLU ─┘
      each branch sees the SAME input

Model Concat and Model Add (palette: Model Structure) apply every wired Branch N to the same input. Concat joins the outputs; Add sums them, which is how you draw a skip connection — put an Identity layer on the other branch.

These are not the Data Routing blocks

Feature Concatenator, Parallel Fan-out and Ensemble Voter look similar and operate on arrays — they combine data or predictions between models. Model Concat and Model Add operate on models and build structure inside one network.

A model block has no data inputs — leave it that way

KNN, SVM, Random Forest, a Conv1d — none of them take data on the canvas. You drop the block, set its parameters, and wire its output to the trainer's Model port. The BCILattice Trainer is what puts data through it: it owns the train/test split and calls fit itself.

If a model block is showing X and y ports, its Method is set to fit — that block is a constructor and a fit call in one. Either set it back to (none — just build the model) and let the trainer do the fitting, or, if you meant it to fit there, feed those ports with Trainer Trigger blocks: they hand over the training split, so the node never sees the rows it will be scored against.

See what you built

Right-click the canvas → View as PyTorch… prints the real nn.Module — the exact object the trainer receives, generated from the constructed model rather than from a second reading of the graph. Save as .py exports it as a standalone file that imports nothing from BCILattice, so a model built from blocks can always leave as ordinary code.

What compiling checks

Compile is validation, not just a save. It refuses a graph that:

  • has no input node — at least one Input Block is required, unless the pipeline only defines a model, which takes no data;
  • does not have exactly one final output node. Zero is ambiguous, and so is two;
  • contains a cycle — the graph has to have a running order;
  • has no model or trainer node, matched against the catalog by type or category, never by guessing at names.

Blocking errors stop the compile and the offending nodes are marked red on the canvas. Warnings are shown in a dialog and you can choose to proceed.

Compiling and publishing

A successful compile registers the pipeline under its tab name and a stable internal id, and it appears in the Workflow palette under Model/Pipelines (Compiled). Recompile whenever you change the pipeline — a workflow that already embeds it keeps using the last compiled version until you do, which is deliberate: editing a pipeline never silently changes a workflow mid-experiment.

2 · Workflow

A Workflow is where abstract processing meets actual recordings. This canvas has Select Files, and it is the only one that does.

Building a workflow

1

Load the data

Drop Select Files and choose the modality, the processing stage (data_source), the session and the subjects. It emits X and Y, plus stage-specific ports.
2

Add your model

Either drop the model blocks straight onto this canvas, or find your compiled pipeline in Model/Pipelines (Compiled). A compiled pipeline that takes data wires itself to the Select Files already on the canvas when you drop it; one that only defines a model has no input port to wire.
3

Add the trainer — this is what produces metrics

Drop a BCILattice Trainer (or Cross-Validation) and give it two different things: the data on Dataset / Labels from Select Files, and the model on Model. A model on its own never reports anything — the trainer owns the split, the fit and the scoring.
4

Wire the results and finish

Send the trainer's Metrics to an Output Block — every workflow ends in exactly one of those (or a Graph Output / Graph Endpoint). Tap Predictions + True Labels into a Confusion Matrix Graph and Training History into a Data Graph to see the run, not just its number.
5

Compile

Same button, same meaning. A compiled workflow is what the Execute tab can run.
  Select Files ─X──▶ BCILattice Trainer . Dataset
               └Y──▶ BCILattice Trainer . Labels
  your model ──────▶ BCILattice Trainer . Model

  Trainer . Metrics ──────────▶ Output Block
  Trainer . Predictions ──────▶ Confusion Matrix Graph . y_pred
  Trainer . True Labels ──────▶ Confusion Matrix Graph . y_true
  Trainer . Training History ─▶ Data Graph . Data

What compiling checks

The same rules as a pipeline, with a wider set of accepted endpoints: the input may be an Input Block or Select Files, and the single final output may be an Output Block, a Graph Output or a Graph Endpoint.

Only terminal nodes compete for the output slot

Data Graph is a pass-through tap, not a terminal node, so you can use as many as you like without breaking the “exactly one final output” rule. Graph Output and Graph Endpoint are terminal — two of those in one graph is a compile error.

3 · Combining the two

This is the part worth understanding properly, because everything else follows from it.

The port contract

One block, one port

Each Input Block inside a pipeline becomes one input port on the compiled block. Each Output Block becomes one output port. The port takes its name from that block's port_name.

…and no port at all when there is no input

A pipeline that only defines a model consumes no data, so it compiles with no input ports even if you left an Input Block on the canvas. There is nothing to connect and nothing left dangling.

When a pipeline does take data, leaving one of its ports unwired is a compile error that names the port — the run will not silently proceed on whatever data happened to be in scope.

Which trainer port a pipeline block feeds follows from what comes out of it: a model goes to Model, processed data goes to Dataset.

So the shape of the block you drop into a Workflow is decided entirely by how many Input/Output Blocks you put in the pipeline, and what you named them. Two inputs and one output in the pipeline gives you a block with two inputs and one output in the Workflow.

Give every Input Block a distinct name. Two blocks sharing one name compile to two identically named ports, which cannot be told apart when wiring.

A worked example

A preprocessing-plus-decoder pipeline that takes a signal and labels, and returns a trained model.

Pipeline tab — "EEG Decoder"
┌──────────────────────────────────────────────────────────────────┐
│  [Input Block]                                                   │
│   port_name: Signal ──┐                                          │
│                       ├──▶ [Channel Selection] ──▶ [BCILattice   │
│  [Input Block]        │                             Trainer] ──┐ │
│   port_name: Labels ──┘                                        │ │
│                                          [Output Block] ◀──────┘ │
│                                           port_name: Model       │
└──────────────────────────────────────────────────────────────────┘
                              │  Compile
                              ▼
Workflow tab — the same pipeline, now one block
┌──────────────────────────────────────────────────────────────────┐
│  [Select Files]              ┌─────────────────┐                 │
│    modality: EEG             │  EEG Decoder    │                 │
│    X ───────────────────────▶│ Signal    Model │──▶ [Output      │
│    Y ───────────────────────▶│ Labels          │      Block]     │
│                              └─────────────────┘                 │
└──────────────────────────────────────────────────────────────────┘
        Signal / Labels / Model are the port_names you set above.

Note what did not happen: the pipeline never mentioned EEG, a session, or a subject. Point a second Select Files at fNIRS recordings and the same compiled block runs against those instead.

Moving a tab to the other canvas

You imported someone's published work, it opened in the Workflow tab, and it belongs in the Pipeline one — or the reverse. Right-click the tab and choose Move to Pipeline… or Move to Workflow….

This is not a rename. The two canvases carry different palettes and enforce different rules, so some blocks change shape and some cannot come with you. Nothing happens until a report has shown you every block, grouped by what happens to it, with the reason attached:

BlockWhat happens
Select Files → PipelineBecomes one Input Block per port you actually wired, so every connection survives. The subject scope and cohort filters come with it; the chosen files do not — a pipeline is fed by whatever runs it.
Input Block → WorkflowMoves as it is — a workflow called by Sub-Workflow is legitimately fed by its caller. No Select Files is added for you; add one yourself before running it from Execute.
Blocks the other palette does not haveRemoved, each one named with why. Going to a Pipeline that means Model Set, Sub-Workflow and any compiled pipeline; going to a Workflow it means the Pipeline-only families — BCI Decoding, Riemannian Geometry, BCI Augmentation, BCI Cross Validation, BCI Epoching.
Everything elseMoves unchanged.

It will drop things, but it will never invent them

Converting a workflow discards its file selection, because a pipeline has no file picker. Converting a pipeline the other way does not hand you a Select Files in return — nothing in a pipeline says which recordings to use, and a guessed cohort would give you a result rather than an error. You are told to add one instead.

The source tab closes on a clean move. The moment anything has to be dropped, Keep the original open comes up already ticked, so a lossy conversion never costs you the work you started from.

If a lot is being dropped, you probably want a compiled pipeline instead

A pipeline full of CSP, Covariances or Tangent Space blocks converts badly, because those are exactly the blocks the Workflow palette does not carry — they are the reason pipelines exist. Press Compile and drag the pipeline into the workflow as a single block instead: every block inside it stays intact. The report says so when it applies.

Saving, renaming, reopening

  • Saving a workflow writes a .workflow file and co-saves every pipeline it references as a .pipeline file in the same folder, so the pair travels together. Keep the referenced pipelines open in the Pipeline tab when you save, or they cannot be bundled.
  • Loading a workflow reads those .pipeline files back, recompiles them, and marks any block red whose pipeline file is missing or failed to compile.
  • References are stored by stable id, not by name, so renaming a pipeline does not break a workflow that embeds it.
  • Both canvases are saved into the study itself, so reopening a study restores every pipeline and workflow tab as you left them.

4 · Execute

The Execute tab lists compiled workflows for the active study. Pick one, pick your subjects, and run. Metrics, training history, confusion matrices and any graphs your blocks produced land in the results tabs.

Per-subject vs pooled

By default a run fans out one job per subject — each subject is trained and evaluated independently, which is usually what you want for a within-subject BCI result.

Cross-subject validation needs pooled mode

Because a normal run only ever sees one subject at a time, leave-one-subject-out has nothing to hold out. Set an Input Block's subject_scope to all to collapse every selected subject into a single run — then a subject-wise split or LOSO strategy has real subject groups to work with.

Free vs Researcher

One thing in the ML Suite depends on your plan, and it is worth being precise about what it is — because it gates a shortcut, not a capability.

Importing an Analysis stage

Select Files's data_source lets you train directly on a stage the Analysis suite already produced — channel-selected data, a dimensionality reduction, an extracted feature table, a time-frequency transform, a signal projection. That reuse is a Researcher feature.

data_sourcePlan
FilteredFree — the band-passed recording, the one pre-analysis entry
ICA Cleaned DataResearcher
Quality Checked DataResearcher
EpochsResearcher
Normalized DataResearcher
Channel Selected DataResearcher
Dim Reduced DataResearcher
FeaturesResearcher
Time-Frequency (ERSP)Researcher
Signal Projection (images)Researcher

On a Free plan the locked entries are refused when you pick one, and Compile also refuses a graph that reached a locked source another way — by loading a file, importing from the community, or generating with AI.

Rebuilding it yourself

Nothing is out of reach on Free

Every gated stage has a block that produces the same thing, grouped under Analysis (BCILattice) in the palette. Start from Filtered and wire the processing you need — the result is the same pipeline, built explicitly instead of inherited. Pipeline Recipes walks through each one with the exact ports and settings.
Instead of importing……wire this block
Channel Selected DataChannel Selection
Signal Projection (images)Signal Projection
Time-Frequency (ERSP)Time-Frequency Features
Dim Reduced DataPCA · FastICA · KernelPCA · TruncatedSVD
FeaturesSelectKBest · SelectPercentile · VarianceThreshold · RFE

Building it in the canvas has a real advantage too: the processing is part of the pipeline, so it travels with it, recompiles with it, and is visible to anyone reading the graph — rather than being an out-of-band step someone has to know was run.

Faster starts

Three ways to avoid starting from a blank canvas. All three produce an ordinary editable graph — nothing they generate is locked, and nothing runs until you compile it yourself.

Build with AI

Where: the Build with AI button at the bottom of the block palette, on either canvas.

Describe what you want in plain language and it lays out a starting graph for the canvas you are on. It is catalog-aware: your description is matched against the live block catalog and the most relevant blocks are given to the model, so it builds from blocks that actually exist in your installation rather than inventing plausible names.

1

Pick a provider and model

Gemini, ChatGPT, Claude, Mistral or DeepSeek. Only providers you have signed into are listed — add an account under AI Chat first. Your own API key is used, and the request goes directly to that provider.
2

Describe the pipeline

Be concrete about signal type, processing and goal. “Bandpass EEG 8–30 Hz, select the best channels, train a classifier for left vs right imagery” gives a far better skeleton than “make me a BCI”.
3

Review before you trust it

The result loads into a new tab as a draft. Check every block is right for your case, set the parameters (frequencies, window sizes, model settings), and fix any ports it left unconnected.

It produces a draft, not a finished pipeline

The model places blocks and guesses connections. It does not know your sampling rate, your montage or your paradigm, and it does not validate the graph — Compile does. Treat the output as a starting skeleton you review, exactly as the dialog warns.

Import from Community

Where: the Import from Community button, directly below Build with AI.

Browses published community pipelines and workflows and imports one into your study, as a new tab or merged into the current canvas. Useful when someone has already solved the preprocessing you need. The same review applies — an imported graph is a starting point, and its parameters were tuned for someone else's recordings.

Standard Preprocessing starter

When you create a new Workflow tab from the + tab, you are asked whether to start Blank or from Standard Preprocessing — the bundled community EEG preprocessing pipeline. Pick Blank when you know what you are building; pick Standard Preprocessing when you want a known-good chain to adapt.

Troubleshooting

MessageWhat to do
Compilation failed: no input nodeAdd an Input Block (Pipeline) or a Select Files block (Workflow).
Exactly one final output node requiredYou have zero or two terminal nodes. Data Graph does not count — Output Block, Graph Output and Graph Endpoint do.
No model or trainer nodeA pipeline has to actually train or apply something. Add a model block or a BCILattice Trainer.
Compiled pipeline not foundThe pipeline was never compiled, or was compiled in a different study. Open its Pipeline tab and press Compile.
Pipeline file not found on loadThe .pipeline file was not next to the .workflow file. Reopen both pipelines and re-save the workflow to bundle them.
Strategy needs at least 2 distinct groupsCross-subject validation on single-subject data. Select more subjects and set subject_scope to all.
A graph did not drawRead the Graph Error port on Data Graph / Graph Output — it carries the exact reason.
This workflow has no Select Files blockShown after moving a pipeline to the Workflow canvas. Nothing in a pipeline says which recordings to use, so none is added for you — drop a Select Files in and wire it to the Input Blocks before running from Execute.
N block(s) end up with no connections at allShown before a move: those blocks fed off something the other palette does not have. Convert anyway and rewire them, or compile the pipeline and embed it as one block instead.

Block-by-block reference: Custom Blocks for the everyday palette, Advanced Blocks for control flow, domain adaptation, continual learning and training primitives, and Extensions for turning a package we did not bundle — Cedalion, or anything on the curated shelf — into blocks on this same canvas.

Using the ML Suite v1.0 · BCINexus Platform · 2026-05-20