Skip to main content

Get Started with Nimbus BCI

Build your first brain-computer interface application in just a few steps. This guide will take you from setup to running inference on neural data in under 10 minutes.
Choose Your SDK:This page covers the Julia SDK (NimbusSDK.jl). If you’re using Python, see the Python SDK Quickstart instead.
  • 🐍 Python SDK: sklearn-compatible, works with MNE-Python, fully local → Python Quickstart
  • Julia SDK: RxInfer.jl-based, maximum performance, requires API key → Continue below

Prerequisites

Before you begin, make sure you have:
  • Julia 1.9+ installed on your system
  • Basic understanding of EEG data and BCI concepts
  • Preprocessed features (CSP, bandpower, etc.) - see preprocessing requirements
Important: NimbusSDK.jl expects preprocessed features, not raw EEG data. You must apply bandpass filtering, artifact removal, and feature extraction (e.g., CSP) before using the SDK.

Step 1: Get Your API Key

Request API Access

Contact us at [email protected] to request your API key. Include your use case and expected volume.
API keys are typically issued within 24 hours. Include details about your BCI application, expected data volume, and timeline in your request.

Step 2: Install NimbusSDK.jl

NimbusSDK.jl is now available in the public Julia General Registry:
using Pkg

# Install the public wrapper (no private registry needed!)
Pkg.add("NimbusSDK")
After installing the wrapper, install the proprietary core with your API key:
using NimbusSDK

# One-time setup: Install the commercial core
NimbusSDK.install_core("your-api-key-here")
What changed? NimbusSDK.jl is now a public wrapper package. No more private registry setup! Just Pkg.add("NimbusSDK") and then install the core with your API key.
The core installation is persistent. You only need to run install_core() once per machine. After that, simply using NimbusSDK will work in any Julia project.

Step 3: Your First BCI Inference

Create a new Julia script and run your first BCI inference:
using NimbusSDK

# Step 1: One-time setup with your API key
NimbusSDK.install_core("your-api-key-here")

# Step 2: Load a pre-trained RxLDA model
model = load_model(RxLDAModel, "motor_imagery_4class_v1")
println("✓ Model loaded: $(get_n_features(model)) features, $(get_n_classes(model)) classes")

# Step 3: Prepare your preprocessed CSP features
# Shape: (n_features × n_samples × n_trials)
# Example: 16 CSP features × 250 samples × 20 trials
n_features = 16
n_samples = 250  # 1 second at 250 Hz
n_trials = 20

# Generate sample CSP features (in practice, load from your preprocessing)
features = randn(n_features, n_samples, n_trials)
labels = rand(1:4, n_trials)  # Random labels for demo

# Create BCIData with metadata
metadata = BCIMetadata(
    sampling_rate = 250.0,
    paradigm = :motor_imagery,
    feature_type = :csp,
    n_features = n_features,
    n_classes = 4,
    chunk_size = nothing  # Batch mode
)

data = BCIData(features, metadata, labels)

# Step 4: Run batch inference
println("Running batch inference...")
results = predict_batch(model, data; iterations=10)

# Step 5: Analyze results
println("\n✓ Results:")
println("  Mean confidence: $(round(mean(results.confidences), digits=3))")
println("  Accuracy: $(round(100 * sum(results.predictions .== labels) / length(labels), digits=1))%")
println("  ITR: $(round(calculate_ITR(
    sum(results.predictions .== labels) / length(labels),
    4,
    4.0
), digits=1)) bits/minute")

Required Format for Features

NimbusSDK expects features in a specific format:
  • Dimensions: (n_features × n_samples × n_trials)
  • Feature types: CSP (recommended), bandpower, ERP amplitudes
  • Preprocessing: Must be applied before using SDK
Critical: You must preprocess your EEG data before using NimbusSDK. The SDK does NOT process raw EEG - it requires extracted features. See Preprocessing Guide for details.

Step 4: Training Your Own Model

Train a custom RxLDA model on your labeled data:
using NimbusSDK

# One-time setup: Install core with your API key
NimbusSDK.install_core("your-api-key")

# Prepare training data (features already preprocessed!)
train_features = load_your_csp_features()  # (n_features × n_samples × n_trials)
train_labels = load_your_labels()         # Vector of integers: 1, 2, 3, 4, ...

# IMPORTANT: Normalize features for cross-session performance
# This is critical if you plan to use the model on new sessions
norm_params = estimate_normalization_params(train_features; method=:zscore)
train_features_norm = apply_normalization(train_features, norm_params)

train_data = BCIData(
    train_features_norm,  # Use normalized features
    BCIMetadata(
        sampling_rate = 250.0,
        paradigm = :motor_imagery,
        feature_type = :csp,
        n_features = 16,
        n_classes = 4,
        chunk_size = nothing,  # Batch mode
        temporal_aggregation = :logvar  # For CSP features in MI
    ),
    train_labels  # Required for training!
)

