> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nimbusbci.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Applications

> Advanced Nimbus BCI patterns for Personalizer personalization, cross-subject training, hybrid systems, adaptive learning, multi-session experiments, and robust deployment.

Advanced BCI patterns without duplicating full SDK walkthroughs. Use this as an implementation checklist, then follow the linked guides for exact APIs.

<Note>
  Frozen-encoder personalization: [Personalizer](/personalizer/overview). Python streaming / sklearn: [Python Streaming Inference](/python-sdk/streaming-inference), [sklearn Integration](/python-sdk/sklearn-integration). Julia streaming: [Julia Streaming Inference](/julia-sdk/streaming-inference).
</Note>

<Tip>
  Under deployment stress on a frozen trunk, prefer `Personalizer` + small labeled calibration over full fine-tuning. Classical cross-subject / multi-session recipes still apply to feature pipelines and standalone heads.
</Tip>

## Cross-subject training

Combine calibration data from multiple users, then personalize to a new user with a small labeled set. When a frozen trunk already transfers, prefer [`Personalizer`](/personalizer/overview) + per-user calibration over full fine-tuning.

Core workflow:

1. Extract the same feature type (or freeze the same encoder) for every subject.
2. Normalize using training subjects only.
3. Train a conservative baseline (`Personalizer.for_features(..., head="lda")` or `NimbusLDA`).
4. Evaluate with subject-wise splits, not random trial splits.
5. Personalize with a small calibration set for the target subject.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from nimbus_bci import Personalizer, wrap, estimate_normalization_params, apply_normalization
    from sklearn.model_selection import LeaveOneGroupOut

    # Frozen trunk path — same encoder for all subjects
    enc = wrap(model.encode, model_id="shared-trunk", embedding_dim=64)

    X, y, groups = load_multi_subject_features()  # or embeddings from enc
    logo = LeaveOneGroupOut()

    for train_idx, test_idx in logo.split(X, y, groups):
        norm = estimate_normalization_params(X[train_idx], method="zscore")
        X_train = apply_normalization(X[train_idx], norm)
        X_test = apply_normalization(X[test_idx], norm)

        adapter = Personalizer(
            encoder=None,  # features/embeddings already prepared
            head="lda",
            classes=["left", "right"],
            preset="research",
        )
        adapter.fit(X_train, y[train_idx])
        states = adapter.predict(X_test)
        # score from labels when available; gate on states[*].confidence in apps
    ```
  </Tab>

  <Tab title="Julia">
    ```julia theme={null}
    using NimbusSDK

    features, labels, subject_ids = load_multi_subject_features()

    for subject in unique(subject_ids)
        train_mask = subject_ids .!= subject
        test_mask = subject_ids .== subject

        norm = estimate_normalization_params(features[:, :, train_mask]; method=:zscore)
        train_features = apply_normalization(features[:, :, train_mask], norm)
        test_features = apply_normalization(features[:, :, test_mask], norm)

        train_data = BCIData(train_features, metadata, labels[train_mask])
        model = train_model(NimbusLDA, train_data; iterations=50)
        results = predict_batch(model, BCIData(test_features, metadata, labels[test_mask]))
    end
    ```
  </Tab>
</Tabs>

## Hybrid BCI

Hybrid BCIs combine evidence from multiple paradigms, such as motor imagery plus P300.

Recommended pattern:

* Keep one Personalizer / head per paradigm (or one frozen trunk + head).
* Normalize each feature family separately.
* Combine posterior probabilities or confidence-weighted decisions.
* Log disagreement between paradigms for later review.

```python theme={null}
mi_states = mi_adapter.predict(mi_features)
p300_states = p300_adapter.predict(p300_features)

# Or combine head posteriors for research fusion
mi_proba = mi_adapter.head.predict_proba(mi_features)
p300_proba = p300_adapter.head.predict_proba(p300_features)
combined = 0.6 * mi_proba + 0.4 * p300_proba
```

Use conservative thresholds for high-stakes actions and require confirmation when paradigms disagree. App gates: [BrainState](/personalizer/brain-state).

## Continuous control

Continuous control maps repeated predictions into a smoothed command stream.

```python theme={null}
alpha = 0.3
smoothed = None

for state in brain_state_stream:  # or posterior_stream from a head
    command = posterior_to_velocity(state.posterior)
    smoothed = command if smoothed is None else alpha * command + (1 - alpha) * smoothed
    if not state.rejected:
        send_control(smoothed)
```

Keep smoothing outside the classifier. The model reports uncertainty; the control layer decides how aggressively to act.

## Adaptive learning

Adaptation is useful when signal distributions drift during long sessions or across days.

| Signal | Pattern |
| - | - |
| Which trials to label | [Active Learning](/python-sdk/active-learning) / `CalibrationSession` |
| Whether to stream-update | `Personalizer.recommend_adapt(..., tau=...)` then `partial_fit` |
| Trial accept / reject | `BrainState.rejected` / `need_more_data` |
| Non-stationary state | `NimbusSTS` / `StreamingSessionSTS` |

```python theme={null}
dec = adapter.recommend_adapt(X_stream, tau=your_tau)
if dec.should_adapt:
    adapter.partial_fit(X_lab, y_lab)

states = adapter.predict(X_eval)
# optional: state.extras.update(dec.to_extras())
```

See [Personalizer overview](/personalizer/overview) for adapt vs trial gates, and [Active Learning](/python-sdk/active-learning) for label selection.

## Multi-session experiments

For experiments that span days or weeks:

* Use a stable preprocessing / encoder pipeline and save its parameters (`model_id`, `embedding_dim`).
* Save profiles / normalization with each subject (`Personalizer.save`, `export_profile()`, head artifacts).
* Track hardware, montage, impedance, session time, and participant state.
* Evaluate same-session and cross-session performance separately.
* Compare adaptation against a no-adaptation baseline.

Suggested metadata to log:

```json theme={null}
{
  "subject_id": "S001",
  "session_id": "2026-04-25-AM",
  "paradigm": "motor_imagery",
  "model_id": "partner-encoder",
  "head": "lda",
  "feature_type": "embedding",
  "normalization": "zscore",
  "sampling_rate": 250
}
```

## Robust deployment

Production systems should separate personalization, control, and safety policy:

1. Personalizer returns `BrainState` (intent, posterior, confidence, gates).
2. Quality gate accepts, rejects, or asks for more data (`need_more_data`).
3. Control layer maps accepted intents to actions.
4. Monitoring logs confidence, latency, rejection rate, and drift.

```python theme={null}
for state in adapter.predict(X_online):
    if state.rejected or state.need_more_data:
        reject_or_ask_more(state)
    elif state.confidence >= 0.9:
        execute(state.intent)
    elif state.confidence >= 0.7:
        request_confirmation(state.intent)
    else:
        reject_or_ask_more(state)
```

For detailed safeguards, see [Error Handling](/inference-configuration/error-handling) and [BrainState](/personalizer/brain-state).

## Next read

<Columns cols={2}>
  <Card title="Personalizer" icon="layers" href="/personalizer/overview">
    Encoder contract, adapt helper, BrainState
  </Card>

  <Card title="Basic Examples" icon="play" href="/examples/basic-examples">
    Compact starter recipes
  </Card>

  <Card title="Active Learning" icon="target" href="/python-sdk/active-learning">
    Label-efficient calibration
  </Card>

  <Card title="Streaming configuration" icon="activity" href="/inference-configuration/streaming-inference">
    Chunking and aggregation decisions
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.