Layout Engines¶
Layout engines define how continuous space is discretized into bins. This page helps you choose the right engine for your analysis.
Quick Decision Guide¶
Most common scenarios:
- Standard open field experiment → Use
Environment.from_samples()(defaults to RegularGridLayout) - Circular arena → Use
Environment.from_polygon()(ShapelyPolygonLayout) - T-maze or track → Use
Environment.from_graph()(GraphLayout) - Need uniform neighbor distances → Use
layout_type="hexagonal"(HexagonalLayout)
Available Engines¶
RegularGridLayout¶
Standard rectangular/cuboid grids - fastest and most common.
When to use: Default choice for rectangular environments Performance: ⭐⭐⭐⭐⭐ (fastest) Memory: ⭐⭐⭐⭐⭐ (most efficient) Setup complexity: ⭐⭐⭐⭐⭐ (easiest)
env = Environment.from_samples(positions, bin_size=2.5)
# or explicitly:
env = Environment.from_samples(positions, bin_size=2.5, layout_type="regular")
HexagonalLayout¶
Hexagonal tessellation with uniform neighbor distances.
When to use: When analyzing directional patterns or need isotropic representation Performance: ⭐⭐⭐⭐ (very fast) Memory: ⭐⭐⭐⭐ (efficient, ~15% more bins) Setup complexity: ⭐⭐⭐⭐ (easy)
Trade-off: All 6 neighbors equidistant, but requires 15% more bins for same coverage.
GraphLayout¶
1D linearized track for mazes and structured environments.
When to use: T-maze, plus maze, linear track experiments Performance: ⭐⭐ (slower lookups) Memory: ⭐⭐ (higher usage) Setup complexity: ⭐⭐ (requires graph definition)
import networkx as nx
G = nx.Graph()
G.add_node(0, pos=(0, 0))
G.add_node(1, pos=(50, 0))
G.add_edge(0, 1, edge_id=0, distance=50.0)
env = Environment.from_graph(G, edge_order=[(0, 1)], bin_size=2.0)
Key feature: Converts 2D positions to 1D linear coordinates with env.to_linear()
MaskedGridLayout¶
Grid with active/inactive regions - automatically used when infer_active_bins=True.
When to use: Sparse data, need to exclude walls/unvisited areas Performance: ⭐⭐⭐⭐ (fast) Memory: ⭐⭐⭐⭐⭐ (very efficient for sparse environments) Setup complexity: ⭐⭐⭐ (automatic with parameter tuning)
env = Environment.from_samples(
positions,
bin_size=2.5,
infer_active_bins=True,
dilate=True,
fill_holes=True
)
ShapelyPolygonLayout¶
Grid bounded by polygon for geometric arenas.
When to use: Circular, elliptical, or custom-shaped arenas Performance: ⭐⭐⭐ (moderate) Memory: ⭐⭐⭐⭐ (efficient) Setup complexity: ⭐⭐⭐ (need polygon definition)
from shapely.geometry import Point
import numpy as np
# Circular arena
theta = np.linspace(0, 2*np.pi, 100)
boundary = np.column_stack([50*np.cos(theta), 50*np.sin(theta)])
from shapely.geometry import Polygon
arena = Polygon(boundary)
env = Environment.from_polygon(arena, bin_size=2.5)
TriangularMeshLayout¶
Triangular tessellation - alternative grid structure.
When to use: Alternative to hexagonal, specific geometric needs Performance: ⭐⭐⭐ (moderate) Memory: ⭐⭐⭐⭐ (efficient) Setup complexity: ⭐⭐⭐⭐ (easy)
ImageMaskLayout¶
Binary image-based boundaries from video analysis.
When to use: Extracting arena from video frames Performance: ⭐⭐⭐ (moderate) Memory: ⭐⭐⭐ (moderate) Setup complexity: ⭐⭐ (requires image preprocessing)
env = Environment.from_image(
image_path="arena_mask.png",
bin_size=2.5,
dimension_ranges=[(0, 100), (0, 100)]
)
Performance Comparison¶
Benchmark: 100x100 cm arena, 2.5 cm bins, 10,000 position lookups
| Engine | Setup (ms) | Lookups/sec | Memory (MB) | Bins |
|---|---|---|---|---|
| Regular | 5 | 1,000,000 | 0.5 | 1,600 |
| Hexagonal | 8 | 900,000 | 0.6 | 1,840 |
| Masked | 15 | 800,000 | 0.3 | ~1,100 |
| Polygon | 50 | 600,000 | 0.6 | ~1,250 |
| Triangular | 10 | 700,000 | 0.6 | ~1,700 |
| Graph | 100 | 100,000 | 1.0 | 50 nodes |
Practical impact: For typical experiments (10k-100k positions), all engines complete in <1 second. Choose based on spatial structure needs, not performance.
Choosing Bin Size¶
Bin size has more impact than engine choice for most analyses.
Guidelines by Arena Size¶
Rat (100x100 cm arena): - Coarse: 5-10 cm → 100-400 bins - Standard: 2-5 cm → 400-2,500 bins - Fine: 1-2 cm → 2,500-10,000 bins
Mouse (40x40 cm arena): - Coarse: 2-4 cm → 100-400 bins - Standard: 1-2 cm → 400-1,600 bins - Fine: 0.5-1 cm → 1,600-6,400 bins
Rule of Thumb¶
Target 10-100 samples per bin for stable statistics:
# Estimate good bin_size
n_samples = len(positions)
arena_area = 100 * 100 # cm²
target_samples_per_bin = 50
target_bins = n_samples / target_samples_per_bin
bin_size = np.sqrt(arena_area / target_bins)
Decision Tree¶
What's your environment?
│
├─ Rectangular/square arena
│ ├─ Need direction-independent analysis? → Hexagonal
│ └─ Standard analysis → Regular (default)
│
├─ Circular/elliptical arena → Polygon
│
├─ Track/maze with branches → Graph
│
└─ Sparse data with walls/obstacles
├─ Have binary image → ImageMask
├─ Have polygon boundary → Polygon
└─ Just position data → Masked (infer_active_bins=True)
Common Patterns¶
Pattern 1: Let neurospatial choose¶
# Simplest - neurospatial picks appropriate engine
env = Environment.from_samples(positions, bin_size=2.5)
Pattern 2: Explicit control¶
# Choose specific engine when you know what you need
env = Environment.from_samples(
positions,
bin_size=2.5,
layout_type="hexagonal" # Override default
)
Pattern 3: Complex boundaries¶
# Use factory method for specific geometry
env = Environment.from_polygon(polygon, bin_size=2.5)
# or
env = Environment.from_image(image_path, bin_size=2.5, dimension_ranges=[(0,100), (0,100)])
Summary¶
For most users:
- Start with Environment.from_samples() and default settings
- Adjust bin_size based on your data density
- Only change engine if you have specific geometric or analytical needs
Key factors: 1. Environment shape (rectangular vs. circular vs. track) 2. Data density (affects bin size, not engine) 3. Analysis needs (directional uniformity, 1D linearization)
See Also¶
- Environments Guide: Using different factory methods
- Examples: Visual comparisons
- API Reference