Skip to content

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:

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")