Skip to main content

Python Brain Organoid Simulator

Project description

PYNEURORG

Python Brain Organoid Simulator

pyneurorg is a Python module designed for simulating brain organoids, leveraging the power and flexibility of the Brian2 spiking neural network simulator. It provides a structured framework to model neurons and synapses within organoids, simulate their development and activity, and interact with them using simulated Microelectrode Arrays (MEAs) and calcium imaging techniques.

Objective

The primary goal of pyneurorg is to provide researchers with an accessible, modular, and extensible in-silico platform to:

  • Model the formation and maturation of neural networks in brain organoids.
  • Simulate electrophysiological experiments, including MEA-based stimulation and recording.
  • Simulate calcium imaging experiments to observe network activity with high spatial resolution.
  • Investigate the effects of various stimuli, genetic modifications, or disease states on organoid network dynamics.
  • Facilitate the exploration of structural and synaptic plasticity mechanisms.

Key Features

  • Brian2-Powered Core: Utilizes Brian2 for efficient and accurate simulation of spiking neuron and synapse models.
  • Organoid Construction: Flexible tools to create 3D organoid structures with defined neuronal populations and spatial arrangements.
  • MEA Integration:
    • Model MEA geometries.
    • Simulate targeted electrical stimulation of organoids via MEA electrodes.
    • Record spike activity and Local Field Potential (LFP) proxies from MEA electrodes.
  • Calcium Imaging Simulation:
    • Neuron models incorporating intracellular calcium dynamics.
    • Simulation of fluorescent calcium indicator signals (e.g., GCaMP's ΔF/F).
  • Network Plasticity: (Module named plasticity)
    • Implement common synaptic plasticity rules (e.g., STDP).
    • Simulate structural plasticity for activity-dependent network formation and pruning.
  • Electrophysiology Suite:
    • Generate complex stimulus patterns.
    • Comprehensive data recording capabilities.
    • Persistent data storage using SQLite for simulation results.
  • Analysis & Visualization:
    • Tools for spike train analysis (firing rates, synchrony, bursting).
    • Calcium trace analysis (ΔF/F, event detection).
    • Functional network inference from spike or calcium activity.
    • Plotting utilities for raster plots, LFP, calcium traces, and connectivity graphs.
  • Modularity and Extensibility: Designed to easily add new neuron models, synapse types, plasticity rules, or analysis techniques.
  • Jupyter Notebook Examples: A comprehensive suite of examples from basic setup to advanced simulations.

Installation

There are two primary ways to install pyneurorg:

Option 1: Install from PyPI (Recommended for users)

If pyneurorg is published on the Python Package Index (PyPI), you can install it directly using pip. This is the simplest method for end-users.

pip install pyneurorg

(Note: This requires the package to be uploaded to PyPI by the developers.)

Option 2: Install from a local directory (For developers or from source)

If you have cloned the pyneurorg repository or have the source code:

  1. Navigate to the top-level pyneurorg project directory in your terminal (this is the directory that contains the setup.py file and the src/ folder).

    cd path/to/your/pyneurorg_project_root
    
  2. (Recommended) Create and activate a virtual environment:

    python -m venv venv_pyneurorg
    source venv_pyneurorg/bin/activate  # On macOS/Linux
    # .\venv_pyneurorg\Scripts\activate  # On Windows
    
  3. Install pyneurorg in editable mode and its dependencies: The -e flag installs the package in "editable" mode. The . tells pip to look for setup.py in the current directory.

    pip install -e .
    

    This command reads setup.py (which should be configured for the src/ layout) and installs dependencies listed in setup.py (which ideally reads them from requirements.txt).

    For a full development setup, including tools for testing, linting, and documentation:

    pip install -r requirements-dev.txt
    

    Or, if you have defined extras in your setup.py (e.g., dev, docs, test):

    pip install -e .[dev,docs,test]
    

Project Directory Structure

The pyneurorg project is organized with a clear separation between the installable package code, documentation, examples, and tests. The main package code resides within the src/ directory.

pyneurorg/
├── docs/
├── examples/
│   ├── 00_Installation_and_Setup.ipynb
│   ├── 01_Creating_Your_First_Organoid.ipynb
│   ├── 02_Running_a_Simple_Simulation_and_Recording_Spikes.ipynb
│   ├── 03_Exploring_Neuron_and_Synapse_Models.ipynb
│   ├── 04_Spatial_Arrangement_and_Connectivity_Rules.ipynb
│   ├── 05_MEA_Stimulation_and_Basic_Recording.ipynb
│   ├── 06_Simulating_Spontaneous_Activity_and_LFP_Proxy.ipynb
│   ├── 07_Patterned_Stimulation_and_Response_Analysis.ipynb
│   ├── 08_Implementing_Synaptic_Plasticity_STDP.ipynb
│   ├── 09_Simulating_Structural_Plasticity_Network_Formation.ipynb
│   ├── 10_Modeling_Calcium_Dynamics_and_Fluorescence_Imaging.ipynb
│   ├── 11_Data_Persistence_Saving_and_Loading_with_SQLite.ipynb
│   ├── 12_Inferring_Functional_Networks_from_Spike_Data.ipynb
│   ├── 13_Inferring_Functional_Networks_from_Calcium_Data.ipynb
│   ├── 14_Advanced_Analysis_Bursting_and_Synchrony.ipynb
│   └── 15_Example_Modeling_a_Simplified_Disease_Phenotype.ipynb
├── src/
│   └── pyneurorg/
│       ├── analysis/
│       ├── core/
│       ├── electrophysiology/
│       ├── mea/
│       ├── organoid/
│       ├── plasticity/
│       ├── simulation/
│       ├── utils/
│       └── visualization/
└── tests/

Description of Top-Level Directories

  • docs/: Contains files for generating the project's official documentation (e.g., using Sphinx).
  • examples/: A collection of Jupyter Notebooks providing practical tutorials and demonstrating pyneurorg usage.
  • src/: Houses the source code of the pyneurorg Python package. Inside src/, the pyneurorg/ subdirectory is the main package.
  • tests/: Contains automated tests (unit, integration) for the pyneurorg codebase.

Usage

Refer to the Quick Start Examples or to the Jupyter Notebooks in the examples/ directory for detailed usage instructions. Start with 00_Installation_and_Setup.ipynb.

Quick Start Examples

The following examples demonstrate basic functionalities of pyneurorg. Ensure pyneurorg is installed before running them.

Stimulating the Organoid with an MEA

This snippet shows how to set up a simple organoid, an MEA, and stimulate a specific region.

import brian2 as b2
import matplotlib.pyplot as plt
import numpy as np # Often needed with Brian2

# Assuming pyneurorg modules are importable
from pyneurorg.organoid.organoid import Organoid
from pyneurorg.organoid import spatial
from pyneurorg.core import neuron_models
from pyneurorg.mea.mea import MEA
from pyneurorg.simulation.simulator import Simulator
from pyneurorg.electrophysiology import stimulus_generator
from pyneurorg.visualization import spike_plotter

# Ensure reproducible results
b2.seed(423)
b2.prefs.codegen.target = 'numpy'

# 1. Create an Organoid
lif_model_definition = neuron_models.LIFNeuron(
    tau_m=20*b2.ms, v_rest=-65*b2.mV, v_reset=-65*b2.mV,
    v_thresh=-50*b2.mV, R_m=100*b2.Mohm, I_tonic=0.0*b2.nA,
    refractory_period=2*b2.ms
)
my_organoid_stim = Organoid(name="StimulationOrganoid")
neuron_positions_stim = spatial.random_positions_in_sphere(N=100, radius=150*b2.um)
my_organoid_stim.add_neurons(
    name="target_neurons", num_neurons=100, model_name="LIFNeuron",
    model_params={
        'tau_m': 20*b2.ms, 'v_rest': -65*b2.mV, 'v_reset': -65*b2.mV,
        'v_thresh': -50*b2.mV, 'R_m': 100*b2.Mohm, 'I_tonic': 0.0*b2.nA,
        'refractory_period': 2*b2.ms
    },
    positions=neuron_positions_stim, initial_values={'v': -65*b2.mV}
)

# 2. Create an MEA
electrode_coords = [(i*40*b2.um, j*40*b2.um, 0*b2.um) for i in range(4) for j in range(4)]
my_mea_stim = MEA(electrode_positions=electrode_coords)

# 3. Setup the Simulator
sim_stim = Simulator(organoid=my_organoid_stim, mea=my_mea_stim, brian2_dt=0.1*b2.ms)

# 4. Define a stimulus
stimulation_duration = 150*b2.ms
stimulus_waveform = stimulus_generator.create_pulse_train(
    amplitude=0.9*b2.nA, frequency=8*b2.Hz,
    pulse_width=2.5*b2.ms, duration=stimulation_duration,
    dt=sim_stim.brian_dt
)

# 5. Add stimulus to an MEA electrode
sim_stim.add_stimulus(
    electrode_id=0, stimulus_waveform=stimulus_waveform,
    target_group_name="target_neurons", influence_radius=50*b2.um
)

# 6. Add a spike monitor
sim_stim.add_recording(
    monitor_name="stimulation_spikes", monitor_type="spike",
    target_group_name="target_neurons", record=True
)

# 7. Run simulation
print("Running MEA stimulation example...")
sim_stim.run(stimulation_duration, report='text', report_period=50*b2.ms)
print("Stimulation simulation finished.")

# 8. Retrieve and plot data
spike_data = sim_stim.get_data("stimulation_spikes")
print(f"Recorded {len(spike_data.i)} spikes during stimulation.")
if len(spike_data.i) > 0:
    fig, ax = plt.subplots(figsize=(10,5))
    spike_plotter.plot_raster(
        spike_indices=spike_data.i, spike_times=spike_data.t,
        duration=stimulation_duration, ax=ax,
        title="Spikes from MEA Stimulation", marker_size=3
    )
    plt.tight_layout()
    plt.show()
else:
    print("No spikes recorded. Try increasing stimulus amplitude or influence_radius.")

Reading Activity: Spikes and Calcium Imaging (Full Example)

This snippet demonstrates recording spikes and simulated calcium fluorescence from active neurons.

import brian2 as b2
import numpy as np
import matplotlib.pyplot as plt

# Assuming pyneurorg modules are importable
from pyneurorg.organoid.organoid import Organoid
from pyneurorg.organoid import spatial
from pyneurorg.core import neuron_models
from pyneurorg.simulation.simulator import Simulator
from pyneurorg.visualization import spike_plotter, calcium_plotter
from pyneurorg.analysis import calcium_analysis

# Ensure reproducible results
b2.seed(12346) # Different seed
np.random.seed(12346)
b2.prefs.codegen.target = 'numpy'

# 1. Define parameters and get the calcium-enabled neuron model definition
# These parameters should match what your LIFCalciumFluorNeuron function expects
calcium_model_function_params = {
    'tau_m': 15*b2.ms, 'v_rest': -65*b2.mV, 'v_reset': -65*b2.mV, 'v_thresh': -50*b2.mV,
    'R_m': 120*b2.Mohm, 'refractory_period': 3*b2.ms,
    'tau_ca': 80*b2.ms,          # Example calcium decay time constant
    'ca_spike_increment': 0.25, # Example arbitrary unit increment of Ca per spike (unitless or specific based on model)
    'tau_f': 120*b2.ms,          # Example fluorescence decay (related to indicator off-rate)
    'k_f': 0.6,                  # Example scaling factor for fluorescence from Ca (unitless or specific)
    'I_tonic': 0.21*b2.nA        # Tonic current to induce spontaneous spiking
}
try:
    # Fetch the model definition dictionary
    calcium_model_def = neuron_models.LIFCalciumFluorNeuron(**calcium_model_function_params)
    model_name_for_organoid = "LIFCalciumFluorNeuron" # Use the function name string for Organoid
except AttributeError:
    print("CRITICAL ERROR: 'LIFCalciumFluorNeuron' model function not found in pyneurorg.core.neuron_models.")
    print("This example cannot proceed without it for calcium imaging simulation.")
    # To prevent error in a batch run of README, could raise SystemExit or use a fallback
    # For now, let's assume it exists for a "real" example.
    raise SystemExit("LIFCalciumFluorNeuron model is required for this example.")


# 2. Create an Organoid
calcium_activity_organoid = Organoid(name="CalciumActivityOrganoid")
num_activity_neurons = 20 # Smaller number for clearer Vm/Ca plots
activity_positions = spatial.random_positions_in_cube(N=num_activity_neurons, side_length=80*b2.um)

# Initial membrane potentials varied to desynchronize initial firing
initial_v_activity = np.random.uniform(-65, -55, num_activity_neurons) * b2.mV
# Initial calcium and fluorescence (assuming they start at baseline, e.g., 0)
initial_ca = calcium_model_def['namespace'].get('Ca_default_init', 0.0)
initial_f = calcium_model_def['namespace'].get('F_default_init', 0.0)

active_neurons_group = calcium_activity_organoid.add_neurons(
    name="active_ca_neurons",
    num_neurons=num_activity_neurons,
    model_name=model_name_for_organoid, # Pass the string name
    model_params=calcium_model_function_params, # Pass the parameters for the model function
    positions=activity_positions,
    initial_values={
        'v': initial_v_activity,
        'Ca': initial_ca, # Assuming 'Ca' is a state variable in the model
        'F': initial_f    # Assuming 'F' is a state variable in the model
    }
)

# 3. Setup the Simulator
sim_activity = Simulator(organoid=calcium_activity_organoid, brian2_dt=0.1*b2.ms)
# sim_activity.setup_simulation_entry(...) # For SQLite logging

# 4. Add SpikeMonitor for all neurons in the group
sim_activity.add_recording(
    monitor_name="activity_spikes",
    monitor_type="spike",
    target_group_name="active_ca_neurons",
    record=True
)

# 5. Add StateMonitor for Vm, Ca, and F for a subset of neurons
# Record from first few neurons, or all if num_activity_neurons is small
indices_to_record_state = list(range(min(3, num_activity_neurons)))
if indices_to_record_state: # Only add if there are neurons to record
    sim_activity.add_recording(
        monitor_name="activity_states",
        monitor_type="state",
        target_group_name="active_ca_neurons",
        variables_to_record=['v', 'Ca', 'F'], # Must match variables in neuron model
        record_indices=indices_to_record_state,
        dt=0.2*b2.ms # Record states at a slightly lower frequency than simulation dt
    )
else:
    print("No neurons selected for state recording.")


# 6. Run simulation
activity_simulation_duration = 600*b2.ms
print(f"\nRunning activity and calcium imaging example for {activity_simulation_duration}...")
sim_activity.run(activity_simulation_duration, report='text', report_period=100*b2.ms)
print("Activity and calcium simulation finished.")

# 7. Retrieve and Plot Spike Data
spike_data_activity = sim_activity.get_data("activity_spikes")
print(f"Activity example recorded {len(spike_data_activity.i)} spikes.")

if len(spike_data_activity.i) > 0:
    fig_spikes_act, ax_spikes_act = plt.subplots(figsize=(10,4))
    spike_plotter.plot_raster(
        spike_indices=spike_data_activity.i,
        spike_times=spike_data_activity.t,
        duration=activity_simulation_duration,
        ax=ax_spikes_act,
        title="Spikes from Active Calcium-Model Neurons"
    )
    plt.tight_layout()
    plt.show()

# 8. Retrieve, Process, and Plot Vm, Calcium, and Fluorescence Data
if "activity_states" in sim_activity.monitors:
    state_data_activity = sim_activity.get_data("activity_states")
    time_pts_ms = state_data_activity.t / b2.ms

    # Plot Vm
    fig_vm, ax_vm = plt.subplots(figsize=(12,4))
    if hasattr(state_data_activity, 'v'):
        for i in range(state_data_activity.v.shape[0]):
            original_idx = indices_to_record_state[i]
            ax_vm.plot(time_pts_ms, state_data_activity.v[i,:] / b2.mV, label=f"Neuron {original_idx}")
        ax_vm.set_xlabel("Time (ms)"); ax_vm.set_ylabel("Vm (mV)")
        ax_vm.set_title("Membrane Potential (Vm) Traces"); ax_vm.legend(fontsize='small')
        plt.tight_layout(); plt.show()

    # Plot raw Ca and F traces
    if hasattr(state_data_activity, 'Ca') and hasattr(state_data_activity, 'F'):
        fig_ca_f, axes_ca_f = plt.subplots(2, 1, sharex=True, figsize=(12, 6))
        for i in range(state_data_activity.F.shape[0]): # Assuming F and Ca have same shape[0]
            original_idx = indices_to_record_state[i]
            axes_ca_f[0].plot(time_pts_ms, state_data_activity.F[i,:], label=f"Neuron {original_idx}")
            axes_ca_f[1].plot(time_pts_ms, state_data_activity.Ca[i,:], label=f"Neuron {original_idx}")
        axes_ca_f[0].set_ylabel("Fluorescence (F arbitrary units)")
        axes_ca_f[0].set_title("Raw Fluorescence Traces")
        axes_ca_f[0].legend(fontsize='small')
        axes_ca_f[1].set_ylabel("Calcium (Ca arbitrary units)")
        axes_ca_f[1].set_xlabel("Time (ms)")
        axes_ca_f[1].set_title("Raw Intracellular Calcium Traces")
        axes_ca_f[1].legend(fontsize='small')
        plt.tight_layout(); plt.show()

        # Calculate and plot ΔF/F
        try:
            # Using the placeholder ΔF/F calculation from previous response
            F_traces_np = np.array(state_data_activity.F)
            delta_F_F_traces_list = []
            for i_trace in range(F_traces_np.shape[0]):
                f_single_trace = F_traces_np[i_trace,:]
                baseline_end_idx = max(1, int(0.1 * len(f_single_trace))) # Use first 10% as baseline F0
                f0 = np.mean(f_single_trace[:baseline_end_idx])
                if abs(f0) < 1e-9: f0 = 1e-9 # Avoid division by zero or very small F0
                delta_F_F_traces_list.append((f_single_trace - f0) / f0)
            delta_F_F_traces_np = np.array(delta_F_F_traces_list)

            if delta_F_F_traces_np.size > 0:
                fig_dff, ax_dff = plt.subplots(figsize=(12,4))
                traces_dict_for_plotter = {
                    indices_to_record_state[i]: delta_F_F_traces_np[i,:] for i in range(delta_F_F_traces_np.shape[0])
                }
                # Assuming calcium_plotter.plot_calcium_traces can take a dict of traces
                # If not, plot directly:
                for i_plot in range(delta_F_F_traces_np.shape[0]):
                    ax_dff.plot(time_pts_ms, delta_F_F_traces_np[i_plot,:], label=f"Neuron {indices_to_record_state[i_plot]}")
                
                ax_dff.set_xlabel("Time (ms)"); ax_dff.set_ylabel("ΔF/F")
                ax_dff.set_title("Simulated ΔF/F Traces"); ax_dff.legend(fontsize='small')
                plt.tight_layout(); plt.show()

        except AttributeError as e_dff_attr:
            print(f"Note: Could not plot ΔF/F. A required analysis function might be missing: {e_dff_attr}")
        except Exception as e_dff:
            print(f"An error occurred during ΔF/F processing or plotting: {e_dff}")
    else:
        print("Fluorescence (F) or Calcium (Ca) variables not found in recorded states.")
else:
    print("State monitor 'activity_states' not found or no data to plot.")

# if hasattr(sim_activity, 'close'): sim_activity.close()

Overview of Example Notebooks (examples/)

The examples/ directory provides a guided tour through pyneurorg's features:

  • 00_Installation_and_Setup.ipynb: Guides users through the installation process of pyneurorg and its dependencies, and includes a basic test to confirm a working setup.
  • 01_Creating_Your_First_Organoid.ipynb: Introduces the Organoid class, demonstrating how to define neuronal populations, assign spatial positions, and conceptually add synapses.
  • 02_Running_a_Simple_Simulation_and_Recording_Spikes.ipynb: Shows how to use the Simulator class to run a basic simulation of a created organoid and record neuronal spikes and membrane potentials.
  • 03_Exploring_Neuron_and_Synapse_Models.ipynb: Details the various predefined neuron and synapse models available in pyneurorg.core and how to customize their parameters.
  • 04_Spatial_Arrangement_and_Connectivity_Rules.ipynb: Focuses on creating 3D spatial layouts for neurons and defining rules for establishing synaptic connections between them (e.g., based on distance or probability).
  • 05_MEA_Stimulation_and_Basic_Recording.ipynb: Demonstrates how to model a Microelectrode Array (MEA), simulate electrical stimulation of the organoid via MEA electrodes, and record activity.
  • 06_Simulating_Spontaneous_Activity_and_LFP_Proxy.ipynb: Illustrates setting up a more complex network (e.g., with excitatory/inhibitory balance) to generate spontaneous activity and compute a proxy for Local Field Potentials (LFP).
  • 07_Patterned_Stimulation_and_Response_Analysis.ipynb: Shows how to use the stimulus_generator to create complex temporal patterns of stimulation and analyze the network's response (e.g., PSTH).
  • 08_Implementing_Synaptic_Plasticity_STDP.ipynb: Explains how to incorporate Spike-Timing-Dependent Plasticity (STDP) rules to modify synaptic strengths based on activity.
  • 09_Simulating_Structural_Plasticity_Network_Formation.ipynb: Covers the simulation of structural changes in the network, such as the activity-dependent formation and pruning of synapses.
  • 10_Modeling_Calcium_Dynamics_and_Fluorescence_Imaging.ipynb: Details how to use neuron models that include intracellular calcium dynamics and simulate the corresponding fluorescence signals from indicators like GCaMP.
  • 11_Data_Persistence_Saving_and_Loading_with_SQLite.ipynb: Demonstrates how to save simulation results (metadata, spikes, state variables) to an SQLite database and load them back for later analysis.
  • 12_Inferring_Functional_Networks_from_Spike_Data.ipynb: Shows how to use analysis tools to infer functional connectivity maps from recorded spike train data (e.g., using STTC or cross-correlation).
  • 13_Inferring_Functional_Networks_from_Calcium_Data.ipynb: Similar to the above, but demonstrates inferring functional networks from simulated calcium imaging data.
  • 14_Advanced_Analysis_Bursting_and_Synchrony.ipynb: Covers more advanced analysis techniques, such as detecting bursting patterns in neuronal activity and quantifying network synchrony.
  • 15_Example_Modeling_a_Simplified_Disease_Phenotype.ipynb: Provides an example of how pyneurorg can be used to model and investigate network alterations associated with a simplified disease phenotype.

License

This project is licensed under the MIT License.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyneurorg-0.1.58.tar.gz (45.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyneurorg-0.1.58-py3-none-any.whl (45.0 kB view details)

Uploaded Python 3

File details

Details for the file pyneurorg-0.1.58.tar.gz.

File metadata

  • Download URL: pyneurorg-0.1.58.tar.gz
  • Upload date:
  • Size: 45.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.14.0a7

File hashes

Hashes for pyneurorg-0.1.58.tar.gz
Algorithm Hash digest
SHA256 b260e94f3ca4d1b6e80a1e6d1cac77eb921409a1072543b77f3288dd83dfd2ad
MD5 77d8af997ba95ff9585437b872c0aabd
BLAKE2b-256 c8ab2298c83ac2600e84f5f49f61b8f5f6ac60edc5af5a7878e6b421303b5f90

See more details on using hashes here.

File details

Details for the file pyneurorg-0.1.58-py3-none-any.whl.

File metadata

  • Download URL: pyneurorg-0.1.58-py3-none-any.whl
  • Upload date:
  • Size: 45.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.14.0a7

File hashes

Hashes for pyneurorg-0.1.58-py3-none-any.whl
Algorithm Hash digest
SHA256 1655cec215e82d7110b72cb4d435a31812b0fc24cfc599b631349290821c68a7
MD5 e9a8be0eee310c3715a96c77b3a8d20e
BLAKE2b-256 9aa46587e312a1bbbbee68fa5cc42b9ebac91c63f8e5572f9eb91f29953cefca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page