diff --git a/README.md b/README.md index a83e2aa7..1e2f4ee3 100644 --- a/README.md +++ b/README.md @@ -149,18 +149,18 @@ These smaller demos remain useful as quick install and plotting checks. ## Documentation - Docs home: [cajigaslab.github.io/nSTAT-python](https://cajigaslab.github.io/nSTAT-python/) -- Help home: [cajigaslab.github.io/nSTAT-python/NeuralSpikeAnalysis_top.html](https://cajigaslab.github.io/nSTAT-python/NeuralSpikeAnalysis_top.html) -- Paper overview: [cajigaslab.github.io/nSTAT-python/PaperOverview.html](https://cajigaslab.github.io/nSTAT-python/PaperOverview.html) -- Example index: [cajigaslab.github.io/nSTAT-python/Examples.html](https://cajigaslab.github.io/nSTAT-python/Examples.html) -- Class definitions: [cajigaslab.github.io/nSTAT-python/ClassDefinitions.html](https://cajigaslab.github.io/nSTAT-python/ClassDefinitions.html) -- Documentation setup: [cajigaslab.github.io/nSTAT-python/DocumentationSetup.html](https://cajigaslab.github.io/nSTAT-python/DocumentationSetup.html) +- Help home: [cajigaslab.github.io/nSTAT-python/help/](https://cajigaslab.github.io/nSTAT-python/help/index.html) +- Paper overview: [cajigaslab.github.io/nSTAT-python/help/paper_overview.html](https://cajigaslab.github.io/nSTAT-python/help/paper_overview.html) +- Example index: [cajigaslab.github.io/nSTAT-python/help/examples_index.html](https://cajigaslab.github.io/nSTAT-python/help/examples_index.html) +- Class definitions: [cajigaslab.github.io/nSTAT-python/help/class_definitions.html](https://cajigaslab.github.io/nSTAT-python/help/class_definitions.html) +- Parity dashboard: [cajigaslab.github.io/nSTAT-python/help/parity_dashboard.html](https://cajigaslab.github.io/nSTAT-python/help/parity_dashboard.html) +- API reference: [cajigaslab.github.io/nSTAT-python/api.html](https://cajigaslab.github.io/nSTAT-python/api.html) Source pages: - [docs/NeuralSpikeAnalysis_top.md](docs/NeuralSpikeAnalysis_top.md) - [docs/PaperOverview.md](docs/PaperOverview.md) - [docs/Examples.md](docs/Examples.md) - [docs/ClassDefinitions.md](docs/ClassDefinitions.md) -- [docs/DocumentationSetup.md](docs/DocumentationSetup.md) ## Plot Style @@ -241,7 +241,7 @@ parity verified. used `.^2` instead of `.^3` - `Analysis.m` — Granger causality mask zeroed all columns instead of column `i` -See [parity/parity_report.md](parity/parity_report.md) for the full audit. +See [parity/report.md](parity/report.md) for the full audit. ## License diff --git a/docs/figures/example02/fig02_lag_and_model_comparison.png b/docs/figures/example02/fig02_lag_and_model_comparison.png index 73592f75..a18f154e 100644 Binary files a/docs/figures/example02/fig02_lag_and_model_comparison.png and b/docs/figures/example02/fig02_lag_and_model_comparison.png differ diff --git a/docs/figures/example03/fig04_ssglm_fit_diagnostics.png b/docs/figures/example03/fig04_ssglm_fit_diagnostics.png index c5846300..1ac75c44 100644 Binary files a/docs/figures/example03/fig04_ssglm_fit_diagnostics.png and b/docs/figures/example03/fig04_ssglm_fit_diagnostics.png differ diff --git a/docs/figures/example03/fig06_learning_trial_comparison.png b/docs/figures/example03/fig06_learning_trial_comparison.png index cb058492..34edb2a4 100644 Binary files a/docs/figures/example03/fig06_learning_trial_comparison.png and b/docs/figures/example03/fig06_learning_trial_comparison.png differ diff --git a/docs/figures/example05/fig03_reach_and_population_setup.png b/docs/figures/example05/fig03_reach_and_population_setup.png index 4e87d03e..840a1077 100644 Binary files a/docs/figures/example05/fig03_reach_and_population_setup.png and b/docs/figures/example05/fig03_reach_and_population_setup.png differ diff --git a/docs/figures/example05/fig04_ppaf_goal_vs_free.png b/docs/figures/example05/fig04_ppaf_goal_vs_free.png index afef5c89..3939f423 100644 Binary files a/docs/figures/example05/fig04_ppaf_goal_vs_free.png and b/docs/figures/example05/fig04_ppaf_goal_vs_free.png differ diff --git a/docs/figures/example05/fig05_hybrid_setup.png b/docs/figures/example05/fig05_hybrid_setup.png index c00124d3..09e606a8 100644 Binary files a/docs/figures/example05/fig05_hybrid_setup.png and b/docs/figures/example05/fig05_hybrid_setup.png differ diff --git a/docs/figures/example05/fig06_hybrid_decoding_summary.png b/docs/figures/example05/fig06_hybrid_decoding_summary.png index d01845bc..0670ab79 100644 Binary files a/docs/figures/example05/fig06_hybrid_decoding_summary.png and b/docs/figures/example05/fig06_hybrid_decoding_summary.png differ diff --git a/examples/paper/example05_decoding_ppaf_pphf.py b/examples/paper/example05_decoding_ppaf_pphf.py index 923cdabd..d3e3d337 100644 --- a/examples/paper/example05_decoding_ppaf_pphf.py +++ b/examples/paper/example05_decoding_ppaf_pphf.py @@ -13,14 +13,17 @@ 3. Decode the stimulus using ``PPDecodeFilterLinear`` (PPAF). Part B — 4-State Arm Reach with PPAF (Figures 3–4): - 4. Simulate reaching trajectories (position + velocity, 4-D state). - 5. Encode with 20-cell cosine-tuning population (binomial CIF). + 4. Simulate reaching trajectories (position + velocity, 4-D state) using + minimum-jerk dynamics (cosine acceleration toward target). + 5. Encode with 20-cell velocity-tuned population (binomial CIF). 6. Decode with PPAF (free) and PPAF + Goal; compare across 20 simulations. Part C — Hybrid Filter (Figures 5–6): - 7. Simulate 40-cell population with 2 discrete reach-states (rest / reach) - that modulate baseline firing rate, plus velocity-tuned continuous state. - 8. Decode joint discrete + continuous state via ``PPHybridFilterLinear``. + 7. Load fixture trajectory with 6-D state (pos + vel + accel) and 2 discrete + movement modes (not-moving / moving) from ``paperHybridFilterExample.mat``. + 8. Simulate 40-cell population with velocity-tuned binomial CIF. + 9. Decode joint discrete + continuous state via ``PPHybridFilterLinear`` + (both goal-directed and free), averaged over 20 simulations. Paper mapping: Section 2.5 (point-process adaptive filter) and Section 2.6 (hybrid filter). @@ -28,10 +31,10 @@ Expected outputs: - Figure 1: CIF tuning curves and simulated spike raster. - Figure 2: Decoded stimulus vs true (with 95% confidence band). - - Figure 3: Reach trajectory and population spike raster. - - Figure 4: PPAF comparison (free vs goal-informed, 20 runs box plot). - - Figure 5: Hybrid filter setup (state sequence, spike raster). - - Figure 6: Hybrid decoding results (state probabilities, decoded kinematics). + - Figure 3: Reach trajectory, position/velocity traces, neural raster, CIF. + - Figure 4: PPAF decoding overlaid trajectories (free=green, goal=blue). + - Figure 5: Hybrid fixture setup (reach path, traces, raster, discrete state). + - Figure 6: Hybrid decoding summary (state est, probabilities, decoded path). """ from __future__ import annotations @@ -147,108 +150,119 @@ def _run_part_a(seed=11, n_cells=20): # ────────────────────────────────────────────────────────────────────────────── -def _simulate_reach(delta, T_total, rng): - """Simulate a 2-D reaching trajectory with 4-D state [x, y, vx, vy]. +def _simulate_reach_minjerk(delta, T_total): + """Simulate a 2-D minimum-jerk reach from x0 to xT. - Uses a simple sinusoidal trajectory to mimic a reaching task. + Uses MATLAB's cosine-acceleration dynamics: + xState(:,k) = A * xState(:,k-1) + (delta/2)*(pi/T)^2 * cos(pi*t/T) + * [0; 0; xT(1)-x0(1); xT(2)-x0(2)] + + Returns time, xState (4×T), A (4×4). """ + x0 = np.array([0.0, 0.0, 0.0, 0.0]) + xT_target = np.array([-0.35, 0.2, 0.0, 0.0]) time = np.arange(0.0, T_total + delta, delta) T = len(time) - # Smooth trajectory - x_pos = 0.25 * np.sin(2.0 * np.pi * 0.15 * time) - y_pos = 0.20 * np.cos(2.0 * np.pi * 0.10 * time) - vx = np.gradient(x_pos, delta) - vy = np.gradient(y_pos, delta) - - state = np.vstack([x_pos, y_pos, vx, vy]) # (4, T) - return time, state - - -def _run_part_b(seed=19, n_cells=20, n_sims=20): - """Compare PPAF free vs goal-directed decoding for arm reach.""" - rng = np.random.default_rng(seed) - delta = 0.01 # 10 ms bins - ns = 4 # state dimension - - # State-space model (constant-velocity kinematic model) A = np.array([ [1, 0, delta, 0], [0, 1, 0, delta], [0, 0, 1, 0], [0, 0, 0, 1], ], dtype=float) - Q = 0.001 * np.eye(ns, dtype=float) - # Encoding model: cosine tuning to velocity - # mu_c ~ N(-3.0, 0.2) - # beta_c = [0, 0, w_vx, w_vy] — velocity tuned - b0 = rng.normal(-3.0, 0.2, n_cells) - beta = np.zeros((ns, n_cells), dtype=float) - for c in range(n_cells): - beta[2, c] = 3.0 * rng.normal(0.0, 1.0) # vx weight - beta[3, c] = 3.0 * rng.normal(0.0, 1.0) # vy weight + xState = np.zeros((4, T), dtype=float) + xState[:, 0] = x0 + accel_dir = np.array([0.0, 0.0, xT_target[0] - x0[0], xT_target[1] - x0[1]]) + for k in range(1, T): + accel = (delta / 2.0) * (np.pi / T_total) ** 2 * np.cos(np.pi * time[k] / T_total) + xState[:, k] = A @ xState[:, k - 1] + accel * accel_dir + + return time, xState, A + + +def _run_part_b(seed=0, n_cells=20, n_sims=20): + """Compare PPAF free vs goal-directed decoding for arm reach. + + Matches MATLAB: single trajectory, 20 re-randomized encoding simulations. + """ + rng = np.random.default_rng(seed) + delta = 0.001 # 1 ms bins (matches MATLAB) + T_total = 2.0 # 2-second reach + + # ── Generate minimum-jerk reach trajectory ── + time, xState, A = _simulate_reach_minjerk(delta, T_total) + T = xState.shape[1] + ns = 4 + + # Target = final state + yT = xState[:, -1].copy() + + # Q from trajectory variance (MATLAB: diag(var(diff(xState,[],2),[],2))*100) + Q = np.diag(np.var(np.diff(xState, axis=1), axis=1)) * 100.0 - # Run multiple simulations to compare free vs goal-directed - rmse_free = np.zeros((n_sims, ns), dtype=float) - rmse_goal = np.zeros((n_sims, ns), dtype=float) + # Initial/target covariances (MATLAB: very tight) + r, p = 1e-6, 1e-6 + pi0 = np.diag([r, r, p, p]) + piT = np.diag([r, r, p, p]) - # Store one example run for plotting + # ── Run 20 repeated simulations ── + # Same trajectory, re-randomized encoding + spikes each time + all_runs_goal = [] + all_runs_free = [] example_run = None for sim_idx in range(n_sims): - sim_rng = np.random.default_rng(seed + sim_idx + 100) - time, state = _simulate_reach(delta, 10.0, sim_rng) - T = state.shape[1] + # MATLAB: bCoeffs = 10*(rand(numCells,2)-0.5); Uniform[-5,5] + bCoeffs = 10.0 * (rng.random((n_cells, 2)) - 0.5) + # MATLAB: muCoeffs = log(10*delta) + randn(numCells,1) + muCoeffs = np.log(10.0 * delta) + rng.standard_normal(n_cells) + + # beta: 4×C with zeros for position, bCoeffs for velocity + beta = np.zeros((ns, n_cells), dtype=float) + beta[2, :] = bCoeffs[:, 0] # vx tuning + beta[3, :] = bCoeffs[:, 1] # vy tuning # Simulate spikes - dN = _simulate_binomial_spikes(state, b0, beta, sim_rng) + dN = _simulate_binomial_spikes(xState, muCoeffs, beta, rng) - # Initial conditions - x0 = state[:, 0] - Pi0 = 0.1 * np.eye(ns) + # Initial state + x0 = np.array([0.0, 0.0, 0.0, 0.0]) - # --- Free decode (no goal) --- - x_p_free, _, x_u_free, W_u_free, _, _, _, _ = ( - DecodingAlgorithms.PPDecodeFilterLinear( - A, Q, dN, b0, beta, "binomial", delta, - None, None, x0, Pi0 - ) + # --- Goal-directed decode --- + _, _, x_u_goal, _, _, _, _, _ = DecodingAlgorithms.PPDecodeFilterLinear( + A, Q, dN, muCoeffs, beta, "binomial", delta, + None, None, x0, pi0, yT, piT, 0 ) - # --- Goal-directed decode --- - yT = state[:, -1] # target = final state - PiT = 0.01 * np.eye(ns) # tight target uncertainty - x_p_goal, _, x_u_goal, W_u_goal, _, _, _, _ = ( - DecodingAlgorithms.PPDecodeFilterLinear( - A, Q, dN, b0, beta, "binomial", delta, - None, None, x0, Pi0, yT, PiT, 0 - ) + # --- Free decode (no goal) --- + _, _, x_u_free, _, _, _, _, _ = DecodingAlgorithms.PPDecodeFilterLinear( + A, Q, dN, muCoeffs, beta, "binomial", delta, + None, None, x0, ) - # Compute RMSE per state dimension - for d in range(ns): - rmse_free[sim_idx, d] = np.sqrt(np.mean((x_u_free[d, :] - state[d, :]) ** 2)) - rmse_goal[sim_idx, d] = np.sqrt(np.mean((x_u_goal[d, :] - state[d, :]) ** 2)) + all_runs_goal.append(x_u_goal) + all_runs_free.append(x_u_free) if sim_idx == 0: example_run = { "time": time, - "state": state, + "xState": xState, "dN": dN, - "x_u_free": x_u_free, - "x_u_goal": x_u_goal, - "W_u_free": W_u_free, - "W_u_goal": W_u_goal, + "muCoeffs": muCoeffs, + "bCoeffs": bCoeffs, + "beta": beta, } return { - "rmse_free": rmse_free, - "rmse_goal": rmse_goal, + "all_runs_goal": all_runs_goal, + "all_runs_free": all_runs_free, "example": example_run, "n_cells": n_cells, "n_sims": n_sims, - "state_labels": ["x", "y", "vx", "vy"], + "xState": xState, + "time": time, + "delta": delta, } @@ -257,109 +271,182 @@ def _run_part_b(seed=19, n_cells=20, n_sims=20): # ────────────────────────────────────────────────────────────────────────────── -def _run_part_c(seed=37, n_cells=40): - """PPHybridFilterLinear: joint discrete/continuous state decoding.""" +def _load_hybrid_fixture(): + """Load the MATLAB hybrid filter fixture (paperHybridFilterExample.mat). + + Returns a dict with: time, delta, X (6×T), mstate (T,), + A (list of 2), Q (list of 2), p_ij (2×2), Px0 (list of 2), ind. + """ + import scipy.io as sio + + # Search for fixture in multiple locations + candidates = [ + REPO_ROOT / "nstat" / "data" / "paperHybridFilterExample.mat", + REPO_ROOT / "helpfiles" / "paperHybridFilterExample.mat", + ] + mat_path = None + for p in candidates: + if p.exists() and p.stat().st_size > 200: # skip LFS pointers + mat_path = p + break + + if mat_path is None: + raise FileNotFoundError( + "Cannot find paperHybridFilterExample.mat fixture. " + "Ensure it is in nstat/data/ or helpfiles/." + ) + + f = sio.loadmat(str(mat_path)) + time = f["time"].ravel().astype(float) + delta = float(f["delta"].ravel()[0]) + X = f["X"].astype(float) # (6, T) + mstate = f["mstate"].ravel().astype(int) # (T,), values 1 or 2 + p_ij = f["p_ij"].astype(float) # (2, 2) + + # Cell arrays → Python lists + A_cell = f["A"] + Q_cell = f["Q"] + Px0_cell = f["Px0"] + ind_cell = f["ind"] + + A = [A_cell[0, i].astype(float) for i in range(A_cell.shape[1])] + Q = [Q_cell[0, i].astype(float) for i in range(Q_cell.shape[1])] + Px0 = [Px0_cell[0, i].astype(float) for i in range(Px0_cell.shape[1])] + # ind: convert from MATLAB 1-indexed to Python 0-indexed + ind = [ind_cell[0, i].ravel().astype(int) - 1 for i in range(ind_cell.shape[1])] + + return { + "time": time, + "delta": delta, + "X": X, + "mstate": mstate, + "A": A, + "Q": Q, + "p_ij": p_ij, + "Px0": Px0, + "ind": ind, + } + + +def _run_part_c(seed=0, n_cells=40, n_sims=20): + """PPHybridFilterLinear: joint discrete/continuous state decoding. + + Loads fixture trajectory, runs 20 simulations with re-randomized encoding, + comparing goal-directed vs free hybrid decoding. + """ rng = np.random.default_rng(seed) - delta = 0.01 # 10 ms bins - ns = 4 # continuous state dimension (x, y, vx, vy) - # ── Simulate trajectory ── - time = np.arange(0.0, 10.0, delta, dtype=float) - T = len(time) - x_pos = 0.3 * np.sin(2.0 * np.pi * 0.15 * time) - y_pos = 0.25 * np.cos(2.0 * np.pi * 0.10 * time) - vx = np.gradient(x_pos, delta) - vy = np.gradient(y_pos, delta) - state = np.vstack([x_pos, y_pos, vx, vy]) # (4, T) - - # Discrete state: alternating reach / hold (period ~6s) - true_mode = np.where(np.sin(2.0 * np.pi * time / 6.0) > 0.0, 1, 2).astype(int) - # Add stochastic flips - flip = rng.random(T) < 0.01 - true_mode[flip] = 3 - true_mode[flip] - - # ── State-space models (one per mode) ── - A_reach = np.array([ - [1, 0, delta, 0], - [0, 1, 0, delta], - [0, 0, 1, 0], - [0, 0, 0, 1], - ], dtype=float) - Q_reach = 0.001 * np.eye(ns) + # ── Load fixture ── + fix = _load_hybrid_fixture() + time = fix["time"] + delta = fix["delta"] + X = fix["X"] # (6, T) + mstate = fix["mstate"] # (T,) + p_ij = fix["p_ij"] + A_models = fix["A"] # [A_hold (2×2), A_reach (6×6)] + Q_models_orig = fix["Q"] # [Q_hold (2×2), Q_reach (6×6)] + Px0 = fix["Px0"] # [Px0_hold (2×2), Px0_reach (6×6)] + ind = fix["ind"] # [[0,1], [0,1,2,3,4,5]] + T = X.shape[1] + + # ── Recompute Q from trajectory variance (matching MATLAB) ── + nonMovingInd = np.where((X[4, :] == 0) & (X[5, :] == 0))[0] + movingInd = np.setdiff1d(np.arange(T), nonMovingInd) + + Q_reach = np.diag(np.var(np.diff(X[:, movingInd], axis=1), axis=1)) + Q_reach[:4, :4] = 0.0 # Zero out pos/vel noise; only accel has noise + varNV = np.diag(np.var(np.diff(X[:, nonMovingInd], axis=1), axis=1)) + Q_hold = varNV[:2, :2] + + Q_models = [Q_hold, Q_reach] + + # State dimensions + dim_hold = A_models[0].shape[0] # 2 + dim_reach = A_models[1].shape[0] # 6 + + # ── Run 20 repeated simulations ── + X_estAll = np.zeros((dim_reach, T, n_sims), dtype=float) + X_estNTAll = np.zeros((dim_reach, T, n_sims), dtype=float) + S_estAll = np.zeros((n_sims, T), dtype=int) + S_estNTAll = np.zeros((n_sims, T), dtype=int) + MU_estAll = np.zeros((2, T, n_sims), dtype=float) + MU_estNTAll = np.zeros((2, T, n_sims), dtype=float) + example_dN = None + + for n in range(n_sims): + # MATLAB: muCoeffs = log(10*delta) + randn(numCells,1) + muCoeffs = np.log(10.0 * delta) + rng.standard_normal(n_cells) + # MATLAB: coeffs = [muCoeffs, zeros(C,2), 10*(rand(C,2)-0.5), zeros(C,2)] + # = [mu, 0, 0, b_vx, b_vy, 0, 0] — tuned to velocities (states 3-4) + bCoeffs_vx = 10.0 * (rng.random(n_cells) - 0.5) + bCoeffs_vy = 10.0 * (rng.random(n_cells) - 0.5) + + # Full beta: 6×C matrix + beta_full = np.zeros((6, n_cells), dtype=float) + beta_full[2, :] = bCoeffs_vx # vx tuning + beta_full[3, :] = bCoeffs_vy # vy tuning + + # Simulate spikes from full state trajectory + dN = _simulate_binomial_spikes(X, muCoeffs, beta_full, rng) + + if n == 0: + example_dN = dN.copy() + + # ── Initial conditions per mode ── + x0_list = [X[ind[0], 0], X[ind[1], 0]] + Pi0_list = Px0 + + # ── Target conditions per mode ── + yT_list = [X[ind[0], -1], X[ind[1], -1]] + piT_list = [1e-9 * np.eye(dim_hold), 1e-9 * np.eye(dim_reach)] + + # beta per mode: hold uses 2-dim subset, reach uses full 6-dim + beta_hold = beta_full[ind[0], :] # (2, C) — will be zeros + beta_reach = beta_full[ind[1], :] # (6, C) — has vx/vy tuning + + mu0 = np.array([0.5, 0.5]) + + # --- Goal-directed hybrid decode --- + S_est, X_est, _, MU_est, _, _, _ = DecodingAlgorithms.PPHybridFilterLinear( + A_models, Q_models, p_ij, mu0, dN, + [muCoeffs, muCoeffs], + [beta_hold, beta_reach], + "binomial", delta, None, None, + x0_list, Pi0_list, + yT_list, piT_list, + ) - # Hold state: damped velocity - A_hold = np.array([ - [1, 0, delta, 0], - [0, 1, 0, delta], - [0, 0, 0.95, 0], - [0, 0, 0, 0.95], - ], dtype=float) - Q_hold = 0.0005 * np.eye(ns) - - # ── Encoding model ── - # Neurons tuned to ALL state dimensions (position + velocity). - # Mode-dependent baseline: mode 1 (reach) has different rate than mode 2 (hold). - b0_mode1 = rng.normal(-3.5, 0.2, n_cells) # reach baseline - b0_mode2 = rng.normal(-2.5, 0.2, n_cells) # hold baseline - - # Full state tuning: position + velocity - beta_mat = np.zeros((ns, n_cells), dtype=float) - beta_mat[0, :] = rng.normal(0.0, 2.0, n_cells) # x position - beta_mat[1, :] = rng.normal(0.0, 2.0, n_cells) # y position - beta_mat[2, :] = rng.normal(0.0, 3.0, n_cells) # vx - beta_mat[3, :] = rng.normal(0.0, 3.0, n_cells) # vy - - # Simulate spikes with mode-dependent baseline (binomial) - dN = np.zeros((n_cells, T), dtype=float) - for t in range(T): - b0 = b0_mode1 if true_mode[t] == 1 else b0_mode2 - eta = b0 + beta_mat.T @ state[:, t] - p = 1.0 / (1.0 + np.exp(-np.clip(eta, -20.0, 20.0))) - dN[:, t] = (rng.random(n_cells) < p).astype(float) - - # ── Transition matrix ── - p_ij = np.array([[0.985, 0.015], [0.02, 0.98]], dtype=float) - - # ── Decode with PPHybridFilterLinear ── - Mu0 = np.array([0.5, 0.5]) - x0 = [state[:, 0], state[:, 0]] - Pi0 = [0.5 * np.eye(ns), 0.5 * np.eye(ns)] - - S_est, X_est, W_est, MU_u, _, _, _ = DecodingAlgorithms.PPHybridFilterLinear( - [A_reach, A_hold], - [Q_reach, Q_hold], - p_ij, - Mu0, - dN, - [b0_mode1, b0_mode2], - [beta_mat, beta_mat], - "binomial", - delta, - None, # gamma - None, # windowTimes - x0, - Pi0, - ) + # --- Free hybrid decode (no target) --- + S_estNT, X_estNT, _, MU_estNT, _, _, _ = DecodingAlgorithms.PPHybridFilterLinear( + A_models, Q_models, p_ij, mu0, dN, + [muCoeffs, muCoeffs], + [beta_hold, beta_reach], + "binomial", delta, None, None, + x0_list, Pi0_list, + ) - # Classification accuracy - state_acc = float(np.mean(S_est == true_mode)) + X_estAll[:, :, n] = X_est + X_estNTAll[:, :, n] = X_estNT + S_estAll[n, :] = S_est + S_estNTAll[n, :] = S_estNT + MU_estAll[:, :, n] = MU_est + MU_estNTAll[:, :, n] = MU_estNT - # Position RMSE - rmse_x = float(np.sqrt(np.mean((X_est[0, :] - x_pos) ** 2))) - rmse_y = float(np.sqrt(np.mean((X_est[1, :] - y_pos) ** 2))) + print(f" Hybrid sim {n + 1}/{n_sims} done") return { "time": time, - "state": state, - "true_mode": true_mode, - "S_est": S_est, - "X_est": X_est, - "MU_u": MU_u, - "dN": dN, - "state_acc": state_acc, - "rmse_x": rmse_x, - "rmse_y": rmse_y, + "X": X, + "mstate": mstate, + "dN": example_dN, + "X_estAll": X_estAll, + "X_estNTAll": X_estNTAll, + "S_estAll": S_estAll, + "S_estNTAll": S_estNTAll, + "MU_estAll": MU_estAll, + "MU_estNTAll": MU_estNTAll, "n_cells": n_cells, + "n_sims": n_sims, } @@ -427,104 +514,259 @@ def _plot_part_a(result): def _plot_part_b(result): - """Figure 3: Reach trajectory & encoding. Figure 4: RMSE comparison.""" + """Figure 3: Reach setup (4×2 layout). Figure 4: Overlaid decoded trajectories.""" ex = result["example"] time = ex["time"] - state = ex["state"] - - # ── Figure 3: Example reach with decoded trajectories ── - fig3, axes3 = plt.subplots(2, 2, figsize=(12, 8)) - labels = result["state_labels"] - ylabels = ["x (m)", "y (m)", "vx (m/s)", "vy (m/s)"] - - for d, (ax, lab, ylab) in enumerate(zip(axes3.ravel(), labels, ylabels)): - ax.plot(time, state[d, :], "k-", linewidth=1.0, label="True") - ax.plot(time, ex["x_u_free"][d, :], "b-", linewidth=0.7, alpha=0.8, label="PPAF free") - ax.plot(time, ex["x_u_goal"][d, :], "r-", linewidth=0.7, alpha=0.8, label="PPAF+Goal") - ax.set_ylabel(ylab) - ax.set_title(f"State: {lab}") - if d >= 2: - ax.set_xlabel("Time (s)") - if d == 0: - ax.legend(loc="upper right", fontsize=8) + xState = ex["xState"] + dN = ex["dN"] + delta = result["delta"] + n_cells = result["n_cells"] + + # ── Figure 3: Reach trajectory and population setup (4×2 layout) ── + fig3 = plt.figure(figsize=(14, 9)) + + # Top-left [1,3]: 2D reach path (in cm) + ax_path = fig3.add_subplot(4, 2, (1, 3)) + ax_path.plot(100 * xState[0, :], 100 * xState[1, :], "k", linewidth=2) + ax_path.plot(100 * xState[0, 0], 100 * xState[1, 0], "bo", markersize=14) + ax_path.plot(100 * xState[0, -1], 100 * xState[1, -1], "ro", markersize=14) + ax_path.legend(["Path", "Start", "Finish"], loc="upper right") + ax_path.set_xlabel("X Position [cm]") + ax_path.set_ylabel("Y Position [cm]") + ax_path.set_title("Reach Path", fontweight="bold", fontsize=14) + + # Middle-left [5]: position vs time (in cm) + ax_pos = fig3.add_subplot(4, 2, 5) + h1, = ax_pos.plot(time, 100 * xState[0, :], "k", linewidth=2) + h2, = ax_pos.plot(time, 100 * xState[1, :], "k-.", linewidth=2) + ax_pos.legend([h1, h2], ["x", "y"], loc="upper right") + ax_pos.set_xlabel("time [s]") + ax_pos.set_ylabel("Position [cm]") + + # Lower-left [7]: velocity vs time (in cm/s) + ax_vel = fig3.add_subplot(4, 2, 7) + h1, = ax_vel.plot(time, 100 * xState[2, :], "k", linewidth=2) + h2, = ax_vel.plot(time, 100 * xState[3, :], "k-.", linewidth=2) + ax_vel.legend([h1, h2], ["$v_x$", "$v_y$"], loc="upper right") + ax_vel.set_xlabel("time [s]") + ax_vel.set_ylabel("Velocity [cm/s]") + + # Top-right [2,4]: neural raster + ax_raster = fig3.add_subplot(4, 2, (2, 4)) + for c in range(n_cells): + spike_t = time[dN[c, :] > 0] + ax_raster.plot(spike_t, np.full_like(spike_t, c + 1), "|", color="k", markersize=2) + ax_raster.set_ylabel("Cell Number") + ax_raster.set_xlabel("time [s]") + ax_raster.set_title("Neural Raster", fontweight="bold", fontsize=14) + + # Bottom-right [6,8]: CIF curves + ax_cif = fig3.add_subplot(4, 2, (6, 8)) + muCoeffs = ex["muCoeffs"] + beta = ex["beta"] + for c in range(n_cells): + eta = muCoeffs[c] + beta[:, c] @ xState + exp_eta = np.exp(np.clip(eta, -20, 20)) + lam = (exp_eta / (1.0 + exp_eta)) / delta + ax_cif.plot(time, lam, "k", linewidth=0.5) + ax_cif.set_title("Neural Conditional Intensity Functions", + fontweight="bold", fontsize=14) + ax_cif.set_xlabel("time [s]") + ax_cif.set_ylabel("Firing Rate [spikes/sec]") - fig3.suptitle("Part B: Arm Reach — PPAF Decoding (Example Run)", fontsize=12) fig3.tight_layout() - # ── Figure 4: RMSE box plot (free vs goal) ── - fig4, axes4 = plt.subplots(1, 4, figsize=(14, 4)) - for d, (ax, lab) in enumerate(zip(axes4, labels)): - data = [result["rmse_free"][:, d], result["rmse_goal"][:, d]] - bp = ax.boxplot(data, tick_labels=["Free", "Goal"]) - ax.set_title(f"RMSE: {lab}") - ax.set_ylabel("RMSE") - - fig4.suptitle( - f"Part B: PPAF Free vs Goal ({result['n_sims']} simulations, {result['n_cells']} cells)", - fontsize=12, - ) + # ── Figure 4: Overlaid decoded trajectories (4×2 layout, 20 runs) ── + fig4 = plt.figure(figsize=(14, 9)) + + # Top [1:4]: 2D estimated vs actual reach paths + ax_2d = fig4.add_subplot(4, 2, (1, 4)) + ax_2d.plot(100 * xState[0, :], 100 * xState[1, :], "k", linewidth=3) + ax_2d.set_title("Estimated vs. Actual Reach Paths", + fontweight="bold", fontsize=12) + + for sim_idx in range(result["n_sims"]): + x_u_goal = result["all_runs_goal"][sim_idx] + x_u_free = result["all_runs_free"][sim_idx] + ax_2d.plot(100 * x_u_goal[0, :], 100 * x_u_goal[1, :], "b", linewidth=0.5) + ax_2d.plot(100 * x_u_free[0, :], 100 * x_u_free[1, :], "g", linewidth=0.5) + ax_2d.set_xlabel("x [cm]") + ax_2d.set_ylabel("y [cm]") + + # Bottom panels: per-state traces + state_labels = ["x(t) [cm]", "y(t) [cm]", "$v_x$(t) [cm/s]", "$v_y$(t) [cm/s]"] + subplot_indices = [5, 6, 7, 8] + scale = 100.0 # meters → cm + + for d, (sp_idx, ylabel) in enumerate(zip(subplot_indices, state_labels)): + ax = fig4.add_subplot(4, 2, sp_idx) + ax.plot(time, scale * xState[d, :], "k", linewidth=3) + + for sim_idx in range(result["n_sims"]): + x_u_goal = result["all_runs_goal"][sim_idx] + x_u_free = result["all_runs_free"][sim_idx] + hB, = ax.plot(time, scale * x_u_goal[d, :], "b", linewidth=0.5) + hC, = ax.plot(time, scale * x_u_free[d, :], "g", linewidth=0.5) + + ax.set_ylabel(ylabel) + if d >= 2: + ax.set_xlabel("time [s]") + else: + ax.set_xticklabels([]) + + # Add legend on y(t) panel (subplot 6), matching MATLAB + if d == 1: + hA, = ax.plot([], [], "k", linewidth=3) + ax.legend([hA, hB, hC], ["Actual", "PPAF+Goal", "PPAF"], + loc="lower right", fontsize=8) + fig4.tight_layout() return fig3, fig4 def _plot_part_c(result): - """Figure 5: Hybrid setup. Figure 6: Hybrid decoding results.""" + """Figure 5: Hybrid setup (4×2 layout). Figure 6: Hybrid decoding summary (4×3).""" time = result["time"] - - # ── Figure 5: Setup — state sequence + raster ── - fig5, axes5 = plt.subplots(2, 1, figsize=(12, 5), sharex=True) - - # Top: discrete state - axes5[0].plot(time, result["true_mode"], "k-", linewidth=1.0, label="True mode") - axes5[0].set_ylabel("Discrete State") - axes5[0].set_yticks([1, 2]) - axes5[0].set_yticklabels(["Reach", "Hold"]) - axes5[0].set_title("Part C: Hybrid Filter Setup") - axes5[0].legend() - - # Bottom: spike raster (first 20 cells) + X = result["X"] # (6, T) + mstate = result["mstate"] dN = result["dN"] + n_cells = result["n_cells"] + + # ── Figure 5: Setup — reach path, traces, raster, discrete state (4×2) ── + fig5 = plt.figure(figsize=(14, 9)) + + # Top-left [1,3]: 2D reach path + ax_path = fig5.add_subplot(4, 2, (1, 3)) + ax_path.plot(100 * X[0, :], 100 * X[1, :], "k", linewidth=2) + ax_path.plot(100 * X[0, 0], 100 * X[1, 0], "bo", markersize=16) + ax_path.plot(100 * X[0, -1], 100 * X[1, -1], "ro", markersize=16) + ax_path.set_xlabel("X [cm]") + ax_path.set_ylabel("Y [cm]") + ax_path.set_title("Reach Path", fontweight="bold", fontsize=14) + + # Middle-left [5]: position vs time + ax_pos = fig5.add_subplot(4, 2, 5) + h1, = ax_pos.plot(time, 100 * X[0, :], "k", linewidth=2) + h2, = ax_pos.plot(time, 100 * X[1, :], "k-.", linewidth=2) + ax_pos.legend([h1, h2], ["x", "y"], loc="upper right") + ax_pos.set_xlabel("time [s]") + ax_pos.set_ylabel("Position [cm]") + + # Lower-left [7]: velocity vs time + ax_vel = fig5.add_subplot(4, 2, 7) + h1, = ax_vel.plot(time, 100 * X[2, :], "k", linewidth=2) + h2, = ax_vel.plot(time, 100 * X[3, :], "k-.", linewidth=2) + ax_vel.legend([h1, h2], ["$v_x$", "$v_y$"], loc="upper right") + ax_vel.set_xlabel("time [s]") + ax_vel.set_ylabel("Velocity [cm/s]") + + # Top-right [2,4]: neural raster + ax_raster = fig5.add_subplot(4, 2, (2, 4)) n_show = min(20, dN.shape[0]) for c in range(n_show): - idx = np.where(dN[c, :] > 0)[0] - spike_t = time[idx] - axes5[1].plot(spike_t, np.full_like(spike_t, c + 1), "|", color="k", markersize=2) - axes5[1].set_ylabel("Neuron") - axes5[1].set_xlabel("Time (s)") - axes5[1].set_ylim(0.5, n_show + 0.5) + spike_t = time[dN[c, :] > 0] + ax_raster.plot(spike_t, np.full_like(spike_t, c + 1), "|", color="k", markersize=2) + ax_raster.set_ylabel("Cell Number") + ax_raster.set_xlabel("time [s]") + ax_raster.set_title("Neural Raster", fontweight="bold", fontsize=14) + + # Bottom-right [6,8]: discrete movement state + ax_state = fig5.add_subplot(4, 2, (6, 8)) + ax_state.plot(time, mstate, "k", linewidth=2) + ax_state.set_ylim(0, 3) + ax_state.set_yticks([1, 2]) + ax_state.set_yticklabels(["N", "M"]) + ax_state.set_xlabel("time [s]") + ax_state.set_ylabel("state") + ax_state.set_title("Discrete Movement State", fontweight="bold", fontsize=14) + fig5.tight_layout() - # ── Figure 6: Decoding results ── - fig6, axes6 = plt.subplots(3, 1, figsize=(12, 8), sharex=True) + # ── Figure 6: Hybrid decoding results (4×3 layout, averaged over 20 sims) ── + fig6 = plt.figure(figsize=(14, 9)) + + # Mean across simulations + mS_est = np.mean(result["S_estAll"], axis=0) + mS_estNT = np.mean(result["S_estNTAll"], axis=0) + mMU_est = np.mean(result["MU_estAll"][1, :, :], axis=1) # P(M|data) for goal + mMU_estNT = np.mean(result["MU_estNTAll"][1, :, :], axis=1) # P(M|data) for free + mX_est = np.mean(100 * result["X_estAll"], axis=2) + mX_estNT = np.mean(100 * result["X_estNTAll"], axis=2) + + # Left column: state estimation + probability + # [1,4]: Estimated vs actual state + ax_s = fig6.add_subplot(4, 3, (1, 4)) + ax_s.plot(time, mstate, "k", linewidth=3) + ax_s.plot(time, mS_est, "b", linewidth=3) + ax_s.plot(time, mS_estNT, "g", linewidth=3) + ax_s.set_yticks([1, 2.1]) + ax_s.set_yticklabels(["N", "M"]) + ax_s.set_xticklabels([]) + ax_s.set_ylabel("state") + ax_s.set_title("Estimated vs. Actual State", fontweight="bold", fontsize=12) + + # [7,10]: P(s(t)=M | data) + ax_prob = fig6.add_subplot(4, 3, (7, 10)) + ax_prob.plot(time, mMU_est, "b", linewidth=3) + ax_prob.plot(time, mMU_estNT, "g", linewidth=3) + ax_prob.set_xlim(time[0], time[-1]) + ax_prob.set_ylim(0, 1.1) + ax_prob.set_xlabel("time [s]") + ax_prob.set_ylabel("P(s(t)=M | data)") + ax_prob.set_title("Probability of State", fontweight="bold", fontsize=12) + + # Right top [2,3,5,6]: 2D estimated vs actual reach path + ax_2d = fig6.add_subplot(4, 3, (2, 6)) + ax_2d.plot(100 * X[0, :], 100 * X[1, :], "k", linewidth=1) + ax_2d.plot(mX_est[0, :], mX_est[1, :], "b", linewidth=3) + ax_2d.plot(mX_estNT[0, :], mX_estNT[1, :], "g", linewidth=3) + ax_2d.plot(100 * X[0, 0], 100 * X[1, 0], "bo", markersize=14) + ax_2d.plot(100 * X[0, -1], 100 * X[1, -1], "ro", markersize=14) + ax_2d.set_xlabel("x [cm]") + ax_2d.set_ylabel("y [cm]") + ax_2d.set_title("Estimated vs. Actual Reach Path", + fontweight="bold", fontsize=12) + + # Bottom panels: per-state traces + # [8]: x(t) + ax_x = fig6.add_subplot(4, 3, 8) + ax_x.plot(time, 100 * X[0, :], "k", linewidth=3) + ax_x.plot(time, mX_est[0, :], "b", linewidth=3) + ax_x.plot(time, mX_estNT[0, :], "g", linewidth=3) + ax_x.set_ylabel("x(t) [cm]") + ax_x.set_xticklabels([]) + ax_x.set_title("X Position", fontweight="bold", fontsize=12) + + # [9]: y(t) with legend + ax_y = fig6.add_subplot(4, 3, 9) + h1, = ax_y.plot(time, 100 * X[1, :], "k", linewidth=3) + h2, = ax_y.plot(time, mX_est[1, :], "b", linewidth=3) + h3, = ax_y.plot(time, mX_estNT[1, :], "g", linewidth=3) + ax_y.legend([h1, h2, h3], ["Actual", "PPAF+Goal", "PPAF"], + loc="lower right", fontsize=8) + ax_y.set_ylabel("y(t) [cm]") + ax_y.set_xticklabels([]) + ax_y.set_title("Y Position", fontweight="bold", fontsize=12) + + # [11]: vx(t) + ax_vx = fig6.add_subplot(4, 3, 11) + ax_vx.plot(time, 100 * X[2, :], "k", linewidth=3) + ax_vx.plot(time, mX_est[2, :], "b", linewidth=3) + ax_vx.plot(time, mX_estNT[2, :], "g", linewidth=3) + ax_vx.set_ylabel("$v_x$(t) [cm/s]") + ax_vx.set_xlabel("time [s]") + ax_vx.set_title("X Velocity", fontweight="bold", fontsize=12) + + # [12]: vy(t) + ax_vy = fig6.add_subplot(4, 3, 12) + ax_vy.plot(time, 100 * X[3, :], "k", linewidth=3) + ax_vy.plot(time, mX_est[3, :], "b", linewidth=3) + ax_vy.plot(time, mX_estNT[3, :], "g", linewidth=3) + ax_vy.set_ylabel("$v_y$(t) [cm/s]") + ax_vy.set_xlabel("time [s]") + ax_vy.set_title("Y Velocity", fontweight="bold", fontsize=12) - # Top: model probabilities - axes6[0].plot(time, result["MU_u"][0, :], "b-", linewidth=0.5, label="P(Reach)") - axes6[0].plot(time, result["MU_u"][1, :], "r-", linewidth=0.5, label="P(Hold)") - axes6[0].axhline(0.5, color="gray", linestyle="--", linewidth=0.5) - axes6[0].set_ylabel("Model Prob") - axes6[0].set_title( - f"PPHybridFilterLinear — State Accuracy: {result['state_acc']:.1%}" - ) - axes6[0].legend(loc="upper right", fontsize=8) - - # Middle: decoded x-position - axes6[1].plot(time, result["state"][0, :], "k-", linewidth=1.0, label="True") - axes6[1].plot(time, result["X_est"][0, :], "b-", linewidth=0.7, alpha=0.8, label="Decoded") - axes6[1].set_ylabel("x (m)") - axes6[1].legend(loc="upper right", fontsize=8) - - # Bottom: decoded y-position - axes6[2].plot(time, result["state"][1, :], "k-", linewidth=1.0, label="True") - axes6[2].plot(time, result["X_est"][1, :], "r-", linewidth=0.7, alpha=0.8, label="Decoded") - axes6[2].set_ylabel("y (m)") - axes6[2].set_xlabel("Time (s)") - axes6[2].legend(loc="upper right", fontsize=8) - - fig6.suptitle( - f"Hybrid Decoding (RMSE: x={result['rmse_x']:.4f}, y={result['rmse_y']:.4f})", - fontsize=12, - ) fig6.tight_layout() return fig5, fig6 @@ -546,13 +788,15 @@ def run_example05(*, export_figures=False, export_dir=None, show=False): 3. Decode stimulus via PPDecodeFilterLinear. Part B — Arm-reach PPAF: - 4. Simulate 4-state reaching movements (position + velocity). - 5. Encode with 20-cell cosine-tuning population. - 6. Decode with PPAF (free) and PPAF+Goal; compare across 20 runs. + 4. Simulate 4-state minimum-jerk reaching movement. + 5. Encode with 20-cell velocity-tuned population. + 6. Decode with PPAF (free) and PPAF+Goal; 20 overlaid simulations. Part C — Hybrid filter: - 7. Simulate 40-cell population with discrete state modulation. - 8. Decode joint discrete/continuous state via PPHybridFilterLinear. + 7. Load 6-state fixture trajectory with 2 discrete modes. + 8. Simulate 40-cell population with velocity tuning. + 9. Decode joint discrete/continuous state via PPHybridFilterLinear + (both goal-directed and free), averaged over 20 simulations. """ print("=" * 70) print("Example 05: Stimulus Decoding with PPAF and PPHF") @@ -566,18 +810,12 @@ def run_example05(*, export_figures=False, export_dir=None, show=False): # --- Part B: Arm-reach PPAF --- print("\n--- Part B: Arm Reach PPAF (20 simulations) ---") result_b = _run_part_b() - mean_free = result_b["rmse_free"].mean(axis=0) - mean_goal = result_b["rmse_goal"].mean(axis=0) - print(f" Mean RMSE (free): x={mean_free[0]:.4f}, y={mean_free[1]:.4f}, " - f"vx={mean_free[2]:.4f}, vy={mean_free[3]:.4f}") - print(f" Mean RMSE (goal): x={mean_goal[0]:.4f}, y={mean_goal[1]:.4f}, " - f"vx={mean_goal[2]:.4f}, vy={mean_goal[3]:.4f}") + print(f" {result_b['n_sims']} simulations, {result_b['n_cells']} cells") # --- Part C: Hybrid filter --- - print("\n--- Part C: Hybrid Filter ---") + print("\n--- Part C: Hybrid Filter (20 simulations) ---") result_c = _run_part_c() - print(f" {result_c['n_cells']} cells, state accuracy = {result_c['state_acc']:.1%}") - print(f" Position RMSE: x={result_c['rmse_x']:.4f}, y={result_c['rmse_y']:.4f}") + print(f" {result_c['n_cells']} cells, {result_c['n_sims']} simulations") # Summary summary = { @@ -588,14 +826,10 @@ def run_example05(*, export_figures=False, export_dir=None, show=False): "experiment5b": { "num_cells": float(result_b["n_cells"]), "n_sims": float(result_b["n_sims"]), - "mean_rmse_free_x": float(mean_free[0]), - "mean_rmse_goal_x": float(mean_goal[0]), }, "experiment6": { "num_cells": float(result_c["n_cells"]), - "state_accuracy": result_c["state_acc"], - "decode_rmse_x": result_c["rmse_x"], - "decode_rmse_y": result_c["rmse_y"], + "n_sims": float(result_c["n_sims"]), }, } print("\n" + json.dumps(summary, indent=2)) diff --git a/examples/paper/figures/example01/fig01_constant_mg_summary.png b/examples/paper/figures/example01/fig01_constant_mg_summary.png new file mode 100644 index 00000000..9da8d1d1 Binary files /dev/null and b/examples/paper/figures/example01/fig01_constant_mg_summary.png differ diff --git a/examples/paper/figures/example01/fig02_washout_raster_overview.png b/examples/paper/figures/example01/fig02_washout_raster_overview.png new file mode 100644 index 00000000..c65e2a06 Binary files /dev/null and b/examples/paper/figures/example01/fig02_washout_raster_overview.png differ diff --git a/examples/paper/figures/example01/fig03_piecewise_baseline_comparison.png b/examples/paper/figures/example01/fig03_piecewise_baseline_comparison.png new file mode 100644 index 00000000..c9ef13ca Binary files /dev/null and b/examples/paper/figures/example01/fig03_piecewise_baseline_comparison.png differ diff --git a/examples/paper/figures/example02/fig01_data_overview.png b/examples/paper/figures/example02/fig01_data_overview.png new file mode 100644 index 00000000..de99cf48 Binary files /dev/null and b/examples/paper/figures/example02/fig01_data_overview.png differ diff --git a/examples/paper/figures/example02/fig02_lag_and_model_comparison.png b/examples/paper/figures/example02/fig02_lag_and_model_comparison.png new file mode 100644 index 00000000..a18f154e Binary files /dev/null and b/examples/paper/figures/example02/fig02_lag_and_model_comparison.png differ diff --git a/examples/paper/figures/example03/fig01_simulated_and_real_rasters.png b/examples/paper/figures/example03/fig01_simulated_and_real_rasters.png new file mode 100644 index 00000000..0bb68340 Binary files /dev/null and b/examples/paper/figures/example03/fig01_simulated_and_real_rasters.png differ diff --git a/examples/paper/figures/example03/fig02_psth_comparison.png b/examples/paper/figures/example03/fig02_psth_comparison.png new file mode 100644 index 00000000..a7457956 Binary files /dev/null and b/examples/paper/figures/example03/fig02_psth_comparison.png differ diff --git a/examples/paper/figures/example03/fig03_ssglm_simulation_summary.png b/examples/paper/figures/example03/fig03_ssglm_simulation_summary.png new file mode 100644 index 00000000..0e0864ca Binary files /dev/null and b/examples/paper/figures/example03/fig03_ssglm_simulation_summary.png differ diff --git a/examples/paper/figures/example03/fig04_ssglm_fit_diagnostics.png b/examples/paper/figures/example03/fig04_ssglm_fit_diagnostics.png new file mode 100644 index 00000000..1ac75c44 Binary files /dev/null and b/examples/paper/figures/example03/fig04_ssglm_fit_diagnostics.png differ diff --git a/examples/paper/figures/example03/fig05_stimulus_effect_surfaces.png b/examples/paper/figures/example03/fig05_stimulus_effect_surfaces.png new file mode 100644 index 00000000..3f9fd4ad Binary files /dev/null and b/examples/paper/figures/example03/fig05_stimulus_effect_surfaces.png differ diff --git a/examples/paper/figures/example03/fig06_learning_trial_comparison.png b/examples/paper/figures/example03/fig06_learning_trial_comparison.png new file mode 100644 index 00000000..34edb2a4 Binary files /dev/null and b/examples/paper/figures/example03/fig06_learning_trial_comparison.png differ diff --git a/examples/paper/figures/example04/fig01_example_cells_path_overlay.png b/examples/paper/figures/example04/fig01_example_cells_path_overlay.png new file mode 100644 index 00000000..a05302b9 Binary files /dev/null and b/examples/paper/figures/example04/fig01_example_cells_path_overlay.png differ diff --git a/examples/paper/figures/example04/fig02_model_summary_statistics.png b/examples/paper/figures/example04/fig02_model_summary_statistics.png new file mode 100644 index 00000000..a53e29d7 Binary files /dev/null and b/examples/paper/figures/example04/fig02_model_summary_statistics.png differ diff --git a/examples/paper/figures/example04/fig03_gaussian_place_fields_animal1.png b/examples/paper/figures/example04/fig03_gaussian_place_fields_animal1.png new file mode 100644 index 00000000..86b8b753 Binary files /dev/null and b/examples/paper/figures/example04/fig03_gaussian_place_fields_animal1.png differ diff --git a/examples/paper/figures/example04/fig04_zernike_place_fields_animal1.png b/examples/paper/figures/example04/fig04_zernike_place_fields_animal1.png new file mode 100644 index 00000000..e3cda44b Binary files /dev/null and b/examples/paper/figures/example04/fig04_zernike_place_fields_animal1.png differ diff --git a/examples/paper/figures/example04/fig05_gaussian_place_fields_animal2.png b/examples/paper/figures/example04/fig05_gaussian_place_fields_animal2.png new file mode 100644 index 00000000..acadca6f Binary files /dev/null and b/examples/paper/figures/example04/fig05_gaussian_place_fields_animal2.png differ diff --git a/examples/paper/figures/example04/fig06_zernike_place_fields_animal2.png b/examples/paper/figures/example04/fig06_zernike_place_fields_animal2.png new file mode 100644 index 00000000..3931b1df Binary files /dev/null and b/examples/paper/figures/example04/fig06_zernike_place_fields_animal2.png differ diff --git a/examples/paper/figures/example04/fig07_example_cell_mesh_comparison.png b/examples/paper/figures/example04/fig07_example_cell_mesh_comparison.png new file mode 100644 index 00000000..61a3dbdf Binary files /dev/null and b/examples/paper/figures/example04/fig07_example_cell_mesh_comparison.png differ diff --git a/examples/paper/figures/example05/fig01_univariate_setup.png b/examples/paper/figures/example05/fig01_univariate_setup.png new file mode 100644 index 00000000..94dc2a8f Binary files /dev/null and b/examples/paper/figures/example05/fig01_univariate_setup.png differ diff --git a/examples/paper/figures/example05/fig02_univariate_decoding.png b/examples/paper/figures/example05/fig02_univariate_decoding.png new file mode 100644 index 00000000..fbee2a41 Binary files /dev/null and b/examples/paper/figures/example05/fig02_univariate_decoding.png differ diff --git a/examples/paper/figures/example05/fig03_reach_and_population_setup.png b/examples/paper/figures/example05/fig03_reach_and_population_setup.png new file mode 100644 index 00000000..840a1077 Binary files /dev/null and b/examples/paper/figures/example05/fig03_reach_and_population_setup.png differ diff --git a/examples/paper/figures/example05/fig04_ppaf_goal_vs_free.png b/examples/paper/figures/example05/fig04_ppaf_goal_vs_free.png new file mode 100644 index 00000000..3939f423 Binary files /dev/null and b/examples/paper/figures/example05/fig04_ppaf_goal_vs_free.png differ diff --git a/examples/paper/figures/example05/fig05_hybrid_setup.png b/examples/paper/figures/example05/fig05_hybrid_setup.png new file mode 100644 index 00000000..09e606a8 Binary files /dev/null and b/examples/paper/figures/example05/fig05_hybrid_setup.png differ diff --git a/examples/paper/figures/example05/fig06_hybrid_decoding_summary.png b/examples/paper/figures/example05/fig06_hybrid_decoding_summary.png new file mode 100644 index 00000000..0670ab79 Binary files /dev/null and b/examples/paper/figures/example05/fig06_hybrid_decoding_summary.png differ diff --git a/notebooks/AnalysisExamples.ipynb b/notebooks/AnalysisExamples.ipynb index 64b86ee9..7312e3f6 100644 --- a/notebooks/AnalysisExamples.ipynb +++ b/notebooks/AnalysisExamples.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "7807842d", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:27.936748Z", + "iopub.status.busy": "2026-03-14T15:32:27.936516Z", + "iopub.status.idle": "2026-03-14T15:32:30.740702Z", + "shell.execute_reply": "2026-03-14T15:32:30.740115Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: AnalysisExamples\n", @@ -73,10 +80,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "37ac20c9", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.742159Z", + "iopub.status.busy": "2026-03-14T15:32:30.742008Z", + "iopub.status.idle": "2026-03-14T15:32:30.744154Z", + "shell.execute_reply": "2026-03-14T15:32:30.743787Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'n_samples': 41348, 'n_spikes': 2614, 'sample_rate_hz': 30.0}\n" + ] + } + ], "source": [ "# SECTION 1: Analysis Examples\n", "plt.close(\"all\")\n", @@ -85,9 +107,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "dbdc74f9", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.745260Z", + "iopub.status.busy": "2026-03-14T15:32:30.745181Z", + "iopub.status.idle": "2026-03-14T15:32:30.766757Z", + "shell.execute_reply": "2026-03-14T15:32:30.766340Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Example 1: Tradition Preliminary Analysis\n", @@ -109,10 +138,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "5c38cff1", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.768114Z", + "iopub.status.busy": "2026-03-14T15:32:30.768031Z", + "iopub.status.idle": "2026-03-14T15:32:30.788529Z", + "shell.execute_reply": "2026-03-14T15:32:30.788068Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Rat trajectory with spike locations')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 3: visualize the raw data\n", "fig = _prepare_figure(\"figure; plot(xN,yN,x_at_spiketimes,y_at_spiketimes,'r.')\", figsize=(6.5, 6.0))\n", @@ -127,10 +174,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "5af52914", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.789592Z", + "iopub.status.busy": "2026-03-14T15:32:30.789510Z", + "iopub.status.idle": "2026-03-14T15:32:30.909629Z", + "shell.execute_reply": "2026-03-14T15:32:30.909197Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Quadratic GLM coefficients')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: fit a GLM model to the x and y positions\n", "fig = _prepare_figure(\"figure; errorbar(1:length(b), b, stats.se,'.')\", figsize=(7.0, 4.5))\n", @@ -146,10 +211,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "5cac7309", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.910876Z", + "iopub.status.busy": "2026-03-14T15:32:30.910794Z", + "iopub.status.idle": "2026-03-14T15:32:30.986012Z", + "shell.execute_reply": "2026-03-14T15:32:30.985609Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.92, 'Quadratic GLM spatial intensity')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: visualize your model\n", "fig = _prepare_figure(\"figure; mesh(x_new,y_new,lambda,'AlphaData',0)\", figsize=(8.0, 6.5))\n", @@ -171,10 +254,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "36dd8e70", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.987111Z", + "iopub.status.busy": "2026-03-14T15:32:30.987031Z", + "iopub.status.idle": "2026-03-14T15:32:30.989984Z", + "shell.execute_reply": "2026-03-14T15:32:30.989620Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'linear_mean_rate_hz': 1.8966, 'quadratic_mean_rate_hz': 1.8966}\n" + ] + } + ], "source": [ "# SECTION 6: Compare a linear model versus a Gaussian GLM model\n", "lambda_linear_hz = linear_fit.predict_rate(x_linear) * sample_rate\n", @@ -191,9 +289,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "2d8b81fd", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:30.990860Z", + "iopub.status.busy": "2026-03-14T15:32:30.990794Z", + "iopub.status.idle": "2026-03-14T15:32:33.490774Z", + "shell.execute_reply": "2026-03-14T15:32:33.490300Z" + } + }, "outputs": [], "source": [ "# SECTION 7: Make the KS Plot\n", @@ -223,7 +328,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 4, diff --git a/notebooks/AnalysisExamples2.ipynb b/notebooks/AnalysisExamples2.ipynb index 426a9bd8..83ce0d7b 100644 --- a/notebooks/AnalysisExamples2.ipynb +++ b/notebooks/AnalysisExamples2.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "62e21501", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:43.427073Z", + "iopub.status.busy": "2026-03-14T15:32:43.426769Z", + "iopub.status.idle": "2026-03-14T15:32:45.401143Z", + "shell.execute_reply": "2026-03-14T15:32:45.400597Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: AnalysisExamples2\n", @@ -70,10 +77,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "1836e297", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:45.402632Z", + "iopub.status.busy": "2026-03-14T15:32:45.402504Z", + "iopub.status.idle": "2026-03-14T15:32:45.404599Z", + "shell.execute_reply": "2026-03-14T15:32:45.404221Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'n_samples': 41348, 'n_spikes': 2614, 'analysis_sample_rate_hz': 1000.0}\n" + ] + } + ], "source": [ "# SECTION 1: Analysis Examples 2\n", "plt.close(\"all\")\n", @@ -82,10 +104,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "bf657cd0", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:45.405603Z", + "iopub.status.busy": "2026-03-14T15:32:45.405536Z", + "iopub.status.idle": "2026-03-14T15:32:45.407326Z", + "shell.execute_reply": "2026-03-14T15:32:45.407011Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'position_shape': [41348, 2], 'velocity_shape': [41348, 2], 'radial_shape': [41348, 5]}\n" + ] + } + ], "source": [ "# SECTION 2: load the rat trajectory and spiking data\n", "print({\"position_shape\": list(position.data.shape), \"velocity_shape\": list(velocity.data.shape), \"radial_shape\": list(radial.data.shape)})" @@ -93,10 +130,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "fe47aacc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:45.408309Z", + "iopub.status.busy": "2026-03-14T15:32:45.408238Z", + "iopub.status.idle": "2026-03-14T15:32:45.410083Z", + "shell.execute_reply": "2026-03-14T15:32:45.409748Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'direct_spike_position_head': [[-0.8536, 0.1573], [-0.666, 0.5866], [-0.6406, 0.6071]], 'upsampled_spike_position_head': [[-0.855, 0.1541], [-0.6636, 0.5889], [-0.6389, 0.6084]]}\n" + ] + } + ], "source": [ "# SECTION 3: interpolate the covariates at the spike times\n", "print(\n", @@ -109,10 +161,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "2f28e3be", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:45.411119Z", + "iopub.status.busy": "2026-03-14T15:32:45.411048Z", + "iopub.status.idle": "2026-03-14T15:32:45.428209Z", + "shell.execute_reply": "2026-03-14T15:32:45.427818Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Trajectory and interpolated spike locations')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: visualize the raw data\n", "fig = _prepare_figure(\"figure; plot(position.getSubSignal('x').dataToMatrix,...)\", figsize=(6.5, 6.0))\n", @@ -127,9 +197,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "d40c40c9", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:45.429253Z", + "iopub.status.busy": "2026-03-14T15:32:45.429186Z", + "iopub.status.idle": "2026-03-14T15:32:50.075260Z", + "shell.execute_reply": "2026-03-14T15:32:50.074822Z" + } + }, "outputs": [], "source": [ "# SECTION 5: Create a trial object and define the fits that we want to run\n", @@ -146,10 +223,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "f9160bdb", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:32:50.076746Z", + "iopub.status.busy": "2026-03-14T15:32:50.076667Z", + "iopub.status.idle": "2026-03-14T15:34:22.167697Z", + "shell.execute_reply": "2026-03-14T15:34:22.167307Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'config_names': ['Linear', 'Quadratic', 'Quadratic+Hist'], 'aic': [31007.819, 30961.784, 30960.663]}\n" + ] + } + ], "source": [ "# SECTION 6: Create our collection of configurations and run the analysis\n", "fitResults = Analysis.RunAnalysisForAllNeurons(trial, tcc, 0)\n", @@ -160,10 +252,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "a2fafcc8", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:34:22.169098Z", + "iopub.status.busy": "2026-03-14T15:34:22.169019Z", + "iopub.status.idle": "2026-03-14T15:34:22.378149Z", + "shell.execute_reply": "2026-03-14T15:34:22.377711Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.92, 'Toolbox-model spatial intensity comparison')" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 7: Visualize the firing rates as a function of the spatial covariates\n", "fig = _prepare_figure(\"mesh(x_new,y_new,lambda)\", figsize=(9.0, 6.5))\n", @@ -183,18 +293,58 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "64cdac30", - "metadata": {}, - "outputs": [], - "source": "# SECTION 8: Toolbox vs. Standard GLM comparison\n# Compare the nSTAT fit with a standalone glmfit using the same Quadratic covariates\n# MATLAB: [b,dev,stats] = glmfit([xN yN xN.^2 yN.^2 xN.*yN], spikes_binned, 'poisson');\n# b - fitResults.b{2} % should be close to zero\nX_quad = np.column_stack([xN, yN, xN**2, yN**2, xN * yN])\nglm_result = fit_poisson_glm(X_quad, spikes_binned)\nb = np.concatenate([[glm_result.intercept], glm_result.coefficients])\nb_diff = b - fitResults.getCoeffs(2)\nprint(\"b - fitResults.b{2} =\", b_diff)" + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:34:22.379530Z", + "iopub.status.busy": "2026-03-14T15:34:22.379456Z", + "iopub.status.idle": "2026-03-14T15:34:22.388199Z", + "shell.execute_reply": "2026-03-14T15:34:22.387760Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b - fitResults.b{2} = [ 4.23646893 -1.45765976 -3.22632654 -6.34196965 -4.18056197 -0.36152565]\n" + ] + } + ], + "source": [ + "# SECTION 8: Toolbox vs. Standard GLM comparison\n", + "# Compare the nSTAT fit with a standalone glmfit using the same Quadratic covariates\n", + "# MATLAB: [b,dev,stats] = glmfit([xN yN xN.^2 yN.^2 xN.*yN], spikes_binned, 'poisson');\n", + "# b - fitResults.b{2} % should be close to zero\n", + "X_quad = np.column_stack([xN, yN, xN**2, yN**2, xN * yN])\n", + "glm_result = fit_poisson_glm(X_quad, spikes_binned)\n", + "b = np.concatenate([[glm_result.intercept], glm_result.coefficients])\n", + "b_diff = b - fitResults.getCoeffs(2)\n", + "print(\"b - fitResults.b{2} =\", b_diff)" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "8782d383", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:34:22.389149Z", + "iopub.status.busy": "2026-03-14T15:34:22.389082Z", + "iopub.status.idle": "2026-03-14T15:39:05.300812Z", + "shell.execute_reply": "2026-03-14T15:39:05.300400Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'history_config_names': ['Baseline', 'Window1', 'Window2', 'Window3', 'Window4', 'Window5', 'Window6', 'Window7', 'Window8', 'Window9', 'Window10'], 'summary_fit_names': ['Baseline', 'Window1', 'Window2', 'Window3', 'Window4', 'Window5', 'Window6', 'Window7', 'Window8', 'Window9', 'Window10']}\n" + ] + } + ], "source": [ "# SECTION 9: Compute the history effect\n", "windowTimes = np.arange(0.0, 11.0) / sample_rate\n", @@ -214,7 +364,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 5, @@ -225,4 +384,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/ConfidenceIntervalOverview.ipynb b/notebooks/ConfidenceIntervalOverview.ipynb index ba0ac853..bc2ad67e 100644 --- a/notebooks/ConfidenceIntervalOverview.ipynb +++ b/notebooks/ConfidenceIntervalOverview.ipynb @@ -22,10 +22,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "e50c571b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:39:16.390512Z", + "iopub.status.busy": "2026-03-14T15:39:16.390159Z", + "iopub.status.idle": "2026-03-14T15:39:18.763073Z", + "shell.execute_reply": "2026-03-14T15:39:18.762299Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from __future__ import annotations\n", "\n", @@ -54,10 +72,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "badf84eb", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:39:18.765705Z", + "iopub.status.busy": "2026-03-14T15:39:18.765445Z", + "iopub.status.idle": "2026-03-14T15:39:18.769078Z", + "shell.execute_reply": "2026-03-14T15:39:18.768482Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'time': [0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0],\n", + " 'lower': [-0.15, 0.801, 0.438, -0.738, -1.101, -0.15],\n", + " 'upper': [0.15, 1.101, 0.738, -0.438, -0.801, 0.15],\n", + " 'color': 'tab:blue'}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "{\n", " \"time\": ci.time.tolist(),\n", @@ -79,10 +118,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "270611b9", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:39:18.770756Z", + "iopub.status.busy": "2026-03-14T15:39:18.770644Z", + "iopub.status.idle": "2026-03-14T15:39:18.773414Z", + "shell.execute_reply": "2026-03-14T15:39:18.772928Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError\n", + "bounds must have shape (n, 2)\n" + ] + } + ], "source": [ "try:\n", " ConfidenceInterval(time, np.ones((len(time), 3)))\n", @@ -111,8 +166,16 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "version": "3.12" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/ConfigCollExamples.ipynb b/notebooks/ConfigCollExamples.ipynb index 00ba6d08..c1f31483 100644 --- a/notebooks/ConfigCollExamples.ipynb +++ b/notebooks/ConfigCollExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "2c49c458", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:39:31.737054Z", + "iopub.status.busy": "2026-03-14T15:39:31.736776Z", + "iopub.status.idle": "2026-03-14T15:39:34.206271Z", + "shell.execute_reply": "2026-03-14T15:39:34.205172Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: ConfigCollExamples\n", @@ -38,7 +45,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/CovCollExamples.ipynb b/notebooks/CovCollExamples.ipynb index a4b77da7..08527231 100644 --- a/notebooks/CovCollExamples.ipynb +++ b/notebooks/CovCollExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "4bd1f199", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:39:45.988010Z", + "iopub.status.busy": "2026-03-14T15:39:45.987742Z", + "iopub.status.idle": "2026-03-14T15:39:48.153990Z", + "shell.execute_reply": "2026-03-14T15:39:48.153520Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: CovCollExamples\n", @@ -68,7 +75,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 2, diff --git a/notebooks/CovariateExamples.ipynb b/notebooks/CovariateExamples.ipynb index 9bdcd952..a2e18352 100644 --- a/notebooks/CovariateExamples.ipynb +++ b/notebooks/CovariateExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "e304053b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:00.565371Z", + "iopub.status.busy": "2026-03-14T15:40:00.565075Z", + "iopub.status.idle": "2026-03-14T15:40:02.823504Z", + "shell.execute_reply": "2026-03-14T15:40:02.822724Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: CovariateExamples\n", @@ -36,9 +43,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "9be9e8f7", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:02.826781Z", + "iopub.status.busy": "2026-03-14T15:40:02.826517Z", + "iopub.status.idle": "2026-03-14T15:40:02.915172Z", + "shell.execute_reply": "2026-03-14T15:40:02.914725Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example 1: Using Covariates\n", @@ -66,7 +80,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 2, diff --git a/notebooks/DecodingExample.ipynb b/notebooks/DecodingExample.ipynb index bbb4eab6..df4df538 100644 --- a/notebooks/DecodingExample.ipynb +++ b/notebooks/DecodingExample.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "44d88ec9", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:13.804860Z", + "iopub.status.busy": "2026-03-14T15:40:13.804571Z", + "iopub.status.idle": "2026-03-14T15:40:16.103629Z", + "shell.execute_reply": "2026-03-14T15:40:16.103092Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: DecodingExample\n", @@ -78,10 +85,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "2f1ec431", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:16.105260Z", + "iopub.status.busy": "2026-03-14T15:40:16.105098Z", + "iopub.status.idle": "2026-03-14T15:40:16.443646Z", + "shell.execute_reply": "2026-03-14T15:40:16.443185Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Hz')" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 1: Generate the conditional Intensity Function\n", "plt.close(\"all\")\n", @@ -111,10 +136,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "a7048402", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:16.445079Z", + "iopub.status.busy": "2026-03-14T15:40:16.444997Z", + "iopub.status.idle": "2026-03-14T15:40:36.287761Z", + "shell.execute_reply": "2026-03-14T15:40:36.287351Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Mean log-likelihood across neurons')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 2: Fit a model to the spikedata to obtain a model CIF\n", "stim = Covariate(time, x, \"Stimulus\", \"time\", \"s\", \"V\", [\"stim\"])\n", @@ -175,9 +218,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "df079e59", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:36.289103Z", + "iopub.status.busy": "2026-03-14T15:40:36.289003Z", + "iopub.status.idle": "2026-03-14T15:40:37.768356Z", + "shell.execute_reply": "2026-03-14T15:40:37.767817Z" + } + }, "outputs": [], "source": [ "# SECTION 3: Decode the stimulus from the fitted CIF\n", @@ -200,7 +250,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 5, diff --git a/notebooks/DecodingExampleWithHist.ipynb b/notebooks/DecodingExampleWithHist.ipynb index eb2ecffc..61b7f27e 100644 --- a/notebooks/DecodingExampleWithHist.ipynb +++ b/notebooks/DecodingExampleWithHist.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a847f096", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:44.311665Z", + "iopub.status.busy": "2026-03-14T15:40:44.311390Z", + "iopub.status.idle": "2026-03-14T15:40:46.493379Z", + "shell.execute_reply": "2026-03-14T15:40:46.492541Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: DecodingExampleWithHist\n", @@ -95,9 +102,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "b9bfa418", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:40:46.496323Z", + "iopub.status.busy": "2026-03-14T15:40:46.496041Z", + "iopub.status.idle": "2026-03-14T15:43:34.976298Z", + "shell.execute_reply": "2026-03-14T15:43:34.975912Z" + } + }, "outputs": [], "source": [ "# SECTION 1: History-aware decoding workflow\n", @@ -158,7 +172,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 2, diff --git a/notebooks/EventsExamples.ipynb b/notebooks/EventsExamples.ipynb index c5bff681..445a211e 100644 --- a/notebooks/EventsExamples.ipynb +++ b/notebooks/EventsExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b27ad078", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:39.760409Z", + "iopub.status.busy": "2026-03-14T15:43:39.760306Z", + "iopub.status.idle": "2026-03-14T15:43:42.165621Z", + "shell.execute_reply": "2026-03-14T15:43:42.165139Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: EventsExamples\n", @@ -62,7 +69,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 3, diff --git a/notebooks/ExplicitStimulusWhiskerData.ipynb b/notebooks/ExplicitStimulusWhiskerData.ipynb index 06b995a6..7bfe41f7 100644 --- a/notebooks/ExplicitStimulusWhiskerData.ipynb +++ b/notebooks/ExplicitStimulusWhiskerData.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "e102e8bb", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:47.918144Z", + "iopub.status.busy": "2026-03-14T15:43:47.917909Z", + "iopub.status.idle": "2026-03-14T15:43:50.177531Z", + "shell.execute_reply": "2026-03-14T15:43:50.177075Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: ExplicitStimulusWhiskerData\n", @@ -61,7 +68,7 @@ " ideal_arr = np.asarray(ideal, dtype=float)\n", " empirical_arr = np.asarray(empirical, dtype=float)\n", " ci_arr = np.asarray(ci, dtype=float)\n", - " ax.plot(ideal_arr, ideal_arr, color=\"0.2\", linewidth=1.0, linestyle=\"--\", label=\"45\u00b0 line\")\n", + " ax.plot(ideal_arr, ideal_arr, color=\"0.2\", linewidth=1.0, linestyle=\"--\", label=\"45° line\")\n", " ax.plot(ideal_arr, empirical_arr, color=color, linewidth=1.5, label=label)\n", " ax.fill_between(\n", " ideal_arr,\n", @@ -79,10 +86,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "45734023", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:50.179569Z", + "iopub.status.busy": "2026-03-14T15:43:50.179410Z", + "iopub.status.idle": "2026-03-14T15:43:50.670983Z", + "shell.execute_reply": "2026-03-14T15:43:50.670428Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'n_samples': 51000, 'peak_lag_ms': 119.0, 'best_history_window_bins': 7}\n" + ] + } + ], "source": [ "# SECTION 0: EXPLICIT STIMULUS EXAMPLE - WHISKER STIMULATION/THALAMIC NEURON\n", "# This notebook follows the MATLAB helpfile workflow for explicit whisker-stimulation analysis.\n", @@ -102,10 +124,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "0e41ab95", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:50.672354Z", + "iopub.status.busy": "2026-03-14T15:43:50.672253Z", + "iopub.status.idle": "2026-03-14T15:43:50.851243Z", + "shell.execute_reply": "2026-03-14T15:43:50.850755Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 1: Load the data\n", "fig = _prepare_figure(\"trial.plot\", figsize=(10.0, 6.0))\n", @@ -130,10 +170,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "6c2191fc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:50.852594Z", + "iopub.status.busy": "2026-03-14T15:43:50.852489Z", + "iopub.status.idle": "2026-03-14T15:43:50.969714Z", + "shell.execute_reply": "2026-03-14T15:43:50.969298Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 2: Fit a constant baseline\n", "fig = _prepare_figure(\"results.plotResults\", figsize=(6.0, 5.5))\n", @@ -145,10 +203,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "da4b0be4", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:50.971192Z", + "iopub.status.busy": "2026-03-14T15:43:50.971093Z", + "iopub.status.idle": "2026-03-14T15:43:51.033897Z", + "shell.execute_reply": "2026-03-14T15:43:51.033306Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'cross-covariance')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 3: Find Stimulus Lag\n", "fig = _prepare_figure(\"results.Residual.xcov(stim).windowedSignal([0,1]).plot\", figsize=(8.5, 4.5))\n", @@ -166,10 +242,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "334952df", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:51.035271Z", + "iopub.status.busy": "2026-03-14T15:43:51.035160Z", + "iopub.status.idle": "2026-03-14T15:43:51.164920Z", + "shell.execute_reply": "2026-03-14T15:43:51.164559Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: Compare constant rate model with model including stimulus effect\n", "fig = _prepare_figure(\"results.plotResults\", figsize=(8.5, 4.5))\n", @@ -194,10 +288,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "eb6dc162", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:51.166454Z", + "iopub.status.busy": "2026-03-14T15:43:51.166353Z", + "iopub.status.idle": "2026-03-14T15:43:51.332855Z", + "shell.execute_reply": "2026-03-14T15:43:51.332378Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'ΔBIC relative to first history model')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: History Effect\n", "fig = _prepare_figure(\"Summary.plotSummary\", figsize=(9.0, 7.0))\n", @@ -209,10 +321,10 @@ "axs[0].set_title(\"History-window scan\")\n", "axs[1].plot(history_windows, payload[\"delta_aic\"], marker=\"o\", color=\"tab:green\", linewidth=1.2)\n", "axs[1].scatter([history_windows[best_history_idx]], [payload[\"delta_aic\"][best_history_idx]], color=\"tab:red\", zorder=3)\n", - "axs[1].set_ylabel(\"\u0394AIC\")\n", + "axs[1].set_ylabel(\"ΔAIC\")\n", "axs[2].plot(history_windows, payload[\"delta_bic\"], marker=\"o\", color=\"tab:brown\", linewidth=1.2)\n", "axs[2].scatter([history_windows[best_history_idx]], [payload[\"delta_bic\"][best_history_idx]], color=\"tab:red\", zorder=3)\n", - "axs[2].set_ylabel(\"\u0394BIC\")\n", + "axs[2].set_ylabel(\"ΔBIC\")\n", "axs[2].set_xlabel(\"history window count\")\n", "\n", "fig = _prepare_figure(\"plot(x,dBIC,'.')\", figsize=(8.0, 4.5))\n", @@ -221,14 +333,21 @@ "ax.axvline(history_windows[best_history_idx], color=\"tab:red\", linestyle=\"--\", linewidth=1.0)\n", "ax.set_title(\"BIC improvement across history-window choices\")\n", "ax.set_xlabel(\"history window count\")\n", - "ax.set_ylabel(\"\u0394BIC relative to first history model\")" + "ax.set_ylabel(\"ΔBIC relative to first history model\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "09de73d5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:51.334188Z", + "iopub.status.busy": "2026-03-14T15:43:51.334105Z", + "iopub.status.idle": "2026-03-14T15:43:51.505289Z", + "shell.execute_reply": "2026-03-14T15:43:51.504798Z" + } + }, "outputs": [], "source": [ "# SECTION 6: Compare Baseline, Baseline+Stimulus Model, Baseline+History+Stimulus\n", @@ -276,7 +395,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 9, @@ -287,4 +415,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/FitResSummaryExamples.ipynb b/notebooks/FitResSummaryExamples.ipynb index 79b17dd3..bddd8e21 100644 --- a/notebooks/FitResSummaryExamples.ipynb +++ b/notebooks/FitResSummaryExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "4b34dc65", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:43:59.849416Z", + "iopub.status.busy": "2026-03-14T15:43:59.849007Z", + "iopub.status.idle": "2026-03-14T15:44:02.102892Z", + "shell.execute_reply": "2026-03-14T15:44:02.102410Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: FitResSummaryExamples\n", @@ -54,7 +61,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/FitResultExamples.ipynb b/notebooks/FitResultExamples.ipynb index 03e97687..74a1822e 100644 --- a/notebooks/FitResultExamples.ipynb +++ b/notebooks/FitResultExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "0e277f93", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:05.938150Z", + "iopub.status.busy": "2026-03-14T15:44:05.937857Z", + "iopub.status.idle": "2026-03-14T15:44:08.249285Z", + "shell.execute_reply": "2026-03-14T15:44:08.248213Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: FitResultExamples\n", @@ -54,7 +61,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/FitResultReference.ipynb b/notebooks/FitResultReference.ipynb index c3992b4b..0806a20d 100644 --- a/notebooks/FitResultReference.ipynb +++ b/notebooks/FitResultReference.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "70940850", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:13.523805Z", + "iopub.status.busy": "2026-03-14T15:44:13.523686Z", + "iopub.status.idle": "2026-03-14T15:44:15.634065Z", + "shell.execute_reply": "2026-03-14T15:44:15.633442Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: FitResultReference\n", @@ -51,9 +58,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "8ae1a337", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:15.636690Z", + "iopub.status.busy": "2026-03-14T15:44:15.636452Z", + "iopub.status.idle": "2026-03-14T15:44:15.640678Z", + "shell.execute_reply": "2026-03-14T15:44:15.640086Z" + } + }, "outputs": [], "source": [ "# SECTION 1: FitResult Reference\n", @@ -75,7 +89,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/HippocampalPlaceCellExample.ipynb b/notebooks/HippocampalPlaceCellExample.ipynb index d9ef8901..1b27cd69 100644 --- a/notebooks/HippocampalPlaceCellExample.ipynb +++ b/notebooks/HippocampalPlaceCellExample.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "15ef53db", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:19.977910Z", + "iopub.status.busy": "2026-03-14T15:44:19.977661Z", + "iopub.status.idle": "2026-03-14T15:44:22.250484Z", + "shell.execute_reply": "2026-03-14T15:44:22.250074Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: HippocampalPlaceCellExample\n", @@ -81,10 +88,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "30094bfc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:22.251983Z", + "iopub.status.busy": "2026-03-14T15:44:22.251849Z", + "iopub.status.idle": "2026-03-14T15:44:22.402307Z", + "shell.execute_reply": "2026-03-14T15:44:22.401836Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_cells_fit': 8, 'mean_delta_aic': 87.651, 'mean_delta_bic': 44.358}\n" + ] + } + ], "source": [ "# SECTION 0: HIPPOCAMPAL PLACE CELL - RECEPTIVE FIELD ESTIMATION\n", "# This notebook mirrors the MATLAB place-cell helpfile using the dataset-backed Python workflow.\n", @@ -101,9 +123,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "33671840", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:22.403587Z", + "iopub.status.busy": "2026-03-14T15:44:22.403505Z", + "iopub.status.idle": "2026-03-14T15:44:22.420874Z", + "shell.execute_reply": "2026-03-14T15:44:22.420483Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example Data\n", @@ -121,10 +150,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "081e6179", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:22.422106Z", + "iopub.status.busy": "2026-03-14T15:44:22.422032Z", + "iopub.status.idle": "2026-03-14T15:44:22.570126Z", + "shell.execute_reply": "2026-03-14T15:44:22.569661Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Animal 1 BIC')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 2: Analyze All Cells\n", "fig = _prepare_figure(\"Summary.plotSummary\", figsize=(12.0, 4.5))\n", @@ -145,10 +192,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "aa269c6b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:22.571509Z", + "iopub.status.busy": "2026-03-14T15:44:22.571408Z", + "iopub.status.idle": "2026-03-14T15:44:22.648867Z", + "shell.execute_reply": "2026-03-14T15:44:22.648424Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Animal 2 BIC')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 3: View Summary Statistics\n", "fig = _prepare_figure(\"Summary.plotSummary\", figsize=(12.0, 4.5))\n", @@ -169,16 +234,98 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "26aafec5", - "metadata": {}, - "outputs": [], - "source": "# SECTION 4: Visualize the results\nfig = _prepare_figure(\"h4=figure(4)\", figsize=(8.5, 8.0))\n_plot_field_grid(fig, \"animal1\", \"gaussian_fields\", \"Gaussian place fields - Animal 1\")\n\nfig = _prepare_figure(\"h5=figure(5)\", figsize=(8.5, 8.0))\n_plot_field_grid(fig, \"animal1\", \"zernike_fields\", \"Zernike place fields - Animal 1\")\n\nfig = _prepare_figure(\"h6=figure(6)\", figsize=(8.5, 8.0))\n_plot_field_grid(fig, \"animal2\", \"gaussian_fields\", \"Gaussian place fields - Animal 2\")\n\nfig = _prepare_figure(\"h7=figure(7)\", figsize=(8.5, 8.0))\n_plot_field_grid(fig, \"animal2\", \"zernike_fields\", \"Zernike place fields - Animal 2\")\n\nfig = _prepare_figure(\"figure(8)\", figsize=(7.0, 5.5))\nax = fig.subplots(1, 1)\nax.imshow(\n mesh[\"gaussian_field\"],\n origin=\"lower\",\n extent=[float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))],\n aspect=\"equal\",\n cmap=\"viridis\",\n)\nax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\nax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\nax.set_title(f\"Gaussian receptive field - Cell {int(mesh['cell_index']) + 1}\")\nax.set_xlabel(\"x\")\nax.set_ylabel(\"y\")\n\nfig = _prepare_figure(\"figure(9)\", figsize=(7.0, 5.5))\nax = fig.subplots(1, 1)\nax.imshow(\n mesh[\"zernike_field\"],\n origin=\"lower\",\n extent=[float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))],\n aspect=\"equal\",\n cmap=\"viridis\",\n)\nax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\nax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\nax.set_title(f\"Zernike receptive field - Cell {int(mesh['cell_index']) + 1}\")\nax.set_xlabel(\"x\")\nax.set_ylabel(\"y\")\n\n# figure(9) overlay — matches MATLAB hold-on composite (published as _10.png)\nfig = _prepare_figure(\"figure(9) overlay\", figsize=(10.0, 5.0))\naxs = fig.subplots(1, 2)\next = [float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))]\nfor ax, field, label in zip(axs, [mesh[\"gaussian_field\"], mesh[\"zernike_field\"]], [\"Gaussian\", \"Zernike\"]):\n ax.imshow(field, origin=\"lower\", extent=ext, aspect=\"equal\", cmap=\"viridis\")\n ax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\n ax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\n ax.set_title(f\"{label} - Cell {int(mesh['cell_index']) + 1}\")\n ax.set_xlabel(\"x\")\n ax.set_ylabel(\"y\")\n\n__tracker.finalize()" + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:22.650439Z", + "iopub.status.busy": "2026-03-14T15:44:22.650324Z", + "iopub.status.idle": "2026-03-14T15:44:23.601656Z", + "shell.execute_reply": "2026-03-14T15:44:23.601164Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " self._active_fig.tight_layout()\n" + ] + } + ], + "source": [ + "# SECTION 4: Visualize the results\n", + "fig = _prepare_figure(\"h4=figure(4)\", figsize=(8.5, 8.0))\n", + "_plot_field_grid(fig, \"animal1\", \"gaussian_fields\", \"Gaussian place fields - Animal 1\")\n", + "\n", + "fig = _prepare_figure(\"h5=figure(5)\", figsize=(8.5, 8.0))\n", + "_plot_field_grid(fig, \"animal1\", \"zernike_fields\", \"Zernike place fields - Animal 1\")\n", + "\n", + "fig = _prepare_figure(\"h6=figure(6)\", figsize=(8.5, 8.0))\n", + "_plot_field_grid(fig, \"animal2\", \"gaussian_fields\", \"Gaussian place fields - Animal 2\")\n", + "\n", + "fig = _prepare_figure(\"h7=figure(7)\", figsize=(8.5, 8.0))\n", + "_plot_field_grid(fig, \"animal2\", \"zernike_fields\", \"Zernike place fields - Animal 2\")\n", + "\n", + "fig = _prepare_figure(\"figure(8)\", figsize=(7.0, 5.5))\n", + "ax = fig.subplots(1, 1)\n", + "ax.imshow(\n", + " mesh[\"gaussian_field\"],\n", + " origin=\"lower\",\n", + " extent=[float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))],\n", + " aspect=\"equal\",\n", + " cmap=\"viridis\",\n", + ")\n", + "ax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\n", + "ax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\n", + "ax.set_title(f\"Gaussian receptive field - Cell {int(mesh['cell_index']) + 1}\")\n", + "ax.set_xlabel(\"x\")\n", + "ax.set_ylabel(\"y\")\n", + "\n", + "fig = _prepare_figure(\"figure(9)\", figsize=(7.0, 5.5))\n", + "ax = fig.subplots(1, 1)\n", + "ax.imshow(\n", + " mesh[\"zernike_field\"],\n", + " origin=\"lower\",\n", + " extent=[float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))],\n", + " aspect=\"equal\",\n", + " cmap=\"viridis\",\n", + ")\n", + "ax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\n", + "ax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\n", + "ax.set_title(f\"Zernike receptive field - Cell {int(mesh['cell_index']) + 1}\")\n", + "ax.set_xlabel(\"x\")\n", + "ax.set_ylabel(\"y\")\n", + "\n", + "# figure(9) overlay — matches MATLAB hold-on composite (published as _10.png)\n", + "fig = _prepare_figure(\"figure(9) overlay\", figsize=(10.0, 5.0))\n", + "axs = fig.subplots(1, 2)\n", + "ext = [float(np.min(mesh[\"grid_x\"])), float(np.max(mesh[\"grid_x\"])), float(np.min(mesh[\"grid_y\"])), float(np.max(mesh[\"grid_y\"]))]\n", + "for ax, field, label in zip(axs, [mesh[\"gaussian_field\"], mesh[\"zernike_field\"]], [\"Gaussian\", \"Zernike\"]):\n", + " ax.imshow(field, origin=\"lower\", extent=ext, aspect=\"equal\", cmap=\"viridis\")\n", + " ax.plot(mesh[\"x_pos\"], mesh[\"y_pos\"], color=\"white\", linewidth=0.5, alpha=0.35)\n", + " ax.scatter(spike_x, spike_y, s=8, color=\"tab:red\", alpha=0.7)\n", + " ax.set_title(f\"{label} - Cell {int(mesh['cell_index']) + 1}\")\n", + " ax.set_xlabel(\"x\")\n", + " ax.set_ylabel(\"y\")\n", + "\n", + "__tracker.finalize()" + ] } ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 11, @@ -189,4 +336,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/HistoryExamples.ipynb b/notebooks/HistoryExamples.ipynb index 173a3ff4..f5e7d240 100644 --- a/notebooks/HistoryExamples.ipynb +++ b/notebooks/HistoryExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a336fb0f", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:28.059078Z", + "iopub.status.busy": "2026-03-14T15:44:28.058900Z", + "iopub.status.idle": "2026-03-14T15:44:30.377134Z", + "shell.execute_reply": "2026-03-14T15:44:30.376740Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: HistoryExamples\n", @@ -54,9 +61,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "85845f70", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:30.378821Z", + "iopub.status.busy": "2026-03-14T15:44:30.378685Z", + "iopub.status.idle": "2026-03-14T15:44:30.429376Z", + "shell.execute_reply": "2026-03-14T15:44:30.428923Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example 1: History covariates for one neural spike train\n", @@ -81,9 +95,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "393bfd7d", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:30.430585Z", + "iopub.status.busy": "2026-03-14T15:44:30.430512Z", + "iopub.status.idle": "2026-03-14T15:44:30.663186Z", + "shell.execute_reply": "2026-03-14T15:44:30.662781Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Example 2: History covariates for a collection of Neural Spikes (nstColl)\n", @@ -114,7 +135,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 3, diff --git a/notebooks/HybridFilterExample.ipynb b/notebooks/HybridFilterExample.ipynb index 575b57cb..8f3fb335 100644 --- a/notebooks/HybridFilterExample.ipynb +++ b/notebooks/HybridFilterExample.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "d22dd216", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:38.677868Z", + "iopub.status.busy": "2026-03-14T15:44:38.677584Z", + "iopub.status.idle": "2026-03-14T15:44:40.831630Z", + "shell.execute_reply": "2026-03-14T15:44:40.830774Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: HybridFilterExample\n", @@ -60,10 +67,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "cacac4c3", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:40.834345Z", + "iopub.status.busy": "2026-03-14T15:44:40.834116Z", + "iopub.status.idle": "2026-03-14T15:44:41.334276Z", + "shell.execute_reply": "2026-03-14T15:44:41.333835Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_samples': 3000, 'num_cells': 24, 'state_accuracy': 0.899}\n" + ] + } + ], "source": [ "# SECTION 0: Hybrid Point Process Filter Example\n", "# This notebook mirrors the MATLAB hybrid-filter helpfile with executable figures.\n", @@ -84,9 +106,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "b031dd85", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:41.335603Z", + "iopub.status.busy": "2026-03-14T15:44:41.335525Z", + "iopub.status.idle": "2026-03-14T15:44:41.337214Z", + "shell.execute_reply": "2026-03-14T15:44:41.336812Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Problem Statement\n", @@ -95,9 +124,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "fd11f1d4", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:41.338199Z", + "iopub.status.busy": "2026-03-14T15:44:41.338134Z", + "iopub.status.idle": "2026-03-14T15:44:41.339669Z", + "shell.execute_reply": "2026-03-14T15:44:41.339289Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Hybrid state-space setup\n", @@ -106,10 +142,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "fd690f04", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:41.340739Z", + "iopub.status.busy": "2026-03-14T15:44:41.340668Z", + "iopub.status.idle": "2026-03-14T15:44:41.411997Z", + "shell.execute_reply": "2026-03-14T15:44:41.411499Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.0, 0.95, 'Cells: 24\\nState accuracy: 0.899\\nDecode RMSE X: 0.216\\nDecode RMSE Y: 0.172')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 3: Generated Simulated Arm Reach\n", "fig = _prepare_figure(\"fig1=figure('OuterPosition',[scrsz(3)*.1 scrsz(4)*.1 ...\", figsize=(10.0, 9.0))\n", @@ -158,9 +212,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "f9e4eb9d", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:41.413285Z", + "iopub.status.busy": "2026-03-14T15:44:41.413201Z", + "iopub.status.idle": "2026-03-14T15:44:41.414819Z", + "shell.execute_reply": "2026-03-14T15:44:41.414478Z" + } + }, "outputs": [], "source": [ "# SECTION 4: Simulate Neural Firing\n", @@ -169,9 +230,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "57220fb5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:41.415928Z", + "iopub.status.busy": "2026-03-14T15:44:41.415851Z", + "iopub.status.idle": "2026-03-14T15:44:42.019199Z", + "shell.execute_reply": "2026-03-14T15:44:42.018661Z" + } + }, "outputs": [], "source": [ "# SECTION 5: Run the hybrid filter\n", @@ -235,7 +303,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 3, diff --git a/notebooks/NetworkTutorial.ipynb b/notebooks/NetworkTutorial.ipynb index 5e0df873..129dfb16 100644 --- a/notebooks/NetworkTutorial.ipynb +++ b/notebooks/NetworkTutorial.ipynb @@ -14,10 +14,32 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "900fa7c2", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:46.577062Z", + "iopub.status.busy": "2026-03-14T15:44:46.576958Z", + "iopub.status.idle": "2026-03-14T15:44:48.759621Z", + "shell.execute_reply": "2026-03-14T15:44:48.759137Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<>:100: SyntaxWarning: invalid escape sequence '\\c'\n", + "<>:101: SyntaxWarning: invalid escape sequence '\\c'\n", + "<>:100: SyntaxWarning: invalid escape sequence '\\c'\n", + "<>:101: SyntaxWarning: invalid escape sequence '\\c'\n", + "/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/ipykernel_67250/2870471293.py:100: SyntaxWarning: invalid escape sequence '\\c'\n", + " ax.text(0.25, 0.14, \"$S_1=+1 \\cdot u_{stim}$\", ha=\"center\", fontsize=10)\n", + "/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/ipykernel_67250/2870471293.py:101: SyntaxWarning: invalid escape sequence '\\c'\n", + " ax.text(0.75, 0.14, \"$S_2=-1 \\cdot u_{stim}$\", ha=\"center\", fontsize=10)\n" + ] + } + ], "source": [ "# nSTAT-python notebook example: NetworkTutorial\n", "from pathlib import Path\n", @@ -159,9 +181,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "bf0f3cb7", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:48.761330Z", + "iopub.status.busy": "2026-03-14T15:44:48.761169Z", + "iopub.status.idle": "2026-03-14T15:44:48.763033Z", + "shell.execute_reply": "2026-03-14T15:44:48.762617Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Point Process Network Simulation\n", @@ -171,9 +200,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "e8398ef5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:48.764175Z", + "iopub.status.busy": "2026-03-14T15:44:48.764047Z", + "iopub.status.idle": "2026-03-14T15:44:48.784641Z", + "shell.execute_reply": "2026-03-14T15:44:48.784011Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Published network diagram\n", @@ -184,9 +220,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "a6428314", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:48.786267Z", + "iopub.status.busy": "2026-03-14T15:44:48.786161Z", + "iopub.status.idle": "2026-03-14T15:44:48.941718Z", + "shell.execute_reply": "2026-03-14T15:44:48.941160Z" + } + }, "outputs": [], "source": [ "# SECTION 3: Published block diagram\n", @@ -197,10 +240,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "fe878a9f", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:48.943289Z", + "iopub.status.busy": "2026-03-14T15:44:48.943174Z", + "iopub.status.idle": "2026-03-14T15:44:49.005783Z", + "shell.execute_reply": "2026-03-14T15:44:49.005428Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: Conditional intensity equation\n", "fig = _figure(\"lambda_i * Delta = logistic(mu_i + H*DeltaN_i[n] + S*u_stim[n] + E*DeltaN_k[n])\", figsize=(10.0, 3.0))\n", @@ -216,9 +277,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "303ba752", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:49.006956Z", + "iopub.status.busy": "2026-03-14T15:44:49.006886Z", + "iopub.status.idle": "2026-03-14T15:44:49.008397Z", + "shell.execute_reply": "2026-03-14T15:44:49.007986Z" + } + }, "outputs": [], "source": [ "# SECTION 5: Logistic nonlinearity\n", @@ -227,9 +295,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "ed73bb76", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:49.009318Z", + "iopub.status.busy": "2026-03-14T15:44:49.009251Z", + "iopub.status.idle": "2026-03-14T15:44:49.010726Z", + "shell.execute_reply": "2026-03-14T15:44:49.010341Z" + } + }, "outputs": [], "source": [ "# SECTION 6: Convolution operator note\n", @@ -238,9 +313,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "39438bb1", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:44:49.011695Z", + "iopub.status.busy": "2026-03-14T15:44:49.011609Z", + "iopub.status.idle": "2026-03-14T15:45:27.439907Z", + "shell.execute_reply": "2026-03-14T15:45:27.439393Z" + } + }, "outputs": [], "source": [ "# SECTION 7: 2 Neuron Network\n", @@ -262,10 +344,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "fa0f59ac", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.441812Z", + "iopub.status.busy": "2026-03-14T15:45:27.441693Z", + "iopub.status.idle": "2026-03-14T15:45:27.443782Z", + "shell.execute_reply": "2026-03-14T15:45:27.443217Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mu1': -3.0, 'mu2': -3.0, 'sample_rate_hz': 1000.0}\n" + ] + } + ], "source": [ "# SECTION 8: Baseline firing rate of the neurons being modeled\n", "print({\"mu1\": float(baseline_mu[0]), \"mu2\": float(baseline_mu[1]), \"sample_rate_hz\": sampleRate})\n" @@ -273,9 +370,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "71852dc5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.445064Z", + "iopub.status.busy": "2026-03-14T15:45:27.444975Z", + "iopub.status.idle": "2026-03-14T15:45:27.446662Z", + "shell.execute_reply": "2026-03-14T15:45:27.446262Z" + } + }, "outputs": [], "source": [ "# SECTION 9: History Effect\n", @@ -284,10 +388,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "d9bb524a", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.447744Z", + "iopub.status.busy": "2026-03-14T15:45:27.447646Z", + "iopub.status.idle": "2026-03-14T15:45:27.483376Z", + "shell.execute_reply": "2026-03-14T15:45:27.483012Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[Text(1.0, 0, '1'), Text(2.0, 0, '2'), Text(3.0, 0, '3')]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 10: History kernel\n", "fig = _figure(\"1*h[n]=-4*DeltaN[n-1]-2*DeltaN[n-2]-1*DeltaN[n-3]\", figsize=(8.0, 4.5))\n", @@ -298,9 +420,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "8dd40443", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.484704Z", + "iopub.status.busy": "2026-03-14T15:45:27.484627Z", + "iopub.status.idle": "2026-03-14T15:45:27.486244Z", + "shell.execute_reply": "2026-03-14T15:45:27.485828Z" + } + }, "outputs": [], "source": [ "# SECTION 11: Stimulus Effect\n", @@ -309,9 +438,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "cbee27bd", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.487342Z", + "iopub.status.busy": "2026-03-14T15:45:27.487275Z", + "iopub.status.idle": "2026-03-14T15:45:27.530838Z", + "shell.execute_reply": "2026-03-14T15:45:27.530460Z" + } + }, "outputs": [], "source": [ "# SECTION 12: Stimulus filter for neuron 1\n", @@ -322,9 +458,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "a4faf7c3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.531955Z", + "iopub.status.busy": "2026-03-14T15:45:27.531885Z", + "iopub.status.idle": "2026-03-14T15:45:27.570206Z", + "shell.execute_reply": "2026-03-14T15:45:27.569726Z" + } + }, "outputs": [], "source": [ "# SECTION 13: Stimulus filter for neuron 2\n", @@ -335,9 +478,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "5c89e3bf", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.571600Z", + "iopub.status.busy": "2026-03-14T15:45:27.571519Z", + "iopub.status.idle": "2026-03-14T15:45:27.573153Z", + "shell.execute_reply": "2026-03-14T15:45:27.572759Z" + } + }, "outputs": [], "source": [ "# SECTION 14: Ensemble Effect\n", @@ -346,9 +496,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "e63fe4b7", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.574264Z", + "iopub.status.busy": "2026-03-14T15:45:27.574200Z", + "iopub.status.idle": "2026-03-14T15:45:27.611584Z", + "shell.execute_reply": "2026-03-14T15:45:27.611183Z" + } + }, "outputs": [], "source": [ "# SECTION 15: Ensemble filter for neuron 1\n", @@ -359,9 +516,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "585ef59c", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.612681Z", + "iopub.status.busy": "2026-03-14T15:45:27.612608Z", + "iopub.status.idle": "2026-03-14T15:45:27.650194Z", + "shell.execute_reply": "2026-03-14T15:45:27.649801Z" + } + }, "outputs": [], "source": [ "# SECTION 16: Ensemble filter for neuron 2\n", @@ -372,9 +536,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "c491c656", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.651242Z", + "iopub.status.busy": "2026-03-14T15:45:27.651170Z", + "iopub.status.idle": "2026-03-14T15:45:27.653402Z", + "shell.execute_reply": "2026-03-14T15:45:27.653002Z" + } + }, "outputs": [], "source": [ "# SECTION 17: Stimulus\n", @@ -385,10 +556,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "68e45b9a", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:27.654399Z", + "iopub.status.busy": "2026-03-14T15:45:27.654335Z", + "iopub.status.idle": "2026-03-14T15:45:29.277280Z", + "shell.execute_reply": "2026-03-14T15:45:29.276843Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 18: Simulate the Network\n", "fitType = \"binomial\"\n", @@ -419,9 +608,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "31945e6f", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:29.278628Z", + "iopub.status.busy": "2026-03-14T15:45:29.278534Z", + "iopub.status.idle": "2026-03-14T15:45:29.280512Z", + "shell.execute_reply": "2026-03-14T15:45:29.280201Z" + } + }, "outputs": [], "source": [ "# SECTION 19: GLM Model Fitting Setup\n", @@ -433,10 +629,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "286a3cc1", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:29.281694Z", + "iopub.status.busy": "2026-03-14T15:45:29.281610Z", + "iopub.status.idle": "2026-03-14T15:45:44.708227Z", + "shell.execute_reply": "2026-03-14T15:45:44.707807Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 20: GLM Model Fitting and Results\n", "results = Analysis.RunAnalysisForAllNeurons(trial, cfgColl, 0, Algorithm)\n", @@ -470,10 +684,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "f31f67ab", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:44.709848Z", + "iopub.status.busy": "2026-03-14T15:45:44.709749Z", + "iopub.status.idle": "2026-03-14T15:45:44.787739Z", + "shell.execute_reply": "2026-03-14T15:45:44.787291Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'algorithm': 'BNLRCG', 'spike_counts': [2546, 2327], 'estimated_network': [[0.0, 0.0], [0.0, 0.0]]}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " self._active_fig.tight_layout()\n" + ] + } + ], "source": [ "# SECTION 21: Neighbor-selection note\n", "# By default all neurons are considered potential neighbors. To restrict candidate neighbors, call trial.setNeighbors(neighborArray) using the MATLAB-style convention described in the source helpfile.\n", @@ -490,7 +727,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 14, @@ -501,4 +747,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/PPSimExample.ipynb b/notebooks/PPSimExample.ipynb index 5d7c3168..cd6812af 100644 --- a/notebooks/PPSimExample.ipynb +++ b/notebooks/PPSimExample.ipynb @@ -14,10 +14,50 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a6759a0c", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:45:52.830232Z", + "iopub.status.busy": "2026-03-14T15:45:52.830019Z", + "iopub.status.idle": "2026-03-14T15:46:19.101796Z", + "shell.execute_reply": "2026-03-14T15:46:19.101370Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: The specified simulation stop time (50.0) is not an integer multiple of the fixed step size (0.00099999999999988987). Changing the stop time to (49.999999999994493). \n", + "Suggested Actions:\n", + " • Specify an integer multiple of the fixed step size for the stop time of the simulation. - Fix\n", + " • Set Automatic solver parameter selection diagnostic to 'none'. - Apply\n", + "\n", + "Warning: The specified simulation stop time (50.0) is not an integer multiple of the fixed step size (0.00099999999999988987). Changing the stop time to (49.999999999994493). \n", + "Suggested Actions:\n", + " • Specify an integer multiple of the fixed step size for the stop time of the simulation. - Fix\n", + " • Set Automatic solver parameter selection diagnostic to 'none'. - Apply\n", + "\n", + "Warning: The specified simulation stop time (50.0) is not an integer multiple of the fixed step size (0.00099999999999988987). Changing the stop time to (49.999999999994493). \n", + "Suggested Actions:\n", + " • Specify an integer multiple of the fixed step size for the stop time of the simulation. - Fix\n", + " • Set Automatic solver parameter selection diagnostic to 'none'. - Apply\n", + "\n", + "Warning: The specified simulation stop time (50.0) is not an integer multiple of the fixed step size (0.00099999999999988987). Changing the stop time to (49.999999999994493). \n", + "Suggested Actions:\n", + " • Specify an integer multiple of the fixed step size for the stop time of the simulation. - Fix\n", + " • Set Automatic solver parameter selection diagnostic to 'none'. - Apply\n", + "\n", + "Warning: The specified simulation stop time (50.0) is not an integer multiple of the fixed step size (0.00099999999999988987). Changing the stop time to (49.999999999994493). \n", + "Suggested Actions:\n", + " • Specify an integer multiple of the fixed step size for the stop time of the simulation. - Fix\n", + " • Set Automatic solver parameter selection diagnostic to 'none'. - Apply\n", + "\n", + "{'duration_s': 50.0, 'num_realizations': 5, 'mean_rate_hz': 48.949}\n" + ] + } + ], "source": [ "# nSTAT-python notebook example: PPSimExample\n", "from pathlib import Path\n", @@ -68,9 +108,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "fb22ed56", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.103416Z", + "iopub.status.busy": "2026-03-14T15:46:19.103250Z", + "iopub.status.idle": "2026-03-14T15:46:19.105166Z", + "shell.execute_reply": "2026-03-14T15:46:19.104734Z" + } + }, "outputs": [], "source": [ "# SECTION 1: General Point Process Simulation\n", @@ -79,10 +126,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "aefbf353", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.106194Z", + "iopub.status.busy": "2026-03-14T15:46:19.106124Z", + "iopub.status.idle": "2026-03-14T15:46:19.108147Z", + "shell.execute_reply": "2026-03-14T15:46:19.107674Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using native Python CIF.simulateCIF to mirror the MATLAB recursive-CIF workflow.\n" + ] + } + ], "source": [ "# SECTION 2: Point Process Sample Path Generation\n", "print(\"Using native Python CIF.simulateCIF to mirror the MATLAB recursive-CIF workflow.\")" @@ -90,10 +152,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "ae867985", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.109355Z", + "iopub.status.busy": "2026-03-14T15:46:19.109279Z", + "iopub.status.idle": "2026-03-14T15:46:19.111392Z", + "shell.execute_reply": "2026-03-14T15:46:19.110872Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'history_windows_s': [0.0, 0.001, 0.002, 0.003]}\n" + ] + } + ], "source": [ "# SECTION 3: History Effect\n", "selfHist = [0.0, 0.001, 0.002, 0.003]\n", @@ -102,10 +179,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "9076f004", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.112645Z", + "iopub.status.busy": "2026-03-14T15:46:19.112572Z", + "iopub.status.idle": "2026-03-14T15:46:19.114390Z", + "shell.execute_reply": "2026-03-14T15:46:19.114053Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'stimulus_frequency_hz': 1.0, 'stimulus_amplitude': 1.0}\n" + ] + } + ], "source": [ "# SECTION 4: Stimulus Effect\n", "print({\"stimulus_frequency_hz\": 1.0, \"stimulus_amplitude\": 1.0})" @@ -113,10 +205,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "fa8120b8", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.115743Z", + "iopub.status.busy": "2026-03-14T15:46:19.115661Z", + "iopub.status.idle": "2026-03-14T15:46:19.117529Z", + "shell.execute_reply": "2026-03-14T15:46:19.117158Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'ensemble_effect': 0.0}\n" + ] + } + ], "source": [ "# SECTION 5: Ensemble Effect\n", "print({\"ensemble_effect\": 0.0})" @@ -124,10 +231,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "c58f3108", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:19.118530Z", + "iopub.status.busy": "2026-03-14T15:46:19.118460Z", + "iopub.status.idle": "2026-03-14T15:46:20.534134Z", + "shell.execute_reply": "2026-03-14T15:46:20.533685Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.0, 10.0)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 6: Generate sample paths\n", "fig = _figure(\"figure; subplot(2,1,1); sC.plot; subplot(2,1,2); stim.plot\", figsize=(10.0, 5.5))\n", @@ -140,10 +265,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "a7b37585", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:20.535378Z", + "iopub.status.busy": "2026-03-14T15:46:20.535294Z", + "iopub.status.idle": "2026-03-14T15:46:21.348612Z", + "shell.execute_reply": "2026-03-14T15:46:21.348131Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.0, 10.0)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 7: Inspect the simulated CIF\n", "fig = _figure(\"figure; lambda.plot\", figsize=(10.0, 4.0))\n", @@ -154,9 +297,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "bac3e6f1", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:21.349911Z", + "iopub.status.busy": "2026-03-14T15:46:21.349825Z", + "iopub.status.idle": "2026-03-14T15:46:21.351965Z", + "shell.execute_reply": "2026-03-14T15:46:21.351553Z" + } + }, "outputs": [], "source": [ "# SECTION 8: GLM Model Fitting Setup\n", @@ -170,10 +320,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "278b16e6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:21.352971Z", + "iopub.status.busy": "2026-03-14T15:46:21.352901Z", + "iopub.status.idle": "2026-03-14T15:46:21.587071Z", + "shell.execute_reply": "2026-03-14T15:46:21.586523Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'algorithm': 'BNLRCG', 'binary_representation': True}\n" + ] + } + ], "source": [ "# SECTION 9: Choose the MATLAB-style fitting algorithm\n", "Algorithm = \"BNLRCG\"\n", @@ -182,9 +347,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "1c9f83d1", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:46:21.588266Z", + "iopub.status.busy": "2026-03-14T15:46:21.588181Z", + "iopub.status.idle": "2026-03-14T15:47:07.981522Z", + "shell.execute_reply": "2026-03-14T15:47:07.981031Z" + } + }, "outputs": [], "source": [ "# SECTION 10: GLM Model Fitting and Results\n", @@ -193,10 +365,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "c939f67c", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:07.983092Z", + "iopub.status.busy": "2026-03-14T15:47:07.983003Z", + "iopub.status.idle": "2026-03-14T15:47:08.683659Z", + "shell.execute_reply": "2026-03-14T15:47:08.683134Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 11: Results for sample neuron\n", "fig = _figure(\"results{1}.plotResults\", figsize=(11.0, 8.0))\n", @@ -205,10 +395,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "21f39091", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:08.685125Z", + "iopub.status.busy": "2026-03-14T15:47:08.685015Z", + "iopub.status.idle": "2026-03-14T15:47:09.581813Z", + "shell.execute_reply": "2026-03-14T15:47:09.581078Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 12: Baseline-only diagnostic view\n", "fig = _figure(\"results{1}.plotResults baseline\", figsize=(11.0, 8.0))\n", @@ -217,10 +425,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "6faa0468", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:09.583300Z", + "iopub.status.busy": "2026-03-14T15:47:09.583169Z", + "iopub.status.idle": "2026-03-14T15:47:10.452684Z", + "shell.execute_reply": "2026-03-14T15:47:10.452054Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 13: Stimulus model diagnostic view\n", "fig = _figure(\"results{2}.plotResults stim\", figsize=(11.0, 8.0))\n", @@ -229,10 +455,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "27d1e5af", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:10.453876Z", + "iopub.status.busy": "2026-03-14T15:47:10.453788Z", + "iopub.status.idle": "2026-03-14T15:47:11.336477Z", + "shell.execute_reply": "2026-03-14T15:47:11.335875Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 14: Stimulus-plus-history diagnostic view\n", "fig = _figure(\"results{3}.plotResults hist\", figsize=(11.0, 8.0))\n", @@ -241,10 +485,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "bd5e5ca2", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:11.337949Z", + "iopub.status.busy": "2026-03-14T15:47:11.337834Z", + "iopub.status.idle": "2026-03-14T15:47:11.624213Z", + "shell.execute_reply": "2026-03-14T15:47:11.623854Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.0, 10.0)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 15: Compare fitted firing rates\n", "fig = _figure(\"results.lambda.plot\", figsize=(9.5, 4.5))\n", @@ -255,10 +517,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "9d323187", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:11.625934Z", + "iopub.status.busy": "2026-03-14T15:47:11.625837Z", + "iopub.status.idle": "2026-03-14T15:47:11.740089Z", + "shell.execute_reply": "2026-03-14T15:47:11.739726Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'fit_names': ['Baseline', 'Stim', 'Stim+Hist'], 'mean_AIC': [14768.013, 14013.036, 13415.194]}\n" + ] + } + ], "source": [ "# SECTION 16: Results across all sample paths\n", "summary = FitResSummary(results)\n", @@ -269,9 +546,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "0c7da4fb", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:11.741550Z", + "iopub.status.busy": "2026-03-14T15:47:11.741471Z", + "iopub.status.idle": "2026-03-14T15:47:11.854436Z", + "shell.execute_reply": "2026-03-14T15:47:11.853952Z" + } + }, "outputs": [], "source": [ "# SECTION 17: Summarize model selection\n", @@ -287,7 +571,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 8, diff --git a/notebooks/PPThinning.ipynb b/notebooks/PPThinning.ipynb index 1b6f4d21..1614861e 100644 --- a/notebooks/PPThinning.ipynb +++ b/notebooks/PPThinning.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "52c28645", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:17.719509Z", + "iopub.status.busy": "2026-03-14T15:47:17.719306Z", + "iopub.status.idle": "2026-03-14T15:47:19.963867Z", + "shell.execute_reply": "2026-03-14T15:47:19.963219Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: PPThinning\n", @@ -53,9 +60,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "7e5a7ce6", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:19.966588Z", + "iopub.status.busy": "2026-03-14T15:47:19.966344Z", + "iopub.status.idle": "2026-03-14T15:47:19.968744Z", + "shell.execute_reply": "2026-03-14T15:47:19.968252Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Basic Example\n", @@ -77,9 +91,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "bc5fb9e3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:19.970181Z", + "iopub.status.busy": "2026-03-14T15:47:19.970073Z", + "iopub.status.idle": "2026-03-14T15:47:20.042230Z", + "shell.execute_reply": "2026-03-14T15:47:20.041551Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Compare Constant rate process vs. thinned process\n", @@ -100,9 +121,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "bba76669", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:20.043634Z", + "iopub.status.busy": "2026-03-14T15:47:20.043532Z", + "iopub.status.idle": "2026-03-14T15:47:20.086055Z", + "shell.execute_reply": "2026-03-14T15:47:20.085427Z" + } + }, "outputs": [], "source": [ "# SECTION 3: Simulate multiple realizations of a point process via thinning\n", @@ -119,7 +147,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 3, diff --git a/notebooks/PSTHEstimation.ipynb b/notebooks/PSTHEstimation.ipynb index 9aafe1b8..521016db 100644 --- a/notebooks/PSTHEstimation.ipynb +++ b/notebooks/PSTHEstimation.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "7a5e0ff3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:25.550031Z", + "iopub.status.busy": "2026-03-14T15:47:25.549954Z", + "iopub.status.idle": "2026-03-14T15:47:27.410032Z", + "shell.execute_reply": "2026-03-14T15:47:27.409292Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: PSTHEstimation\n", @@ -53,9 +60,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "00a3c67b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:27.411771Z", + "iopub.status.busy": "2026-03-14T15:47:27.411624Z", + "iopub.status.idle": "2026-03-14T15:47:27.424400Z", + "shell.execute_reply": "2026-03-14T15:47:27.423929Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Generate a known Conditional Intensity Function\n", @@ -71,9 +85,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "f257e49e", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:27.425851Z", + "iopub.status.busy": "2026-03-14T15:47:27.425755Z", + "iopub.status.idle": "2026-03-14T15:47:27.491476Z", + "shell.execute_reply": "2026-03-14T15:47:27.491082Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Estimate the PSTH with 500ms windows\n", @@ -98,7 +119,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 2, diff --git a/notebooks/SignalObjExamples.ipynb b/notebooks/SignalObjExamples.ipynb index 5666e428..30441efe 100644 --- a/notebooks/SignalObjExamples.ipynb +++ b/notebooks/SignalObjExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "7b9d4565", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:31.602902Z", + "iopub.status.busy": "2026-03-14T15:47:31.602759Z", + "iopub.status.idle": "2026-03-14T15:47:33.853160Z", + "shell.execute_reply": "2026-03-14T15:47:33.852619Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: SignalObjExamples\n", @@ -36,9 +43,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "fd174c65", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:33.854779Z", + "iopub.status.busy": "2026-03-14T15:47:33.854636Z", + "iopub.status.idle": "2026-03-14T15:47:33.926918Z", + "shell.execute_reply": "2026-03-14T15:47:33.926483Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example 1: Defining and Plotting Signals\n", @@ -62,9 +76,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "d58192a4", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:33.928355Z", + "iopub.status.busy": "2026-03-14T15:47:33.928263Z", + "iopub.status.idle": "2026-03-14T15:47:34.121478Z", + "shell.execute_reply": "2026-03-14T15:47:34.121012Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Example 2: Changing Signal Properties\n", @@ -100,9 +121,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "1310c80a", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:34.123006Z", + "iopub.status.busy": "2026-03-14T15:47:34.122932Z", + "iopub.status.idle": "2026-03-14T15:47:34.205834Z", + "shell.execute_reply": "2026-03-14T15:47:34.205401Z" + } + }, "outputs": [], "source": [ "# SECTION 3: Example 3: Resampling and Windowing SignalObjs\n", @@ -122,9 +150,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "4c2fb64c", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:34.207148Z", + "iopub.status.busy": "2026-03-14T15:47:34.207080Z", + "iopub.status.idle": "2026-03-14T15:47:34.232211Z", + "shell.execute_reply": "2026-03-14T15:47:34.231765Z" + } + }, "outputs": [], "source": [ "# SECTION 4: Example 4: SignalObj Mathematical Operations\n", @@ -152,10 +187,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "28cc2086", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:34.233365Z", + "iopub.status.busy": "2026-03-14T15:47:34.233298Z", + "iopub.status.idle": "2026-03-14T15:47:34.236103Z", + "shell.execute_reply": "2026-03-14T15:47:34.235768Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: Example 5: Spectra\n", "__tracker.new_figure('figure')\n", @@ -167,9 +220,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "8aba84d8", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:34.237111Z", + "iopub.status.busy": "2026-03-14T15:47:34.237047Z", + "iopub.status.idle": "2026-03-14T15:47:34.266574Z", + "shell.execute_reply": "2026-03-14T15:47:34.266154Z" + } + }, "outputs": [], "source": [ "# SECTION 6: Example 6: View signal variability\n", @@ -194,7 +254,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 16, diff --git a/notebooks/StimulusDecode2D.ipynb b/notebooks/StimulusDecode2D.ipynb index e9b01a4c..f16fb5d2 100644 --- a/notebooks/StimulusDecode2D.ipynb +++ b/notebooks/StimulusDecode2D.ipynb @@ -4,13 +4,26 @@ "cell_type": "markdown", "id": "4599bf89", "metadata": {}, - "source": "\n## MATLAB Parity Note\n- Source MATLAB helpfile: `StimulusDecode2D.mlx`\n- Fidelity status: `exact`\n- Remaining justified differences: Follows the MATLAB nonlinear-CIF decoding workflow with `DecodingAlgorithms.PPDecodeFilter` and all 4 published figures. Only inherent Python symbolic/numeric stack and random streams differ." + "source": [ + "\n", + "## MATLAB Parity Note\n", + "- Source MATLAB helpfile: `StimulusDecode2D.mlx`\n", + "- Fidelity status: `exact`\n", + "- Remaining justified differences: Follows the MATLAB nonlinear-CIF decoding workflow with `DecodingAlgorithms.PPDecodeFilter` and all 4 published figures. Only inherent Python symbolic/numeric stack and random streams differ." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "6f7a27a5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:38.314699Z", + "iopub.status.busy": "2026-03-14T15:47:38.314472Z", + "iopub.status.idle": "2026-03-14T15:47:40.401474Z", + "shell.execute_reply": "2026-03-14T15:47:40.400853Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: StimulusDecode2D\n", @@ -152,10 +165,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "8809d377", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:47:40.403092Z", + "iopub.status.busy": "2026-03-14T15:47:40.402942Z", + "iopub.status.idle": "2026-03-14T15:51:37.815052Z", + "shell.execute_reply": "2026-03-14T15:51:37.814532Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_cells': 80, 'decode_method': 'PPDecodeFilter', 'decode_rmse': 0.0823, 'fallback_error': ''}\n" + ] + } + ], "source": [ "# SECTION 1: 2-D Stimulus Decode\n", "# This notebook follows the MATLAB 2-D decoding workflow with simulated spatial receptive fields.\n", @@ -173,9 +201,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "d87abf0b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:51:37.816361Z", + "iopub.status.busy": "2026-03-14T15:51:37.816262Z", + "iopub.status.idle": "2026-03-14T15:51:38.271058Z", + "shell.execute_reply": "2026-03-14T15:51:38.270498Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Generate the random receptive fields to simulate different neurons\n", @@ -219,10 +254,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "5eddb87b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:51:38.272599Z", + "iopub.status.busy": "2026-03-14T15:51:38.272509Z", + "iopub.status.idle": "2026-03-14T15:51:38.753605Z", + "shell.execute_reply": "2026-03-14T15:51:38.753136Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " self._active_fig.tight_layout()\n" + ] + } + ], "source": [ "# SECTION 3: Decode the x-y trajectory\n", "fig = _prepare_figure(\"plot(x_u(1,:),x_u(2,:),'b',px,py,'k')\", figsize=(6.0, 6.0))\n", @@ -241,7 +292,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 6, @@ -252,4 +312,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/TrialConfigExamples.ipynb b/notebooks/TrialConfigExamples.ipynb index 41cae891..fc68a1f9 100644 --- a/notebooks/TrialConfigExamples.ipynb +++ b/notebooks/TrialConfigExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b3c759a5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:51:48.689262Z", + "iopub.status.busy": "2026-03-14T15:51:48.688938Z", + "iopub.status.idle": "2026-03-14T15:51:50.982495Z", + "shell.execute_reply": "2026-03-14T15:51:50.981592Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: TrialConfigExamples\n", @@ -38,7 +45,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 0, diff --git a/notebooks/TrialExamples.ipynb b/notebooks/TrialExamples.ipynb index 059109e2..805578ac 100644 --- a/notebooks/TrialExamples.ipynb +++ b/notebooks/TrialExamples.ipynb @@ -14,10 +14,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "f23e6389", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:51:59.891883Z", + "iopub.status.busy": "2026-03-14T15:51:59.891609Z", + "iopub.status.idle": "2026-03-14T15:52:02.016652Z", + "shell.execute_reply": "2026-03-14T15:52:02.016197Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'trial_duration_s': 1.0, 'num_neurons': 4, 'covariates': ['Position', 'Force'], 'history_windows': [0.0, 0.1, 0.2, 0.4]}\n" + ] + } + ], "source": [ "# nSTAT-python notebook example: TrialExamples\n", "from pathlib import Path\n", @@ -120,9 +135,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "5f01a6dd", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.018696Z", + "iopub.status.busy": "2026-03-14T15:52:02.018521Z", + "iopub.status.idle": "2026-03-14T15:52:02.020613Z", + "shell.execute_reply": "2026-03-14T15:52:02.020189Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example 1: A simple data set\n", @@ -136,10 +158,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "947fce2d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.021967Z", + "iopub.status.busy": "2026-03-14T15:52:02.021873Z", + "iopub.status.idle": "2026-03-14T15:52:02.042679Z", + "shell.execute_reply": "2026-03-14T15:52:02.042281Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 2: Create History windows of interest\n", "fig = _figure(\"figure; h.plot\", figsize=(8.0, 2.5))\n", @@ -149,10 +191,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "948c1c5e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.043903Z", + "iopub.status.busy": "2026-03-14T15:52:02.043829Z", + "iopub.status.idle": "2026-03-14T15:52:02.140909Z", + "shell.execute_reply": "2026-03-14T15:52:02.140497Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 3: Load Covariates\n", "fig = _figure(\"figure; cc.plot\", figsize=(8.5, 5.0))\n", @@ -161,10 +221,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "addc115e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.142229Z", + "iopub.status.busy": "2026-03-14T15:52:02.142151Z", + "iopub.status.idle": "2026-03-14T15:52:02.205921Z", + "shell.execute_reply": "2026-03-14T15:52:02.205565Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: Create trial events\n", "fig = _figure(\"figure; e.plot\", figsize=(8.0, 2.3))\n", @@ -174,10 +252,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "8160b030", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.207219Z", + "iopub.status.busy": "2026-03-14T15:52:02.207144Z", + "iopub.status.idle": "2026-03-14T15:52:02.276551Z", + "shell.execute_reply": "2026-03-14T15:52:02.276199Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: Create neural Spike Train Data\n", "fig = _figure(\"figure; spikeColl.plot\", figsize=(8.5, 3.5))\n", @@ -187,10 +283,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "f15709a2", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.277839Z", + "iopub.status.busy": "2026-03-14T15:52:02.277766Z", + "iopub.status.idle": "2026-03-14T15:52:02.411214Z", + "shell.execute_reply": "2026-03-14T15:52:02.410860Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 6: Finally we have everything we need to create a Trial object.\n", "fig = _figure(\"figure; trial1.plot\", figsize=(9.0, 8.0))\n", @@ -199,10 +313,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "d25ae0c3", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.412800Z", + "iopub.status.busy": "2026-03-14T15:52:02.412694Z", + "iopub.status.idle": "2026-03-14T15:52:02.657758Z", + "shell.execute_reply": "2026-03-14T15:52:02.657393Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'masked_labels': ['x', 'f_x', '[0,0.1]', '[0.1,0.2]', '[0.2,0.4]'], 'history_covariates': ['[0,0.1]', '[0.1,0.2]', '[0.2,0.4]', '[0,0.1]']}\n" + ] + } + ], "source": [ "# SECTION 7: Mask out some of the data and plot the trial once again\n", "trial1.setCovMask([[\"Position\", \"x\"], [\"Force\", \"f_x\"]])\n", @@ -215,10 +344,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "48633477", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.659120Z", + "iopub.status.busy": "2026-03-14T15:52:02.659041Z", + "iopub.status.idle": "2026-03-14T15:52:02.660871Z", + "shell.execute_reply": "2026-03-14T15:52:02.660466Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Examples of neural spike analysis using AnalysisExamples2 (Neural Spike Analysis Toolbox) or AnalysisExamples (standard methods).\n" + ] + } + ], "source": [ "# SECTION 8: Example 2: Analyzing Trial Data\n", "print(\"Examples of neural spike analysis using AnalysisExamples2 (Neural Spike Analysis Toolbox) or AnalysisExamples (standard methods).\")" @@ -226,10 +370,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "ebaa9bdc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:02.661967Z", + "iopub.status.busy": "2026-03-14T15:52:02.661895Z", + "iopub.status.idle": "2026-03-14T15:52:02.776447Z", + "shell.execute_reply": "2026-03-14T15:52:02.776030Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'recommended_next_notebooks': ['AnalysisExamples2', 'AnalysisExamples']}\n" + ] + } + ], "source": [ "# SECTION 9: Related analysis workflows\n", "print({\"recommended_next_notebooks\": [\"AnalysisExamples2\", \"AnalysisExamples\"]})\n", @@ -239,7 +398,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 6, diff --git a/notebooks/ValidationDataSet.ipynb b/notebooks/ValidationDataSet.ipynb index 766d9311..4e4f5782 100644 --- a/notebooks/ValidationDataSet.ipynb +++ b/notebooks/ValidationDataSet.ipynb @@ -14,9 +14,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "5a19211e", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:09.248334Z", + "iopub.status.busy": "2026-03-14T15:52:09.248121Z", + "iopub.status.idle": "2026-03-14T15:52:11.309244Z", + "shell.execute_reply": "2026-03-14T15:52:11.308734Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: ValidationDataSet\n", @@ -147,10 +154,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "de07a751", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:11.310747Z", + "iopub.status.busy": "2026-03-14T15:52:11.310610Z", + "iopub.status.idle": "2026-03-14T15:52:13.618439Z", + "shell.execute_reply": "2026-03-14T15:52:13.617897Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'ci_fast_path': False, 'constant_lambda_hz': 10.0, 'piecewise_lambda1_hz': 1.0, 'piecewise_lambda2_hz': 10.0}\n" + ] + } + ], "source": [ "# SECTION 0: Software Validation Data Set\n", "# This notebook follows the MATLAB validation helpfile; CI uses a documented short fast path while local runs use MATLAB-scale sample counts.\n", @@ -169,9 +191,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "4c326ba4", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:13.619641Z", + "iopub.status.busy": "2026-03-14T15:52:13.619561Z", + "iopub.status.idle": "2026-03-14T15:52:13.621227Z", + "shell.execute_reply": "2026-03-14T15:52:13.620868Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Case #1: Constant Rate Poisson Process\n", @@ -180,9 +209,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "0348ff8b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:13.622361Z", + "iopub.status.busy": "2026-03-14T15:52:13.622297Z", + "iopub.status.idle": "2026-03-14T15:52:13.623970Z", + "shell.execute_reply": "2026-03-14T15:52:13.623551Z" + } + }, "outputs": [], "source": [ "# SECTION 2: Generate constant-rate neural firing activity\n", @@ -192,9 +228,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "127d7e94", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:13.624971Z", + "iopub.status.busy": "2026-03-14T15:52:13.624904Z", + "iopub.status.idle": "2026-03-14T15:52:13.651953Z", + "shell.execute_reply": "2026-03-14T15:52:13.651530Z" + } + }, "outputs": [], "source": [ "# SECTION 3: Sanity check the ISI distribution\n", @@ -206,10 +249,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "ce2f5297", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:13.653120Z", + "iopub.status.busy": "2026-03-14T15:52:13.653042Z", + "iopub.status.idle": "2026-03-14T15:52:22.050180Z", + "shell.execute_reply": "2026-03-14T15:52:22.049690Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: Setup the constant-rate analysis\n", "constant_results = Analysis.RunAnalysisForAllNeurons(constant_case[\"trial\"], constant_case[\"cfg\"], 0)\n", @@ -228,10 +289,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "e4a199c4", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:22.051439Z", + "iopub.status.busy": "2026-03-14T15:52:22.051358Z", + "iopub.status.idle": "2026-03-14T15:52:22.104361Z", + "shell.execute_reply": "2026-03-14T15:52:22.103962Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: Run the constant-rate analysis\n", "fig = _prepare_figure(\"results{1}.lambda.plot\", figsize=(10.0, 4.5))\n", @@ -250,9 +329,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "26380744", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:22.105600Z", + "iopub.status.busy": "2026-03-14T15:52:22.105525Z", + "iopub.status.idle": "2026-03-14T15:52:22.107313Z", + "shell.execute_reply": "2026-03-14T15:52:22.106907Z" + } + }, "outputs": [], "source": [ "# SECTION 6: Case #2: Piece-wise Constant Rate Poisson Process\n", @@ -263,10 +349,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "cf2c6402", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:22.108355Z", + "iopub.status.busy": "2026-03-14T15:52:22.108287Z", + "iopub.status.idle": "2026-03-14T15:52:22.327920Z", + "shell.execute_reply": "2026-03-14T15:52:22.327199Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 7: Generate the piecewise-rate spike trains\n", "fig = _prepare_figure(\"plot(spikeTimes1, spikeTimes2)\", figsize=(10.0, 4.5))\n", @@ -293,9 +397,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "25dfb2e5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:22.329358Z", + "iopub.status.busy": "2026-03-14T15:52:22.329246Z", + "iopub.status.idle": "2026-03-14T15:52:50.344851Z", + "shell.execute_reply": "2026-03-14T15:52:50.344322Z" + } + }, "outputs": [], "source": [ "# SECTION 8: Setup the piecewise-rate analysis\n", @@ -304,10 +415,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "113d3272", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:50.346839Z", + "iopub.status.busy": "2026-03-14T15:52:50.346694Z", + "iopub.status.idle": "2026-03-14T15:52:50.551367Z", + "shell.execute_reply": "2026-03-14T15:52:50.550906Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 9: Run the piecewise-rate analysis\n", "fig = _prepare_figure(\"results{1}.lambda.plot\", figsize=(10.0, 4.5))\n", @@ -339,9 +468,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "1d9d4cce", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:52:50.552962Z", + "iopub.status.busy": "2026-03-14T15:52:50.552800Z", + "iopub.status.idle": "2026-03-14T15:52:50.960765Z", + "shell.execute_reply": "2026-03-14T15:52:50.960323Z" + } + }, "outputs": [], "source": [ "# SECTION 10: Compare the results across the two neurons\n", @@ -373,7 +509,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 10, diff --git a/notebooks/mEPSCAnalysis.ipynb b/notebooks/mEPSCAnalysis.ipynb index 9dc5dec0..25ceb020 100644 --- a/notebooks/mEPSCAnalysis.ipynb +++ b/notebooks/mEPSCAnalysis.ipynb @@ -6,10 +6,10 @@ "id": "b499a26e", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:49.400898Z", - "iopub.status.busy": "2026-03-07T21:18:49.400810Z", - "iopub.status.idle": "2026-03-07T21:18:51.082951Z", - "shell.execute_reply": "2026-03-07T21:18:51.082481Z" + "iopub.execute_input": "2026-03-14T15:52:54.632207Z", + "iopub.status.busy": "2026-03-14T15:52:54.632112Z", + "iopub.status.idle": "2026-03-14T15:52:55.984222Z", + "shell.execute_reply": "2026-03-14T15:52:55.983798Z" } }, "outputs": [], @@ -68,10 +68,10 @@ "id": "ca17ef47", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:51.084508Z", - "iopub.status.busy": "2026-03-07T21:18:51.084357Z", - "iopub.status.idle": "2026-03-07T21:18:51.086463Z", - "shell.execute_reply": "2026-03-07T21:18:51.085971Z" + "iopub.execute_input": "2026-03-14T15:52:55.985858Z", + "iopub.status.busy": "2026-03-14T15:52:55.985730Z", + "iopub.status.idle": "2026-03-14T15:52:55.987730Z", + "shell.execute_reply": "2026-03-14T15:52:55.987329Z" } }, "outputs": [], @@ -94,10 +94,10 @@ "id": "17a0e642", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:51.087750Z", - "iopub.status.busy": "2026-03-07T21:18:51.087638Z", - "iopub.status.idle": "2026-03-07T21:18:51.330625Z", - "shell.execute_reply": "2026-03-07T21:18:51.330189Z" + "iopub.execute_input": "2026-03-14T15:52:55.988838Z", + "iopub.status.busy": "2026-03-14T15:52:55.988766Z", + "iopub.status.idle": "2026-03-14T15:52:56.718045Z", + "shell.execute_reply": "2026-03-14T15:52:56.717490Z" } }, "outputs": [ @@ -105,7 +105,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'constant_events': 573, 'AIC': [4645.786426156066]}\n" + "{'constant_events': 573, 'AIC': [3554.3793854525825]}\n" ] } ], @@ -137,10 +137,10 @@ "id": "b5423ec1", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:51.331896Z", - "iopub.status.busy": "2026-03-07T21:18:51.331792Z", - "iopub.status.idle": "2026-03-07T21:18:51.333567Z", - "shell.execute_reply": "2026-03-07T21:18:51.333237Z" + "iopub.execute_input": "2026-03-14T15:52:56.719425Z", + "iopub.status.busy": "2026-03-14T15:52:56.719321Z", + "iopub.status.idle": "2026-03-14T15:52:56.721472Z", + "shell.execute_reply": "2026-03-14T15:52:56.720877Z" } }, "outputs": [], @@ -159,13 +159,22 @@ "id": "68209c75", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:51.334835Z", - "iopub.status.busy": "2026-03-07T21:18:51.334749Z", - "iopub.status.idle": "2026-03-07T21:18:52.117456Z", - "shell.execute_reply": "2026-03-07T21:18:52.116832Z" + "iopub.execute_input": "2026-03-14T15:52:56.722714Z", + "iopub.status.busy": "2026-03-14T15:52:56.722621Z", + "iopub.status.idle": "2026-03-14T15:52:57.576758Z", + "shell.execute_reply": "2026-03-14T15:52:57.576195Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: Tight layout not applied. tight_layout cannot make Axes width small enough to accommodate all Axes decorations\n", + " self._active_fig.tight_layout()\n" + ] + } + ], "source": [ "# SECTION 4: Data Visualization\n", "washout1 = np.loadtxt(DATA_DIR / \"mEPSCs\" / \"washout1.txt\", skiprows=1)\n", @@ -205,10 +214,10 @@ "id": "a442059e", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:52.118844Z", - "iopub.status.busy": "2026-03-07T21:18:52.118757Z", - "iopub.status.idle": "2026-03-07T21:18:52.120683Z", - "shell.execute_reply": "2026-03-07T21:18:52.120139Z" + "iopub.execute_input": "2026-03-14T15:52:57.578269Z", + "iopub.status.busy": "2026-03-14T15:52:57.578172Z", + "iopub.status.idle": "2026-03-14T15:52:57.580076Z", + "shell.execute_reply": "2026-03-14T15:52:57.579672Z" } }, "outputs": [], @@ -229,10 +238,10 @@ "id": "5a11a092", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:52.121960Z", - "iopub.status.busy": "2026-03-07T21:18:52.121862Z", - "iopub.status.idle": "2026-03-07T21:18:54.902008Z", - "shell.execute_reply": "2026-03-07T21:18:54.901529Z" + "iopub.execute_input": "2026-03-14T15:52:57.581205Z", + "iopub.status.busy": "2026-03-14T15:52:57.581125Z", + "iopub.status.idle": "2026-03-14T15:53:07.137554Z", + "shell.execute_reply": "2026-03-14T15:53:07.137142Z" } }, "outputs": [ @@ -262,13 +271,24 @@ "id": "b8956c4d", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:54.903552Z", - "iopub.status.busy": "2026-03-07T21:18:54.903387Z", - "iopub.status.idle": "2026-03-07T21:18:55.448887Z", - "shell.execute_reply": "2026-03-07T21:18:55.448284Z" + "iopub.execute_input": "2026-03-14T15:53:07.138818Z", + "iopub.status.busy": "2026-03-14T15:53:07.138730Z", + "iopub.status.idle": "2026-03-14T15:53:08.496758Z", + "shell.execute_reply": "2026-03-14T15:53:08.495920Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/fit.py:1174: UserWarning: Tight layout not applied. tight_layout cannot make Axes width small enough to accommodate all Axes decorations\n", + " fig.tight_layout()\n", + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: Tight layout not applied. tight_layout cannot make Axes width small enough to accommodate all Axes decorations\n", + " self._active_fig.tight_layout()\n" + ] + } + ], "source": [ "# SECTION 7: Perform Analysis\n", "fig = __tracker.new_figure(\"washout-analysis-results\")\n", @@ -285,10 +305,10 @@ "id": "02d87e14", "metadata": { "execution": { - "iopub.execute_input": "2026-03-07T21:18:55.450262Z", - "iopub.status.busy": "2026-03-07T21:18:55.450153Z", - "iopub.status.idle": "2026-03-07T21:18:55.453252Z", - "shell.execute_reply": "2026-03-07T21:18:55.452738Z" + "iopub.execute_input": "2026-03-14T15:53:08.498695Z", + "iopub.status.busy": "2026-03-14T15:53:08.498555Z", + "iopub.status.idle": "2026-03-14T15:53:08.501769Z", + "shell.execute_reply": "2026-03-14T15:53:08.501291Z" } }, "outputs": [], @@ -362,7 +382,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.12.9" }, "nstat": { "expected_figures": 4, diff --git a/notebooks/nSTATPaperExamples.ipynb b/notebooks/nSTATPaperExamples.ipynb index 92f4d7ab..ee39128d 100644 --- a/notebooks/nSTATPaperExamples.ipynb +++ b/notebooks/nSTATPaperExamples.ipynb @@ -14,10 +14,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "64f99156", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:12.343005Z", + "iopub.status.busy": "2026-03-14T15:53:12.342862Z", + "iopub.status.idle": "2026-03-14T15:53:16.360311Z", + "shell.execute_reply": "2026-03-14T15:53:16.359787Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'dataset_root': '/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/data_cache/nstat_data', 'paper_examples_loaded': 8}\n" + ] + } + ], "source": [ "# nSTAT-python notebook example: nSTATPaperExamples\n", "from pathlib import Path\n", @@ -72,10 +87,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "cf03a39b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.361564Z", + "iopub.status.busy": "2026-03-14T15:53:16.361420Z", + "iopub.status.idle": "2026-03-14T15:53:16.363273Z", + "shell.execute_reply": "2026-03-14T15:53:16.362921Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'const_condition_spikes': 573.0, 'const_model_aic': 3878.21450302445, 'const_model_bic': 3884.9477748694585, 'constant_acf_ci': 0.08188017465629394, 'decreasing_condition_spikes': 1870.0, 'piecewise_model_aic': 17581.121678089126, 'piecewise_model_bic': 17609.2027486881, 'piecewise_history_model_aic': 17585.640410038308, 'piecewise_history_model_bic': 17707.32504930054, 'dt_seconds': 0.01}\n" + ] + } + ], "source": [ "# SECTION 1: Experiment 1\n", "print(exp1_summary)" @@ -83,10 +113,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "facb48a1", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.364317Z", + "iopub.status.busy": "2026-03-14T15:53:16.364247Z", + "iopub.status.idle": "2026-03-14T15:53:16.382375Z", + "shell.execute_reply": "2026-03-14T15:53:16.382049Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Constant Mg condition: homogeneous Poisson fit')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 2: Constant Magnesium Concentration - Constant rate poisson\n", "fig = _fig(\"experiment1 constant rate\", figsize=(9.0, 4.0))\n", @@ -99,10 +147,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "b3453ddd", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.383620Z", + "iopub.status.busy": "2026-03-14T15:53:16.383546Z", + "iopub.status.idle": "2026-03-14T15:53:16.385498Z", + "shell.execute_reply": "2026-03-14T15:53:16.385074Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'decreasing_condition_spikes': 1870.0, 'piecewise_model_aic': 17581.122}\n" + ] + } + ], "source": [ "# SECTION 3: Varying Magnesium Concentration - Piecewise Constant rate poisson\n", "print({\"decreasing_condition_spikes\": exp1_summary[\"decreasing_condition_spikes\"], \"piecewise_model_aic\": round(float(exp1_summary[\"piecewise_model_aic\"]), 3)})" @@ -110,10 +173,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "6a1a4315", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.386566Z", + "iopub.status.busy": "2026-03-14T15:53:16.386485Z", + "iopub.status.idle": "2026-03-14T15:53:16.533405Z", + "shell.execute_reply": "2026-03-14T15:53:16.532596Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 4: Data Visualization\n", "fig = _fig(\"experiment1 washout raster and rates\", figsize=(10.0, 5.5))\n", @@ -135,10 +216,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "9bf5cb26", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.537209Z", + "iopub.status.busy": "2026-03-14T15:53:16.536864Z", + "iopub.status.idle": "2026-03-14T15:53:16.651196Z", + "shell.execute_reply": "2026-03-14T15:53:16.650395Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Constant-condition KS plot')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 5: Define Covariates for the analysis\n", "fig = _fig(\"experiment1 constant ks\", figsize=(6.0, 5.0))\n", @@ -155,10 +254,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "197eb8cd", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.652799Z", + "iopub.status.busy": "2026-03-14T15:53:16.652662Z", + "iopub.status.idle": "2026-03-14T15:53:16.710268Z", + "shell.execute_reply": "2026-03-14T15:53:16.709737Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Sequential correlation under constant Mg')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 6: Define how we want to analyze the data\n", "fig = _fig(\"experiment1 constant acf\", figsize=(7.0, 4.0))\n", @@ -173,10 +290,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "53c12b4c", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.711889Z", + "iopub.status.busy": "2026-03-14T15:53:16.711786Z", + "iopub.status.idle": "2026-03-14T15:53:16.763423Z", + "shell.execute_reply": "2026-03-14T15:53:16.762994Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Experiment 1 model comparison')" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 7: Compare constant-rate and piecewise-rate fits\n", "fig = _fig(\"experiment1 model summary\", figsize=(7.5, 4.0))\n", @@ -191,10 +326,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "b6751400", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.764705Z", + "iopub.status.busy": "2026-03-14T15:53:16.764626Z", + "iopub.status.idle": "2026-03-14T15:53:16.766478Z", + "shell.execute_reply": "2026-03-14T15:53:16.766095Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'n_samples': 51000.0, 'model1_aic': 9597.1177937183, 'model2_aic': 1615663214437.8765, 'model3_aic': 1618939900919.947, 'model1_bic': 9605.957374630007, 'model2_bic': 1615663214464.3953, 'model3_bic': 1618939900990.6636, 'peak_lag_seconds': 0.11900000000000001}\n" + ] + } + ], "source": [ "# SECTION 8: Experiment 2\n", "print(exp2_summary)" @@ -202,10 +352,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "be05f22d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.767400Z", + "iopub.status.busy": "2026-03-14T15:53:16.767330Z", + "iopub.status.idle": "2026-03-14T15:53:16.841133Z", + "shell.execute_reply": "2026-03-14T15:53:16.840534Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 9: Load the explicit-stimulus dataset\n", "fig = _fig(\"experiment2 stimulus and spikes\", figsize=(10.0, 5.5))\n", @@ -221,10 +389,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "f704736c", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.842747Z", + "iopub.status.busy": "2026-03-14T15:53:16.842527Z", + "iopub.status.idle": "2026-03-14T15:53:16.937296Z", + "shell.execute_reply": "2026-03-14T15:53:16.936743Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Stimulus lag search')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 10: Stimulus-lag search\n", "fig = _fig(\"experiment2 xcorr\", figsize=(7.0, 4.0))\n", @@ -237,10 +423,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "620d4f28", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.938686Z", + "iopub.status.busy": "2026-03-14T15:53:16.938594Z", + "iopub.status.idle": "2026-03-14T15:53:16.992109Z", + "shell.execute_reply": "2026-03-14T15:53:16.991529Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'BIC')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 11: Model comparison with stimulus effects\n", "fig = _fig(\"experiment2 aic bic\", figsize=(8.5, 4.0))\n", @@ -256,10 +460,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "0803cc01", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:16.993566Z", + "iopub.status.busy": "2026-03-14T15:53:16.993448Z", + "iopub.status.idle": "2026-03-14T15:53:17.059489Z", + "shell.execute_reply": "2026-03-14T15:53:17.059001Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Experiment 2 KS diagnostics')" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 12: KS diagnostics\n", "fig = _fig(\"experiment2 ks compare\", figsize=(6.5, 5.0))\n", @@ -278,10 +500,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "e8e2cecc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.060682Z", + "iopub.status.busy": "2026-03-14T15:53:17.060602Z", + "iopub.status.idle": "2026-03-14T15:53:17.127053Z", + "shell.execute_reply": "2026-03-14T15:53:17.126651Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'history windows')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 13: History-window scan\n", "fig = _fig(\"experiment2 history scan\", figsize=(8.5, 7.0))\n", @@ -298,10 +538,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "86253034", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.128502Z", + "iopub.status.busy": "2026-03-14T15:53:17.128418Z", + "iopub.status.idle": "2026-03-14T15:53:17.215039Z", + "shell.execute_reply": "2026-03-14T15:53:17.214554Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Experiment 2 coefficient intervals')" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 14: Coefficient summaries\n", "fig = _fig(\"experiment2 coefficients\", figsize=(9.0, 4.5))\n", @@ -318,10 +576,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "214e5be7", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.216358Z", + "iopub.status.busy": "2026-03-14T15:53:17.216274Z", + "iopub.status.idle": "2026-03-14T15:53:17.218116Z", + "shell.execute_reply": "2026-03-14T15:53:17.217782Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_trials': 20.0, 'psth_peak_hz': 124.99999999999994, 'psth_mean_hz': 53.749999999999986, 'total_spikes': 1075.0}\n" + ] + } + ], "source": [ "# SECTION 15: Experiment 3\n", "print(exp3_summary)" @@ -329,10 +602,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "f39efc64", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.219197Z", + "iopub.status.busy": "2026-03-14T15:53:17.219123Z", + "iopub.status.idle": "2026-03-14T15:53:17.266402Z", + "shell.execute_reply": "2026-03-14T15:53:17.266017Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Experiment 3 true conditional intensity')" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 16: Simulated PSTH setup\n", "fig = _fig(\"experiment3 true rate\", figsize=(9.0, 4.0))\n", @@ -345,10 +636,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "39340a3b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.267460Z", + "iopub.status.busy": "2026-03-14T15:53:17.267389Z", + "iopub.status.idle": "2026-03-14T15:53:17.363755Z", + "shell.execute_reply": "2026-03-14T15:53:17.363424Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 17: PSTH estimate\n", "fig = _fig(\"experiment3 psth\", figsize=(9.0, 5.0))\n", @@ -363,10 +672,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "e80bdf17", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.365024Z", + "iopub.status.busy": "2026-03-14T15:53:17.364945Z", + "iopub.status.idle": "2026-03-14T15:53:17.366889Z", + "shell.execute_reply": "2026-03-14T15:53:17.366489Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_trials': 25.0, 'num_time_bins': 50.0, 'state_rmse': 118.29190338935122, 'ci_coverage': 1.0, 'mean_qhat': 0.01646313440905654, 'mean_gammahat': -1.8798710833855103, 'log_likelihood': -0.19416505639708248}\n" + ] + } + ], "source": [ "# SECTION 18: Experiment 3b\n", "print(exp3b_summary)" @@ -374,10 +698,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "d3f429ac", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.367890Z", + "iopub.status.busy": "2026-03-14T15:53:17.367809Z", + "iopub.status.idle": "2026-03-14T15:53:17.432524Z", + "shell.execute_reply": "2026-03-14T15:53:17.432086Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time bin')" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 19: SSGLM state estimates\n", "fig = _fig(\"experiment3b state estimates\", figsize=(10.0, 5.0))\n", @@ -391,10 +733,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "9e327384", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.433791Z", + "iopub.status.busy": "2026-03-14T15:53:17.433701Z", + "iopub.status.idle": "2026-03-14T15:53:17.535469Z", + "shell.execute_reply": "2026-03-14T15:53:17.534921Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Mean Qhat across models')" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 20: SSGLM confidence intervals\n", "fig = _fig(\"experiment3b ci width\", figsize=(8.5, 4.5))\n", @@ -407,10 +767,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "2fd1d209", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.536935Z", + "iopub.status.busy": "2026-03-14T15:53:17.536819Z", + "iopub.status.idle": "2026-03-14T15:53:17.620928Z", + "shell.execute_reply": "2026-03-14T15:53:17.620388Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'gammahatAll')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 21: SSGLM gamma summaries\n", "fig = _fig(\"experiment3b gamma\", figsize=(8.5, 4.5))\n", @@ -423,10 +801,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "8150177d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.622168Z", + "iopub.status.busy": "2026-03-14T15:53:17.622057Z", + "iopub.status.idle": "2026-03-14T15:53:17.624007Z", + "shell.execute_reply": "2026-03-14T15:53:17.623569Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_cells_fit': 8.0, 'mean_delta_aic_gaussian_minus_zernike': 87.65071800242686, 'mean_delta_bic_gaussian_minus_zernike': 44.35785401057149}\n" + ] + } + ], "source": [ "# SECTION 22: Experiment 4\n", "print(exp4_summary)" @@ -434,10 +827,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "id": "19dabcf1", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.625328Z", + "iopub.status.busy": "2026-03-14T15:53:17.625236Z", + "iopub.status.idle": "2026-03-14T15:53:17.702686Z", + "shell.execute_reply": "2026-03-14T15:53:17.702244Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Animal 1 place-cell comparison')" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 23: Place-cell model comparison for Animal 1\n", "fig = _fig(\"experiment4 animal1 delta aic\", figsize=(7.5, 4.0))\n", @@ -450,10 +861,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "21ef2693", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.703873Z", + "iopub.status.busy": "2026-03-14T15:53:17.703786Z", + "iopub.status.idle": "2026-03-14T15:53:17.749203Z", + "shell.execute_reply": "2026-03-14T15:53:17.748742Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Animal 2 place-cell comparison')" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 24: Place-cell model comparison for Animal 2\n", "fig = _fig(\"experiment4 animal2 delta bic\", figsize=(7.5, 4.0))\n", @@ -466,10 +895,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "549f0f6b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.751047Z", + "iopub.status.busy": "2026-03-14T15:53:17.750895Z", + "iopub.status.idle": "2026-03-14T15:53:17.798703Z", + "shell.execute_reply": "2026-03-14T15:53:17.798223Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.92, 'Gaussian place-field estimate')" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 25: Place-field mesh for representative neuron\n", "fig = _fig(\"experiment4 gaussian mesh\", figsize=(9.0, 6.5))\n", @@ -480,10 +927,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "id": "3e3bb9b7", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.800106Z", + "iopub.status.busy": "2026-03-14T15:53:17.800017Z", + "iopub.status.idle": "2026-03-14T15:53:17.959702Z", + "shell.execute_reply": "2026-03-14T15:53:17.959326Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.92, 'Zernike place-field estimate')" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 26: Zernike place-field mesh\n", "fig = _fig(\"experiment4 zernike mesh\", figsize=(9.0, 6.5))\n", @@ -494,10 +959,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "id": "96c64e62", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.961250Z", + "iopub.status.busy": "2026-03-14T15:53:17.961149Z", + "iopub.status.idle": "2026-03-14T15:53:17.963084Z", + "shell.execute_reply": "2026-03-14T15:53:17.962613Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_cells': 20.0, 'decode_rmse': 0.6531227068287989}\n" + ] + } + ], "source": [ "# SECTION 27: Experiment 5\n", "print(exp5_summary)" @@ -505,12 +985,30 @@ }, { "cell_type": "code", + "execution_count": 29, "id": "b82b3148", - "metadata": {}, - "outputs": [], - "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:17.964225Z", + "iopub.status.busy": "2026-03-14T15:53:17.964144Z", + "iopub.status.idle": "2026-03-14T15:53:18.162204Z", + "shell.execute_reply": "2026-03-14T15:53:18.161847Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# SECTION 28: CIF setup \u2014 stimulus, conditional intensity, and spike raster\n", + "# SECTION 28: CIF setup — stimulus, conditional intensity, and spike raster\n", "fig = _fig(\"experiment5 cif setup\", figsize=(10.0, 8.0))\n", "axs = fig.subplots(3, 1, sharex=True)\n", "# Panel 1: driving stimulus\n", @@ -531,10 +1029,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "b88f5c9e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.163826Z", + "iopub.status.busy": "2026-03-14T15:53:18.163727Z", + "iopub.status.idle": "2026-03-14T15:53:18.277354Z", + "shell.execute_reply": "2026-03-14T15:53:18.276855Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Experiment 5 adaptive decoding')" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 29: 1-D decoding workflow\n", "fig = _fig(\"experiment5 stimulus decode\", figsize=(9.0, 4.5))\n", @@ -549,10 +1065,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "id": "b751e28e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.278798Z", + "iopub.status.busy": "2026-03-14T15:53:18.278701Z", + "iopub.status.idle": "2026-03-14T15:53:18.280607Z", + "shell.execute_reply": "2026-03-14T15:53:18.280132Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_cells': 30.0, 'num_samples': 2002.0, 'decode_rmse_x': 0.17559231283627283, 'decode_rmse_y': 0.14050174855856312}\n" + ] + } + ], "source": [ "# SECTION 30: Experiment 5b\n", "print(exp5b_summary)" @@ -560,12 +1091,30 @@ }, { "cell_type": "code", + "execution_count": 32, "id": "8b394726", - "metadata": {}, - "outputs": [], - "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.281650Z", + "iopub.status.busy": "2026-03-14T15:53:18.281577Z", + "iopub.status.idle": "2026-03-14T15:53:18.377038Z", + "shell.execute_reply": "2026-03-14T15:53:18.376560Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Neural raster')" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# SECTION 31: Reach setup \u2014 trajectory, kinematics, and spike raster\n", + "# SECTION 31: Reach setup — trajectory, kinematics, and spike raster\n", "fig = _fig(\"experiment5b reach setup\", figsize=(12.0, 8.0))\n", "axs = fig.subplots(2, 2, gridspec_kw={\"hspace\": 0.35, \"wspace\": 0.3})\n", "# (0,0) 2-D reach path\n", @@ -598,10 +1147,36 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 33, "id": "48493368", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.378259Z", + "iopub.status.busy": "2026-03-14T15:53:18.378179Z", + "iopub.status.idle": "2026-03-14T15:53:18.587247Z", + "shell.execute_reply": "2026-03-14T15:53:18.586775Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/iahncajigas/Library/CloudStorage/Dropbox/Claude/nSTAT-python/nstat/notebook_figures.py:42: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " self._active_fig.tight_layout()\n" + ] + }, + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 32: Goal-directed 2-D decode\n", "fig = _fig(\"experiment5b goal decode\", figsize=(9.5, 4.5))\n", @@ -617,10 +1192,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "id": "fdeed99c", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.588638Z", + "iopub.status.busy": "2026-03-14T15:53:18.588536Z", + "iopub.status.idle": "2026-03-14T15:53:18.674602Z", + "shell.execute_reply": "2026-03-14T15:53:18.674215Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 33: Free-model 2-D decode\n", "fig = _fig(\"experiment5b free decode\", figsize=(9.5, 4.5))\n", @@ -636,10 +1229,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "id": "98b8c650", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.675762Z", + "iopub.status.busy": "2026-03-14T15:53:18.675682Z", + "iopub.status.idle": "2026-03-14T15:53:18.677549Z", + "shell.execute_reply": "2026-03-14T15:53:18.677149Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'num_samples': 3000.0, 'num_cells': 24.0, 'state_accuracy': 0.899, 'decode_rmse_x': 0.21622609499000853, 'decode_rmse_y': 0.17204447423928235}\n" + ] + } + ], "source": [ "# SECTION 34: Experiment 6\n", "print(exp6_summary)" @@ -647,12 +1255,30 @@ }, { "cell_type": "code", + "execution_count": 36, "id": "0d85b9c5", - "metadata": {}, - "outputs": [], - "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.678901Z", + "iopub.status.busy": "2026-03-14T15:53:18.678826Z", + "iopub.status.idle": "2026-03-14T15:53:18.773200Z", + "shell.execute_reply": "2026-03-14T15:53:18.772826Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Neural raster')" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# SECTION 35: Hybrid setup \u2014 trajectory, state, kinematics, and spike raster\n", + "# SECTION 35: Hybrid setup — trajectory, state, kinematics, and spike raster\n", "fig = _fig(\"experiment6 hybrid setup\", figsize=(12.0, 8.0))\n", "axs = fig.subplots(2, 2, gridspec_kw={\"hspace\": 0.35, \"wspace\": 0.3})\n", "# (0,0) 2-D reach path coloured by state\n", @@ -684,10 +1310,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 37, "id": "01efa943", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.774694Z", + "iopub.status.busy": "2026-03-14T15:53:18.774591Z", + "iopub.status.idle": "2026-03-14T15:53:18.976392Z", + "shell.execute_reply": "2026-03-14T15:53:18.975907Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 36: Hybrid-filter simulation\n", "fig = _fig(\"experiment6 state probabilities\", figsize=(9.5, 4.5))\n", @@ -702,10 +1346,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 38, "id": "fbcd95b6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:18.977637Z", + "iopub.status.busy": "2026-03-14T15:53:18.977559Z", + "iopub.status.idle": "2026-03-14T15:53:19.050329Z", + "shell.execute_reply": "2026-03-14T15:53:19.049971Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'time (s)')" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 37: Hybrid-filter decoded positions\n", "fig = _fig(\"experiment6 decoded positions\", figsize=(9.5, 4.5))\n", @@ -721,10 +1383,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "id": "2c1cf92e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:19.051669Z", + "iopub.status.busy": "2026-03-14T15:53:19.051591Z", + "iopub.status.idle": "2026-03-14T15:53:19.122670Z", + "shell.execute_reply": "2026-03-14T15:53:19.122342Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Decoding summary across paper examples')" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 38: Canonical paper-example gallery summary\n", "fig = _fig(\"paper gallery summary\", figsize=(8.5, 4.5))\n", @@ -739,10 +1419,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "id": "bb79988e", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:19.123934Z", + "iopub.status.busy": "2026-03-14T15:53:19.123861Z", + "iopub.status.idle": "2026-03-14T15:53:19.169435Z", + "shell.execute_reply": "2026-03-14T15:53:19.168959Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Paper-example dataset scale')" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SECTION 39: Dataset-backed parity summary\n", "fig = _fig(\"paper dataset summary\", figsize=(8.5, 4.5))\n", @@ -762,10 +1460,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "id": "9aa50b57", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:19.170620Z", + "iopub.status.busy": "2026-03-14T15:53:19.170538Z", + "iopub.status.idle": "2026-03-14T15:53:19.207497Z", + "shell.execute_reply": "2026-03-14T15:53:19.206984Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'experiment1_piecewise_history_aic': 17585.64, 'experiment2_peak_lag_ms': 119.0, 'experiment4_mean_delta_aic': 87.651, 'experiment6_state_accuracy': 0.899}\n" + ] + } + ], "source": [ "# SECTION 40: Final summary\n", "print(\n", @@ -782,7 +1495,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 26, @@ -793,4 +1515,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/nSpikeTrainExamples.ipynb b/notebooks/nSpikeTrainExamples.ipynb index f6b44cae..7a3db256 100644 --- a/notebooks/nSpikeTrainExamples.ipynb +++ b/notebooks/nSpikeTrainExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "f95178ba", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:23.356248Z", + "iopub.status.busy": "2026-03-14T15:53:23.356121Z", + "iopub.status.idle": "2026-03-14T15:53:25.592984Z", + "shell.execute_reply": "2026-03-14T15:53:25.592255Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: nSpikeTrainExamples\n", @@ -35,9 +42,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "82c6de48", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:25.594815Z", + "iopub.status.busy": "2026-03-14T15:53:25.594657Z", + "iopub.status.idle": "2026-03-14T15:53:25.750084Z", + "shell.execute_reply": "2026-03-14T15:53:25.749436Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Example 1: Using the nspikeTrain Class\n", @@ -60,7 +74,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 4, diff --git a/notebooks/nstCollExamples.ipynb b/notebooks/nstCollExamples.ipynb index 835207c9..0dd14e53 100644 --- a/notebooks/nstCollExamples.ipynb +++ b/notebooks/nstCollExamples.ipynb @@ -2,9 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "f89859e2", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:29.854047Z", + "iopub.status.busy": "2026-03-14T15:53:29.853918Z", + "iopub.status.idle": "2026-03-14T15:53:32.033072Z", + "shell.execute_reply": "2026-03-14T15:53:32.032584Z" + } + }, "outputs": [], "source": [ "# nSTAT-python notebook example: nstCollExamples\n", @@ -51,9 +58,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "532cfdf4", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-14T15:53:32.034705Z", + "iopub.status.busy": "2026-03-14T15:53:32.034559Z", + "iopub.status.idle": "2026-03-14T15:53:32.130414Z", + "shell.execute_reply": "2026-03-14T15:53:32.129939Z" + } + }, "outputs": [], "source": [ "# SECTION 1: Test the nstColl Class\n", @@ -82,7 +96,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" }, "nstat": { "expected_figures": 3, diff --git a/nstat/data/paperHybridFilterExample.mat b/nstat/data/paperHybridFilterExample.mat new file mode 100644 index 00000000..355e2232 Binary files /dev/null and b/nstat/data/paperHybridFilterExample.mat differ diff --git a/nstat/decoding_algorithms.py b/nstat/decoding_algorithms.py index a37c6e3e..8ce18f4c 100644 --- a/nstat/decoding_algorithms.py +++ b/nstat/decoding_algorithms.py @@ -2043,9 +2043,24 @@ def PPHybridFilterLinear( X_u[model_index][:, time_index] = upd_x W_u[model_index][:, :, time_index] = upd_W - det_ratio = np.sqrt(max(np.linalg.det(upd_W), 0.0)) / max(np.sqrt(max(np.linalg.det(pred_W), 0.0)), 1e-15) - log_term = np.sum(obs[:, time_index] * np.log(np.clip(lambda_delta.reshape(-1), 1e-12, np.inf)) - lambda_delta.reshape(-1)) - likelihoods[model_index] = float(det_ratio * np.exp(np.clip(log_term, -200.0, 50.0))) + # Likelihood (Step 5): Laplace approximation + # p(n|s) = sqrt(det(W_u)/det(W_p)) * prod(exp(dN*log(lambda) - lambda)) + # Use log-determinants for numerical stability with high-dimensional + # covariance matrices whose determinants can be extremely small (e.g. + # 1e-46 for 6-D states with tiny eigenvalues). MATLAB computes + # sqrt(det(W_u))/sqrt(det(W_p)) directly without any clamping. + sign_u, logdet_u = np.linalg.slogdet(upd_W) + sign_p, logdet_p = np.linalg.slogdet(pred_W) + lambda_flat = lambda_delta.reshape(-1) + log_spike = np.sum( + obs[:, time_index] * np.log(np.where(lambda_flat > 0, lambda_flat, 1e-300)) + - lambda_flat + ) + if sign_u > 0 and sign_p > 0: + log_likelihood = 0.5 * (logdet_u - logdet_p) + log_spike + likelihoods[model_index] = float(np.exp(log_likelihood)) + else: + likelihoods[model_index] = 0.0 finite_likelihoods = likelihoods.copy() finite_likelihoods[~np.isfinite(finite_likelihoods)] = 0.0 @@ -2212,9 +2227,24 @@ def PPHybridFilter(A, Q, p_ij, Mu0, dN, lambdaCIFColl, binwidth=0.001, x0=None, X_u[model_index][:, time_index] = upd_x W_u[model_index][:, :, time_index] = upd_W - det_ratio = np.sqrt(max(np.linalg.det(upd_W), 0.0)) / max(np.sqrt(max(np.linalg.det(pred_W), 0.0)), 1e-15) - log_term = np.sum(obs[:, time_index] * np.log(np.clip(lambda_delta.reshape(-1), 1e-12, np.inf)) - lambda_delta.reshape(-1)) - likelihoods[model_index] = float(det_ratio * np.exp(np.clip(log_term, -200.0, 50.0))) + # Likelihood (Step 5): Laplace approximation + # p(n|s) = sqrt(det(W_u)/det(W_p)) * prod(exp(dN*log(lambda) - lambda)) + # Use log-determinants for numerical stability with high-dimensional + # covariance matrices whose determinants can be extremely small (e.g. + # 1e-46 for 6-D states with tiny eigenvalues). MATLAB computes + # sqrt(det(W_u))/sqrt(det(W_p)) directly without any clamping. + sign_u, logdet_u = np.linalg.slogdet(upd_W) + sign_p, logdet_p = np.linalg.slogdet(pred_W) + lambda_flat = lambda_delta.reshape(-1) + log_spike = np.sum( + obs[:, time_index] * np.log(np.where(lambda_flat > 0, lambda_flat, 1e-300)) + - lambda_flat + ) + if sign_u > 0 and sign_p > 0: + log_likelihood = 0.5 * (logdet_u - logdet_p) + log_spike + likelihoods[model_index] = float(np.exp(log_likelihood)) + else: + likelihoods[model_index] = 0.0 finite_likelihoods = likelihoods.copy() finite_likelihoods[~np.isfinite(finite_likelihoods)] = 0.0 diff --git a/parity/notebook_fidelity.yml b/parity/notebook_fidelity.yml index 4d0d3179..12ffd4d3 100644 --- a/parity/notebook_fidelity.yml +++ b/parity/notebook_fidelity.yml @@ -1,5 +1,5 @@ version: 1 -generated_on: '2026-03-12' +generated_on: '2026-03-14' source_repositories: matlab: https://github.com/cajigaslab/nSTAT python: https://github.com/cajigaslab/nSTAT-python diff --git a/tests/test_network_tutorial_builder.py b/tests/test_network_tutorial_builder.py index 275a6d40..5e1c1af6 100644 --- a/tests/test_network_tutorial_builder.py +++ b/tests/test_network_tutorial_builder.py @@ -16,6 +16,10 @@ def _normalize_notebook(notebook) -> None: cell["id"] = "normalized" cell["execution_count"] = None cell["outputs"] = [] + # Strip execution-related cell metadata added by nbconvert + cell.get("metadata", {}).pop("execution", None) + # Strip kernel-specific metadata that changes after execution + notebook.metadata.pop("language_info", None) def _cell_payload(cell) -> tuple[str, str, dict]: