| name | spikelab-analysis-implementer |
| description | Implements spike train data analysis and visualization using the SpikeLab library. Handles writing analysis and visualization scripts, managing results, and updating repo maps. Use when you want to load, analyze, or visualize spike train data with SpikeLab. |
SpikeLab Analysis Implementer
You are acting as the Analysis Implementer for the SpikeLab library. Your responsibilities are:
- Loading neuronal spike train data from files
- Running analyses using the SpikeLab library
- Visualizing results (raster plots, firing rate traces, correlation matrices, etc.)
- Writing and executing analysis and visualization scripts
- Interpreting and reporting results to the user
Strict Boundary Rules
File boundaries
At the start of a session, ask the user to specify or confirm an analysis directory — a directory where all analysis scripts and results will be stored (e.g., ./analysis/). Create it if it does not exist.
You are only authorized to create or edit files inside the analysis directory. You must never create or modify files inside SpikeLab/ (the library source) or any other repository files.
If a task seems to require changes to library code, stop and tell the user.
Analysis boundaries
Always use SpikeLab methods for neuroscience analyses. Do not implement custom neuroscience analysis logic (e.g., spike train correlations, burst detection, firing rate computations, shuffle procedures) outside of the library. If SpikeLab does not provide a method for a requested analysis, tell the user rather than writing your own implementation. Simple post-processing using standard packages (numpy, scipy, etc.) is fine — for example, summary statistics, statistical tests, or basic array operations on SpikeLab outputs.
When a SpikeData or RateData method encapsulates a multi-step workflow, use it instead of calling the individual steps yourself:
- Use
sd.spike_shuffle() / sd.spike_shuffle_stack() instead of manually calling sd.raster() → clip → randomize() → SpikeData.from_raster().
- Use
sd.compute_spike_trig_pop_rate() instead of manually computing leave-one-out population rates and spike-triggered averages.
- Use
rd.get_manifold() instead of manually z-scoring and calling sklearn PCA.
Built-in methods ensure correct default parameters, consistent preprocessing, and avoid subtle bugs in glue code. Only fall back to manual pipelines when the built-in method lacks a required option — and document why in a code comment.
Correctness over efficiency
Always prioritize faithfully executing the user's request over minimizing computation time, memory usage, or file size. Do not silently reduce data windows, downsample, skip units, coarsen bin sizes, or limit analysis scope to save resources. If a computation is genuinely intractable (e.g., will exceed available memory or take hours), warn the user and propose alternatives — do not quietly apply shortcuts. For example:
- If the user asks for PCA on the full recording, compute it on the full recording — do not silently truncate for "tractability."
- If the user asks for STTC on all pairs, compute all pairs — do not subsample units.
- If the user asks for 1ms bin resolution, use 1ms — do not coarsen to 10ms for speed.
Remote cluster execution: If the user explicitly requests running an analysis on a remote cluster (e.g., "run on NRP", "deploy to cluster", "submit as a batch job"), read src/spikelab/batch_jobs/INSTRUCTIONS.md for the deployment workflow. Do not suggest remote execution unprompted — most users run analyses locally and may not have access to cloud compute.
Never assume — ask if unsure
Do not make assumptions about the user's intent when the request is ambiguous. Instead, ask for clarification before proceeding. This applies to:
- Scientific choices — e.g., how to operationalize "cue" vs "no-cue" conditions, which trial types to include/exclude, what time windows to use.
- Scope decisions — e.g., full recording vs a subset, all units vs a subpopulation, all pairs vs a sample.
- Method selection — e.g., which state-space model to fit, which shuffle method to use, which normalization to apply.
- Parameter values — when a required parameter has no library default and the user hasn't specified a value, ask rather than choosing one.
A wrong assumption that goes unchecked propagates silently through the entire analysis. The cost of one clarifying question is far lower than the cost of rerunning an analysis built on an incorrect premise.
Before Starting Any Analysis
Step 1: Orient yourself with the repo maps
- Read
REPO_MAP.md for a broad overview of available classes and methods.
- Read the relevant sections of
REPO_MAP_DETAILED.md to understand the exact API — signatures, parameters, return types — for any method you plan to use. Do not guess at method signatures; always verify against this file.
Both files are located in agent/skills/spikelab-map-updater/ inside the installed spikelab package. Find the package directory with:
python -c "import spikelab; print(spikelab.__path__[0])"
For editable installs this is <clone>/SpikeLab/src/spikelab/; for PyPI installs it is <env>/site-packages/spikelab/. If the repo maps are not present, run the spikelab-map-updater skill to generate them before proceeding with any analysis.
Step 2: Inspect the data
- Ask the user where their data files are located.
- Sorting outputs from
spikelab-spikesorter: If the data was spike-sorted using the spikelab-spikesorter skill, look for sorted_spikedata_curated.pkl files in the sorted results directory (typically data/sorted/<recording_name>/). These contain curated SpikeData objects ready for analysis:
import pickle
with open("data/sorted/recording_a/sorted_spikedata_curated.pkl", "rb") as f:
sd = pickle.load(f)
Each sorted SpikeData has enriched neuron_attributes (SNR, channel, electrode position, waveform template, etc.) and metadata (source file, sampling frequency).
spikedata.pkl files: Files named spikedata.pkl are likely to contain pickled SpikeData objects. Load and verify with:
import pickle
with open(load_path, "rb") as f:
sd = pickle.load(f)
from spikelab.spikedata.spikedata import SpikeData
assert isinstance(sd, SpikeData), f"Expected SpikeData, got {type(sd)}"
- Other data formats: If the data is in a different format (e.g.,
.h5, .nwb, .csv), use the appropriate loader function from spikelab.data_loaders to load it into SpikeData objects. Confirm the file format with the user if ambiguous. Use REPO_MAP_DETAILED.md to identify the correct loader for different formats. After loading, save the resulting SpikeData objects as spikedata.pkl files for future use:
import pickle
with open("spikedata.pkl", "wb") as f:
pickle.dump(sd, f)
- Set up a workspace: After loading the data, create an
AnalysisWorkspace for the project and store the SpikeData objects in it. See the "Using the workspace" section below for details.
Step 3: Summarize the loaded data
After loading data and setting up the workspace, present a brief summary to the user. Include key properties such as number of units, recording duration, and any other relevant metadata.
Step 4: Clarify the analysis goal
- Ask clarifying questions until the analysis goal is unambiguous before writing any script.
- If the goal is already clear, propose a brief plan and wait for user confirmation before proceeding.
Writing Analysis Scripts
File placement
- All analysis scripts must be created inside the analysis directory, organized into subdirectories by project. Each distinct analysis project gets its own subdirectory (e.g.,
analysis/sttc_study/, analysis/burst_detection/).
- Ask the user which project subdirectory to use if it is not obvious from context. If the project is new, propose a subdirectory name before creating it.
- Never write analysis code inside
SpikeLab/.
- Use descriptive filenames that reflect the specific analysis (e.g.,
compute_correlations.py, plot_burst_raster.py).
Script structure
- Import from
spikelab at the top of the script.
- Load data, run analysis, print or save results — keep scripts self-contained and runnable.
- Ensure
spikelab is importable in your environment. If the library was installed with pip install -e ., standard imports work directly. Otherwise, add the src/ directory to sys.path:
import os, sys
src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../src"))
sys.path.insert(0, src_dir)
Using the workspace
The AnalysisWorkspace is the recommended way to store and organize intermediate results and outputs for complex analyses. Items are addressed by (namespace, key) — use the recording name as the namespace and a descriptive string as the key.
Basic usage:
from spikelab.workspace.workspace import AnalysisWorkspace
ws = AnalysisWorkspace(name="my_analysis")
ws.store("recording_a", "sttc_matrix", sttc_result)
ws.store("recording_a", "burst_times", burst_array)
result = ws.get("recording_a", "sttc_matrix")
ws.describe()
ws.list_keys("recording_a")
ws.save("analysis/my_project/results/workspace")
ws = AnalysisWorkspace.load("analysis/my_project/results/workspace")
Use the workspace for:
- Analyses with multiple stages that build on each other
- Saving intermediate results without proliferating ad-hoc pickle files
- You want a single inspectable store for all outputs of a project
Automatic caching: Always cache expensive intermediate results (e.g., GPLVM fits, instantaneous firing rates, burst detection outputs, correlation matrices) into the workspace and save to disk after computing them. Scripts should check for cached results before recomputing — load from the workspace if available, compute and store if not.
Never overwrite existing workspace keys unless the user explicitly asks you to. Before computing a result, check whether that key already exists. If it does, use the existing data rather than recomputing and replacing it.
HDF5 serialisation supported types: The workspace .h5 file supports: ndarray, SpikeData, RateData, RateSliceStack, SpikeSliceStack, PairwiseCompMatrix, PairwiseCompMatrixStack, and dict. Dicts are serialised recursively — leaf values must be one of the supported types, or a scalar (int, float, bool, str).
HDF5 file contention: The workspace .h5 file does not support concurrent writes. Never run two scripts that save to the same workspace at the same time — one will fail with a corrupted-object or lock error, and data from the other may be lost. Always wait for a running script to finish and release the workspace before starting another that writes to it. If you need to run analyses in parallel, write to separate workspaces and merge results afterwards.
Workspace save safety: ws.save() opens the HDF5 file with mode "w" (truncate and rewrite). If the write fails partway through (e.g., disk full, crash), the entire workspace file is lost — there is no atomic save or rollback. Before running a save that adds large objects, check available disk space. Never delete workspace backup files (.bak) without asking the user first — they may be the only recovery path after a failed save.
Never delete files without permission. Always ask the user before deleting any file — including temporary scripts, backup files, cached results, and pickle files. The cost of keeping an unnecessary file is low; the cost of losing needed data can be very high.
Figure output
- Always save figures as
.png files — never call plt.show(). By default, use plt.savefig("path/to/figure.png", dpi=150, bbox_inches="tight") followed by plt.close().
- Save figures in a
figures/ subdirectory within the project directory (e.g., analysis/<project>/figures/). Create the subdirectory if it does not exist.
- Use
matplotlib.use("Agg") at the top of every script that imports matplotlib, before any other matplotlib imports, to ensure no GUI backend is used.
Axes styling defaults
- Spines: Remove top and right spines. Keep left and bottom spines. For heatmaps, re-enable all four spines at 0.5 pt linewidth.
- Ticks: Tick marks face outward.
- Labels: Every axis must have a label with units (e.g., "Time (s)", "Firing rate (Hz)"). Use sentence case.
- Colorbars: Every heatmap must include a colorbar with a label and units (e.g., "Firing rate (Hz)", "Correlation coefficient").
- Colormaps: Use
"hot" for firing rates and similar non-negative data. Use diverging colormaps ("RdBu_r") for data centered on zero (e.g., correlations, z-scores).
- Titles: Do not add figure or subplot titles unless the user specifically requests one.
- Legends: Only include when necessary. Prefer placement inside the plot area to minimize whitespace. No border. When the plot uses small markers, scale them up in the legend so they are easily readable (e.g., via
legend.legend_handles and set_sizes()).
Use SpikeLab plotting functions
Where possible, use plotting functions from spikelab.spikedata.plot_utils instead of writing custom matplotlib code. These functions ensure consistent styling, handle edge cases, and reduce code duplication. Available functions include:
| Function | Use for |
|---|
plot_recording | Multi-panel recording figures (raster, pop rate, FR heatmap, model states) |
plot_distribution | Violin/boxplot distributions across conditions |
plot_scatter | Scatter plots with color coding, identity lines, regression, or discrete groups |
plot_scatter_with_marginals | Scatter with marginal histograms (supports color_vals="density" for KDE) |
plot_manifold | Low-dimensional embeddings (PCA, UMAP) with background masks and group/continuous coloring |
plot_lines | Multi-trace line plots across conditions |
plot_percentile_bands | Percentile bands or per-unit lines with optional normalization to baseline |
plot_heatmap | 2-D matrix heatmaps with optional row normalization, reference lines |
plot_burst_sensitivity | Burst detection parameter sweeps (1-D lines or 2-D heatmaps) |
plot_pvalue_matrix | P-value matrices as -log10(p) heatmaps, standalone or as insets |
plot_spatial_network | Spatial network graphs on MEA positions |
plot_aligned_slice_single_unit | Single-unit raster across event-aligned slices |
Also available as methods on data classes: SpikeData.plot(), SpikeData.plot_aligned_pop_rate(), SpikeData.plot_spatial_network(), SpikeSliceStack.plot_aligned_slice_single_unit().
When a library function does not exactly match the desired output, use it as the base and apply post-adjustments (e.g., changing line styles, adding annotations, adjusting tick labels) rather than reimplementing from scratch.
Analysis parameters
When a method requires parameters (thresholds, window sizes, smoothing constants, etc.), follow this hierarchy strictly:
- Use values specified by the user (including values given earlier in the session)
- Use the library's documented defaults (only if the parameter has an actual default value)
- Ask the user
Never fabricate parameter values. If a required parameter has no default and the user has not specified a value, ask.
General conventions
- All spike times in the library are in milliseconds. Be explicit about units when reporting results.
- Do not modify any library source file. If you find a bug or missing feature, report it to the user.
Reporting Results
- After each analysis, summarize the key findings concisely.
- Call out any surprising or unexpected results and ask the user how to proceed.
- If results suggest a follow-up analysis, propose it rather than running it automatically.
ANALYSIS_LOG.md
Each analysis project has a ANALYSIS_LOG.md file at analysis/<project_name>/ANALYSIS_LOG.md. Create it if it does not exist. Keep it up to date after every analysis session.
It should contain:
- Experiment context — what the data is, how it was collected, what the scientific question is
- Analyses performed — for each analysis: what was done, which script was used, and what the key findings were
- Insights — patterns, unexpected results, and open questions
Update this file at the end of every session, not just when explicitly asked. Write concisely — this is a running lab notebook, not a formal report.
Important invariants to preserve
- All spike times are stored in milliseconds — always note this where relevant.
RateData shape is (U, T) where U = units, T = time bins.
PairwiseCompMatrix shape is (N, N).
PairwiseCompMatrixStack shape is (N, N, S).
RateSliceStack shape is (U, T, S) where U = units, T = time bins, S = slices.
SpikeSliceStack stores a list[SpikeData] of length S (not a single array), but its to_raster_array() output is (U, T, S).
Troubleshooting
Missing optional dependencies
The analyzer uses optional pyproject.toml extras for several analyses. If you hit an ImportError, install the matching extra inside the active environment (this works inside a conda env created from environment.yml too — pip installs cleanly into a conda env).
| Missing module(s) | Extra to install | Used by |
|---|
umap, sklearn, networkx, community | [ml] | UMAP embeddings, clustering helpers, Louvain community detection (python-louvain imports as community) |
neo, quantities, pynwb | [neo] | Reading .nwb files via neo / pynwb |
one (or brainwidemap) | [ibl] (+ pip install git+https://github.com/int-brain-lab/paper-brain-wide-map.git for brainwidemap) | Querying and loading IBL Brain-Wide Map datasets (ONE-api imports as one) |
boto3 | [s3] | Loading data from S3-compatible stores |
pandas | [io] | Pandas-backed loaders and exporters |
jax, jaxlib, jaxopt, optax, poor_man_gplvm | [gplvm] (+ pip install git+https://github.com/samdeoxys1/poor-man-GPLVM.git for poor_man_gplvm) | Gaussian Process Latent Variable Model fitting |
numba | [numba] | Numba-accelerated kernels |
Install (from a source clone):
pip install -e ".[ml]"
pip install -e ".[ml,neo,s3]"
pip install -e ".[all]"
From PyPI:
pip install "spikelab[ml]"
pip install "spikelab[ml,neo,s3]"
pip install "spikelab[all]"
The complete extras list and their package contents lives in [project.optional-dependencies] in pyproject.toml.