# Train the model
println("Training RxLDA model...")
model = train_model(
    RxLDAModel,
    train_data;
    iterations = 50,        # Number of inference iterations
    showprogress = true,    # Show training progress
    name = "my_custom_model",
    description = "4-class motor imagery with CSP"
)

# Save the trained model WITH normalization parameters
using JLD2
@save "my_custom_model.jld2" model norm_params

# Later: Load and use with same normalization
@load "my_custom_model.jld2" model norm_params
test_features_norm = apply_normalization(test_features, norm_params)
test_data = BCIData(test_features_norm, metadata)
results = predict_batch(model, test_data)
Feature Normalization is Critical!EEG amplitude varies 50-200% across sessions. Without normalization, accuracy drops 15-30% for cross-session BCI.Always:
  1. Estimate normalization params from training data
  2. Apply same params to test/deployment data
  3. Save params with your model
See Feature Normalization for details.
New in v0.2.0: You can now fine-tune model hyperparameters (dof_offset, mean_prior_precision) to optimize performance for your specific dataset. See the model documentation for detailed tuning guidance.

Step 5: Streaming Inference

For real-time applications, use streaming inference:
using NimbusSDK

# One-time setup: Install core with your API key
NimbusSDK.install_core("your-api-key")

# Load model (core is already installed)
model = load_model(RxLDAModel, "motor_imagery_4class_v1")

# Initialize streaming session (1 second chunks at 250 Hz)
metadata = BCIMetadata(
    sampling_rate = 250.0,
    paradigm = :motor_imagery,
    feature_type = :csp,
    n_features = 16,
    n_classes = 4,
    chunk_size = 250,  # 1 second chunks
    temporal_aggregation = :logvar  # For CSP features in MI
)

session = init_streaming(model, metadata)

# Process chunks as they arrive
for trial in your_eeg_stream()
    # Process one chunk
    chunk_result = process_chunk(session, trial)
    
    println("Chunk prediction: $(chunk_result.prediction) " *
            "(confidence: $(round(chunk_result.confidence, digits=3)))")
    
    # Finalize trial with weighted voting across all chunks
    if is_trial_complete(trial)
        final_result = finalize_trial(session; method=:weighted_vote)
        println("✓ Final prediction: $(final_result.prediction)")
        
        # Check quality
        if should_reject_trial(final_result.confidence, 0.7)
            println("⚠️ Low confidence - trial rejected")
        end
    end
end

Available Models

NimbusSDK includes three Bayesian inference models:

Bayesian LDA (RxLDA)

API Name: RxLDAModel
Mathematical Model: Pooled Gaussian Classifier (PGC)
  • Shared covariance across classes
  • Fast inference and training
  • Best for well-separated classes
  • Typical training time: 10-30 seconds (50 iterations, 100 trials)

Bayesian GMM (RxGMM)

API Name: RxGMMModel
Mathematical Model: Heteroscedastic Gaussian Classifier (HGC)
  • Class-specific covariances
  • More flexible, handles overlapping distributions
  • Better for complex class structures
  • Slightly slower than Bayesian LDA

Bayesian MPR (RxPolya)

API Name: RxPolyaModel
Mathematical Model: Bayesian Multinomial Probit Regression
  • Continuous transition mapping to latent space
  • Most flexible for complex multinomial tasks
  • Works in (K-1) dimensional space
  • Ideal for advanced BCI applications
All models use RxInfer.jl for efficient variational Bayesian inference with reactive message passing.

Next Steps

Now that you have NimbusSDK running, explore these advanced features:

Common Use Cases

Troubleshooting

API Key Issues:
  • Ensure your API key is set correctly
  • Contact [email protected] if your key isn’t working
Data Format Issues:
  • Features must be in shape: (n_features × n_samples × n_trials)
  • Ensure you’ve applied preprocessing (filtering, artifact removal, feature extraction)
  • No NaN or Inf values in data
Model Loading Issues:
  • Check that you have a valid model name
  • Ensure your internet connection is working (for pre-trained models)
  • Verify API key is authenticated
Need assistance? We’re here to help:
  • Email: [email protected] for technical support
  • Documentation: Browse our comprehensive guides
  • Examples: Check out working code samples in the SDK
  • API Documentation: See Julia SDK Reference
When reporting issues, please include:
  • Your Julia version and OS
  • The exact error message
  • A minimal code example that reproduces the issue
  • Your data shape and preprocessing steps

Performance Tips

Optimize for Production:
  • Use batch inference for multiple trials (more efficient than streaming)
  • Implement proper error handling with try-catch blocks
  • Monitor API usage and quotas
  • Cache model information locally after loading
  • Use appropriate confidence thresholds for your application
  • Check preprocessing quality with diagnose_preprocessing()
Congratulations! 🎉 You’ve successfully set up NimbusSDK.jl and run your first brain-computer interface inference. You’re now ready to build powerful BCI applications with sub-20ms latency and production-ready Bayesian inference.