vccs
Vue 3용 차트 컴포넌트 모음, Recharts에서 포팅
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗neurospatial is a Python library for spatial neuroscience. It helps scientists analyze how animals move through space and how neural activity represents it, with tools for environments, behavior, neural encoding and decoding, simulation, and visualization.
Our highest design priority is the scientist's experience: common analyses should be easy to discover, behave as a scientist reasonably expects, and produce scientifically correct, interpretable results. Clear assumptions, consistent interfaces, and useful error messages are part of that experience.
The long-term goal is to encompass the operations of spatial neuroscience in a coherent workflow, from recorded data and environment definition through analysis, statistical evaluation, visualization, and export. Spatial discretization and connectivity graphs provide a shared foundation for these operations. See the design principles for how we evaluate changes toward this goal.
pip install neurospatial
Or with uv:
uv pip install neurospatial
# Clone the repository
git clone https://github.com/edeno/neurospatial.git
cd neurospatial
# Install with uv (recommended)
uv sync
# Or with pip
pip install -e ".[dev]"
Note: This project uses uv for package management. If you have uv installed, all commands should be prefixed with uv run (e.g., uv run pytest).
neurospatial v0.8.0 has been tested with the following dependency versions:
| Package | Tested Version |
|---|---|
| Python | 3.13.13 |
| numpy | 2.3.4 |
| pandas | 2.3.3 |
| matplotlib | 3.10.7 |
| networkx | 3.5 |
| scipy | 1.16.3 |
| scikit-learn | 1.7.2 |
| scikit-image | 0.25.2 |
| shapely | 2.1.2 |
| alphashape | 1.3.1 |
| track-linearization | 2.4.0 |
| tqdm | 4.67.1 |
These versions match the current locked development environment. The supported
dependency ranges are defined in pyproject.toml, and CI exercises all supported
Python versions plus dedicated optional-dependency jobs.
For animation features, install optional dependencies:
pip install "neurospatial[animation]" # napari viewer and notebook widget
pip install "neurospatial[video]" # MP4, WebM, MOV, AVI and GIF export
pip install "neurospatial[fastplotlib]" # GPU viewer (Python 3.11 or newer)
HTML players need no extra packages. Video export uses the ffmpeg binary that
the video extra installs (through imageio-ffmpeg); no system ffmpeg is
needed.
Here's a minimal example showing how to create an environment from spatial data:
import numpy as np
from neurospatial import Environment
# Generate some 2D position data (e.g., from animal tracking)
# Shape: (n_samples, 2) for x, y coordinates in centimeters
position_data = np.array([
[0.0, 0.0],
[5.0, 5.0],
[10.0, 10.0],
[15.0, 5.0],
[20.0, 0.0],
# ... more positions
])
# Create an environment with 2 cm bins
env = Environment.from_samples(
positions=position_data,
bin_size=2.0, # 2 cm bins
name="OpenField"
)
# Query the environment
print(f"Environment has {env.n_bins} bins")
print(f"Dimensions: {env.n_dims}D")
print(f"Extent: {env.dimension_ranges}")
# Map a point to its bin
point = np.array([[10.5, 10.2]])
bin_idx = env.bin_at(point)
print(f"Point {point[0]} is in bin {bin_idx[0]}")
# Find neighbors of a bin
neighbors = env.neighbors(bins=bin_idx[0])
print(f"Bin {bin_idx[0]} has {len(neighbors)} neighbors")
# Visualize the environment
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
env.plot(ax=ax)
plt.show()
A neuroscientist's first task is usually: "I have an animal moving around and spikes from a neuron — show me where the cell fires." Here is the end-to-end pipeline using simulated data so you can run it right now without any setup:
import numpy as np
import matplotlib.pyplot as plt
from neurospatial import Environment
from neurospatial import compute_spatial_rate
from neurospatial.simulation import generate_population_spikes
from neurospatial.simulation.models import PlaceCellModel
from neurospatial.simulation.trajectory import simulate_trajectory_ou
# 1. Build a square open-field environment (60 × 60 cm, 2 cm bins).
xx, yy = np.meshgrid(np.linspace(0, 60, 31), np.linspace(0, 60, 31))
arena_corners = np.column_stack([xx.ravel(), yy.ravel()])
env = Environment.from_samples(arena_corners, bin_size=2.0)
env = env.with_metadata(units="cm")
# 2. Simulate ten minutes of foraging.
positions_t, times = simulate_trajectory_ou(
env, duration=600.0, speed_units="cm", seed=42,
)
# 3. Build a place cell tuned to the middle of the arena.
cell = PlaceCellModel(env, center=np.array([30.0, 30.0]), width=8.0,
max_rate=15.0, seed=42)
# 4. Generate the spike train from the cell's firing model + trajectory.
spike_times = generate_population_spikes(
[cell], times, positions_t, seed=42, show_progress=False,
)[0]
# 5. Recover the place field from spikes + trajectory.
result = compute_spatial_rate(
env, spike_times, times, positions_t,
bandwidth=5.0,
)
# Fast screen; field detection and cell classification are separate questions.
print("Place candidate:", result.is_place_cell(criterion="threshold"))
# 6. Plot.
fig, ax = plt.subplots()
result.plot(ax=ax)
ax.set_title("Place field recovered from simulated spikes")
plt.show()
The recovered field will be a Gaussian-like blob centered near (30, 30) — the same location we put the simulated cell. From here you can
swap in real spike times, change the trajectory, add more cells, or
detect place fields with detect_place_fields.
A field can also flag untuned low-count units. For a verdict that also
tests information against a circular-shift null, call
is_place_cell(env, spike_times, times, positions_t, criterion="shuffle", rng=0)
with the raw arrays (about 1000 map recomputes by default).
See the spatial rate-map tutorial
for the full tutorial.
To start with a real recording, run
the real-data tutorial: it downloads and verifies
the 75 MB J16 Zenodo dataset, then
analyzes its 203 hippocampal spike trains.
To download ahead of time, run uv run python data/load_bandit_data.py from the
repository root. See the data guide for units and attribution.
To go the other way — reconstruct position from population spikes — the one-call
path is
decode_session:
from neurospatial.decoding import decode_session runs the whole encode → bin →
decode pipeline and returns a DecodingResult. With precomputed count matrices
and population rate maps, use
decode_position(env, spike_counts, encoding_models, dt=dt). Pauses in tracking
are never decoded: time bins are formed only within recorded stretches, and
epochs=/spike_window= restrict them further. See
the position-decoding tutorial
for the full Bayesian decoding tutorial.
Gaps longer than max_gap=0.5 seconds are detected from times and excluded automatically.
epochs= selects analysis windows; spike_window= covers ephys that started late or stopped early.
Without spike_window, spikes are assumed recorded whenever position was; result.spike_window_assumed says so.
run_epochs = [(float(times[0]), float(times[-1]))]
t_ephys_start, t_ephys_stop = run_epochs[0]
result = compute_spatial_rate(env, spike_times, times, positions_t,
epochs=run_epochs, spike_window=(t_ephys_start, t_ephys_stop))
neurospatial discretizes continuous space into bins (also called nodes). Each bin represents a region of space with a center coordinate and (optionally) a size.
Active bins are bins that contain actual data or are considered part of the environment. When creating an environment from data samples, neurospatial can automatically infer which bins should be active based on:
This is essential for neuroscience applications where you want to focus on visited areas while excluding walls, obstacles, or unvisited regions.
Each environment includes a connectivity graph (NetworkX Graph) defining which bins are neighbors. This graph powers spatial queries like:
The connectivity graph includes mandatory metadata on all nodes and edges (positions, distances, vectors, indices) for robust spatial operations.
Layout engines define how space is discretized. Available engines include:
You typically don't interact with layout engines directly; instead, use the Environment factory methods which select the appropriate engine for you.
import numpy as np
from neurospatial import Environment
# In a real analysis, replace these three lines with your own loader:
# times = load_timestamps() # shape (n_timepoints,) in seconds
# position = load_tracking() # shape (n_timepoints, 2)
# speeds = load_speeds() # shape (n_timepoints,)
# Here we synthesize a 1 Hz, 60 s random walk in a 100x100 cm arena so the
# block is runnable end-to-end.
rng = np.random.default_rng(0)
times = np.linspace(0.0, 60.0, 60)
position = np.cumsum(rng.normal(0, 3.0, size=(60, 2)), axis=0) + 50.0
speeds = np.linalg.norm(np.diff(position, axis=0, prepend=position[:1]), axis=1) / np.gradient(times)
# Create environment with 5 cm bins, auto-detect active areas
env = Environment.from_samples(
positions=position,
bin_size=5.0, # cm
infer_active_bins=True,
dilate=True, # Expand active region
fill_holes=True, # Fill small gaps
name="Experiment1_OpenField"
)
# Treat this intentionally coarse 1 Hz example as continuous with max_gap=None.
# Compute occupancy with speed filtering
occupancy = env.occupancy(
times=times,
positions=position,
speed=speeds,
min_speed=2.5, # cm/s - filter slow periods
bandwidth=10.0, # cm - smooth the occupancy map
max_gap=None,
)
# Analyze movement patterns
transitions = env.transitions(times=times, positions=position, normalize=True, max_gap=None)
bin_sequence = env.bin_sequence(times=times, positions=position, dedup=True, max_gap=None)
from shapely.geometry import Polygon
# Define a circular arena (80 cm diameter)
theta = np.linspace(0, 2*np.pi, 100)
boundary = np.column_stack([40 * np.cos(theta), 40 * np.sin(theta)])
polygon = Polygon(boundary)
# Create environment bounded by polygon
env = Environment.from_polygon(
polygon=polygon,
bin_size=2.5, # cm
name="CircularArena"
)
import networkx as nx
# Define track graph (e.g., plus maze)
graph = nx.Graph()
graph.add_node(0, pos=(0.0, 0.0)) # center
graph.add_node(1, pos=(0.0, 50.0)) # north arm
graph.add_node(2, pos=(50.0, 0.0)) # east arm
graph.add_node(3, pos=(0.0, -50.0)) # south arm
graph.add_node(4, pos=(-50.0, 0.0)) # west arm
graph.add_edge(0, 1, edge_id=0, distance=50.0)
graph.add_edge(0, 2, edge_id=1, distance=50.0)
graph.add_edge(0, 3, edge_id=2, distance=50.0)
graph.add_edge(0, 4, edge_id=3, distance=50.0)
# Create 1D linearized environment
env = Environment.from_graph(
graph=graph,
edge_order=[(4, 0), (0, 1), (0, 2), (0, 3)], # traversal order
edge_spacing=0.0, # no gaps between edges
bin_size=2.0, # cm
track_width=10.0, # measured corridor width, cm; positions farther out get bin -1
name="PlusMaze"
)
# Convert 2D positions to 1D linearized coordinates
position_2d = np.array([[25.0, 0.0]]) # halfway down east arm
position_1d = env.to_linear(position_2d)
# And back
position_2d_reconstructed = env.linear_to_nd(position_1d)
from neurospatial import Regions
from shapely.geometry import Point
# Create environment
env = Environment.from_samples(position_data, bin_size=3.0)
# Define reward zones as circular regions (buffered points)
reward1_polygon = Point(10.0, 10.0).buffer(5.0) # 5 cm radius circle
reward2_polygon = Point(30.0, 30.0).buffer(5.0)
region_builder = Regions(env.regions.values())
region_builder.add("RewardZone1", polygon=reward1_polygon)
region_builder.add("RewardZone2", polygon=reward2_polygon)
# Or add a point region
region_builder.add("StartLocation", point=(0.0, 0.0))
env = env.with_regions(region_builder)
# Access region information
print(f"Number of regions: {len(env.regions)}")
print(f"Region names: {list(env.regions)}")
# Get region statistics
area = env.regions["RewardZone1"].area
center = env.regions["RewardZone1"].center
print(f"RewardZone1 area: {area:.2f}, center: {center}")
neurospatial includes a comprehensive simulation subpackage for generating synthetic spatial data, neural activity, and spike trains. This is essential for testing analysis pipelines, validating algorithms against ground truth, and creating educational examples.
import numpy as np
from neurospatial import Environment
from neurospatial.simulation import (
simulate_trajectory_ou,
PlaceCellModel,
generate_poisson_spikes,
)
# Sample positions used to infer the active region (e.g., a 100x100 cm arena).
# In real use, replace this with your own tracking data.
arena_data = np.random.default_rng(0).uniform(0, 100, size=(2000, 2))
# Create environment
env = Environment.from_samples(arena_data, bin_size=5.0)
env = env.with_metadata(units="cm") # Required for trajectory simulation
# Generate realistic trajectory using Ornstein-Uhlenbeck process.
# speed_units must match env.units exactly (no auto-conversion).
positions, times = simulate_trajectory_ou(
env,
duration=600.0, # seconds: ten minutes covers a 1 m arena
speed_units="cm",
speed_mean=8.0, # cm/s
speed_std=4.0, # cm/s: the animal speeds up and slows down
coherence_time=0.7, # seconds over which the speed changes
seed=42
)
# Create place cell with known ground truth
place_cell = PlaceCellModel(
env,
center=[50.0, 75.0], # field center in cm
width=10.0, # field width (Gaussian std)
max_rate=25.0 # peak firing rate in Hz
)
# Generate spikes
firing_rates = place_cell.firing_rate(positions, times)
# Seed spikes apart from the trajectory so spike draws do not track position.
spike_times = generate_poisson_spikes(firing_rates, times, seed=1)
# Validate with neurospatial analysis
from neurospatial import compute_spatial_rate
result = compute_spatial_rate(env, spike_times, times, positions)
detected_field = result.firing_rate
# Compare detected field to ground truth
true_center = place_cell.ground_truth['center']
print(f"True field center: {true_center}")
print(f"Detected peak: {result.peak_location()}")
Trajectory Simulation
Neural Models
.ground_truth for validationSpike Generation
High-Level API
open_field_session(), linear_track_session(), etc.validate_simulation() compares detected vs true parameterssimulate_session() handles trajectory + models + spikesFollow the place-field recovery tutorial to generate labeled spike trains, recover known fields, and interpret per-unit recovery outcomes and missing metrics. The API reference covers additional simulation models and customization.
Watch how a spatial field changes over time on the recording clock: a decoded posterior beside the animal's position, a field across laps, or a replay event at a tenth of its speed.
from neurospatial.animation import PositionOverlay
from neurospatial.decoding import decode_session
# Assumes you already have positions (n_samples, 2) in cm, times (n_samples,)
# in seconds, and spike_times (one array per unit), as in the place-field
# example above.
env = Environment.from_samples(positions, bin_size=2.5, units="cm")
result = decode_session(env, spike_times, times, positions, dt=0.025)
# One row per 25 ms decoding bin; the animal's position on its own clock
animal = PositionOverlay(positions, times=times, color="magenta")
# Explore interactively (returns a view; view.native is the napari viewer)
view = env.animate_fields(
result.posterior, frame_times=result.times, overlays=[animal],
backend="napari",
)
# Share a clip at half speed (returns the file path)
env.animate_fields(
result.posterior, frame_times=result.times, overlays=[animal],
save_path="decoding.mp4", speed=0.5,
)
A field row is held until the next one, up to max_gap=0.5 s; across longer
gaps in the recording it is shown as missing rather than frozen. The
backend can be "napari", "widget" (Jupyter), "fastplotlib" (GPU), or
a file: .mp4, .webm, .mov, .avi, .gif or .html. Without
backend=, a video or .html save_path writes that file; otherwise
Jupyter opens the notebook widget and other sessions open napari.
Keep a long posterior on disk as a memory-mapped array; rows are read only when a frame needs them:
import numpy as np
posterior = np.load("posterior.npy", mmap_mode="r") # (n_bins_time, env.n_bins)
env.animate_fields(posterior, frame_times=decode_times, backend="napari")
src/neurospatial/
├── environment/ # Environments, trajectories, fields, queries, and plots
├── layout/ # Grid, hexagonal, graph, polygon, mask, and mesh layouts
├── encoding/ # Place, grid, border, directional, view, and egocentric coding
├── decoding/ # Bayesian decoding, summaries, trajectories, and assemblies
├── behavior/ # Segmentation, navigation, decisions, rewards, and VTE
├── events/ # Peri-event alignment, detection, intervals, and regressors
├── stats/ # Circular statistics, shuffles, and surrogate data
├── simulation/ # Mazes, trajectories, neural models, spikes, and validation
├── io/ # Environment files plus NWB and pynapple interoperability
├── animation/ # Field animation: viewers, overlays, and video/HTML export
├── annotation/ # Interactive video and track-graph annotation
├── ops/ # Graph-aware distance, smoothing, transforms, and visibility
├── regions/ # Standalone region containers, operations, I/O, and plotting
└── composite.py # Multi-environment composition
tests/ # More than 9,000 automated tests across the package
examples/ # 9 task-oriented, paired Python/Jupyter tutorials
docs/ # User guides, API reference, examples, and contributor docs
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)uv run pytest)uv run ruff check . && uv run ruff format .)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)See CLAUDE.md for detailed development guidelines.
If you use neurospatial in your research, please cite:
@software{neurospatial2026,
author = {Denovellis, Eric},
title = {neurospatial: Spatial environment discretization for neuroscience},
year = {2026},
url = {https://github.com/edeno/neurospatial},
version = {0.8.0}
}
This project is licensed under the MIT License - see the LICENSE file for details.
Status: Alpha - API may change. Contributions and feedback welcome!