Quickstart¶
This guide provides a quick introduction to neurospatial's main features. You'll learn how to create environments, query spatial bins, and define regions of interest.
Basic Environment Creation¶
The most common use case is creating an environment from spatial data samples:
import numpy as np
from neurospatial import Environment
# Simulate position tracking data (x, y coordinates in cm)
# Shape: (n_timepoints, 2)
position_data = np.array([
[10.0, 10.0],
[12.0, 11.0],
[15.0, 15.0],
[18.0, 12.0],
[20.0, 10.0],
[22.0, 15.0],
[25.0, 20.0],
# ... more positions
])
# Create environment with 2 cm bins
env = Environment.from_samples(
positions=position_data,
bin_size=2.0, # Required parameter
name="OpenField"
)
# Inspect the environment
print(f"Environment: {env.name}")
print(f"Number of bins: {env.n_bins}")
print(f"Dimensions: {env.n_dims}D")
print(f"Spatial extent: {env.dimension_ranges}")
Spatial Queries¶
Once you have an environment, you can perform various spatial queries:
# Map a point to its bin index
point = np.array([[15.0, 15.0]])
bin_idx = env.bin_at(point)[0]
print(f"Point {point[0]} is in bin {bin_idx}")
# Get bin center coordinates
center = env.bin_centers[bin_idx]
print(f"Bin {bin_idx} center: {center}")
# Check if a point is in the environment
is_inside = env.contains(point)[0]
print(f"Point is inside environment: {is_inside}")
# Find neighbors of a bin
neighbors = env.neighbors(bin_idx)
print(f"Bin {bin_idx} has {len(neighbors)} neighbors: {neighbors}")
# Calculate geodesic distance between bins
# (path_between/distance_to operate on bin indices; distance_between expects
# point coordinates and is the right call when you have raw (x, y) positions.)
bin_a, bin_b = 0, 10
distance = float(env.distance_to([bin_b])[bin_a])
print(f"Distance between bins {bin_a} and {bin_b}: {distance:.2f}")
# Find shortest path
path = env.path_between(bin_a, bin_b)
print(f"Shortest path: {path}")
Computing Occupancy¶
A common neuroscience workflow is computing time spent in each bin:
# Assign all positions to bins
bin_indices = env.bin_at(position_data)
# Compute occupancy histogram
occupancy, _ = np.histogram(
bin_indices,
bins=np.arange(env.n_bins + 1)
)
print(f"Time in bin 0: {occupancy[0]} samples")
print(f"Total occupied bins: {np.sum(occupancy > 0)}")
Defining Regions of Interest¶
You can define named regions (ROIs) within your environment:
from shapely.geometry import Point
# Add a circular reward zone (5 cm radius)
reward_polygon = Point(15.0, 15.0).buffer(5.0)
env.regions.add("RewardZone", polygon=reward_polygon)
# Add a point marker for start location
env.regions.add("StartLocation", point=(10.0, 10.0))
# Query regions
print(f"Number of regions: {len(env.regions)}")
print(f"Region names: {env.regions.list_names()}")
# Get region statistics
area = env.regions.area("RewardZone")
center = env.regions.region_center("RewardZone")
print(f"Reward zone area: {area:.2f} cm²")
print(f"Reward zone center: {center}")
Visualizing the Environment¶
Visualize your environment with matplotlib:
import matplotlib.pyplot as plt
# Create figure
fig, ax = plt.subplots(figsize=(8, 8))
# Plot the environment
env.plot(ax=ax)
# Overlay trajectory
ax.plot(
position_data[:, 0],
position_data[:, 1],
'r-', alpha=0.5, linewidth=1,
label='Trajectory'
)
ax.set_title(env.name)
ax.legend()
plt.show()
Automatic Active Bin Detection¶
For sparse data, you can automatically detect active regions:
# Create environment with automatic active bin detection
env_auto = Environment.from_samples(
positions=position_data,
bin_size=2.0,
infer_active_bins=True, # Enable automatic detection
bin_count_threshold=1, # Minimum samples per bin
dilate=True, # Expand active region
fill_holes=True, # Fill gaps
name="OpenField_Auto"
)
print(f"Active bins: {env_auto.n_bins}")
Creating Masked Environments¶
Create environments bounded by polygons:
from shapely.geometry import Polygon
# Define a circular arena (40 cm radius)
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_circle = Environment.from_polygon(
polygon=polygon,
bin_size=2.5,
name="CircularArena"
)
print(f"Circular arena bins: {env_circle.n_bins}")
Working with Different Layout Types¶
neurospatial supports multiple layout engines. from_samples() infers the
active region from sample points and currently supports RegularGrid (the
default) and Hexagonal layouts:
# Hexagonal layout (more uniform neighbor distances)
env_hex = Environment.from_samples(
positions=position_data,
bin_size=2.0,
layout="Hexagonal",
name="HexEnvironment"
)
For other layouts (Triangular mesh, Shapely polygon, masked grid, etc.) use
the matching factory or Environment.from_layout(...). See the
layout engines guide for the full list and
required parameters.
Next Steps¶
Now that you understand the basics, explore:
- Core Concepts: Deeper understanding of bins, graphs, and layout engines
- User Guide: Detailed guides for specific features
- API Reference: Complete API documentation
- Examples: Real-world use cases with Jupyter notebooks
Common Patterns¶
Pattern 1: Spatial Firing Rate Map¶
from neurospatial.encoding import compute_spatial_rate, compute_spatial_rates
# Single neuron
result = compute_spatial_rate(
env,
spike_times,
times,
position_data,
smoothing_method="diffusion_kde",
bandwidth=5.0,
)
firing_rate = result.firing_rate
peak_location = result.peak_location()
spatial_info = result.spatial_information()
# Population batch. Reuses occupancy and binning across neurons.
population = compute_spatial_rates(
env,
[spike_times_cell0, spike_times_cell1, spike_times_cell2],
times,
position_data,
n_jobs=2,
)
summary = population.to_dataframe()
Pattern 2: Distance to Target¶
# Find bin containing target location
target = np.array([[20.0, 20.0]])
target_bin = env.bin_at(target)[0]
# Distance from every bin to the target bin (geodesic by default)
distances = env.distance_to([target_bin])
print(f"Mean distance to target: {np.mean(distances):.2f} cm")
Pattern 3: Region Occupancy¶
# Define multiple zones
zones = {
"Zone1": Point(10.0, 10.0).buffer(5.0),
"Zone2": Point(25.0, 25.0).buffer(5.0),
}
for name, polygon in zones.items():
env.regions.add(name, polygon=polygon)
# Discretize the trajectory once (positions: (n_time, n_dims) of (x, y) coordinates)
position_bin_indices = env.bin_sequence(times, position_data)
# Compute time in each zone using the high-level region API
for name in zones.keys():
region_bins = env.bins_in_region(name)
time_in_region = int(np.sum(np.isin(position_bin_indices, region_bins)))
print(f"Time in {name}: {time_in_region} samples")