Dimensionality Support in neurospatial¶
Last Updated: 2025-12-05
Summary¶
neurospatial supports 1D, 2D, and 3D spatial environments. Full 3D support including affine transformations.
Supported Dimensionalities¶
1D Environments (Linearized Tracks)¶
Use case: Linear tracks, mazes with defined paths, sequential spatial trajectories
How to create:
import networkx as nx
from neurospatial import Environment
# From a track graph with an explicit linearization order
graph = nx.Graph()
graph.add_node(0, pos=(0.0, 0.0))
graph.add_node(1, pos=(50.0, 0.0))
graph.add_edge(0, 1, edge_id=0, distance=50.0)
env = Environment.from_graph(
graph=graph,
edge_order=[(0, 1)],
edge_spacing=0.0,
bin_size=2.0,
)
Unique features:
- env.to_linear(nd_position) - Convert N-D coordinates to linear position
- env.linear_to_nd(linear_position) - Convert linear position to N-D coordinates
- env.plot_1d() - 1D visualization
- env.is_linearized_track == True
Typical applications: - Linear tracks (e.g., T-maze arms, figure-8 tracks) - Virtual reality corridors - Sequential navigation analysis
2D Environments (Grid-Based Layouts)¶
Use case: Open field navigation, arenas, complex 2D environments
How to create:
from neurospatial import Environment
import numpy as np
# From 2D position samples
data_2d = np.random.randn(1000, 2) * 50 # shape (n_samples, 2)
env = Environment.from_samples(data_2d, bin_size=2.0)
# From polygon boundary
from shapely.geometry import Polygon
polygon = Polygon([(0, 0), (100, 0), (100, 100), (0, 100)])
env = Environment.from_polygon(polygon, bin_size=2.0)
# From a pixel / image mask (boolean array, not a file path)
import imageio.v3 as iio
image_mask = iio.imread("arena_mask.png").astype(bool)
env = Environment.from_pixel_mask(image_mask, pixel_size=0.1) # cm/pixel
Layout engines available:
- RegularGridLayout - Standard rectangular grids
- HexagonalLayout - Hexagonal tessellations
- TriangularMeshLayout - Triangular tessellations
- MaskedGridLayout - Grids with arbitrary active/inactive regions
- ImageMaskLayout - Binary image-based layouts
- ShapelyPolygonLayout - Polygon-bounded grids
Full feature support: - ✅ 2D affine transforms (rotation, scaling, translation) - ✅ Polygon regions - ✅ Image mask layouts - ✅ All spatial queries (bin_at, contains, neighbors, path_between) - ✅ Alignment and probability mapping between environments - ✅ Full visualization suite
Typical applications: - Open field experiments - Water maze navigation - Complex 2D arenas with barriers - Multi-room environments
3D Support Status¶
What Works in 3D¶
✅ Basic spatial binning:
data_3d = np.random.randn(1000, 3) * 50 # shape (n_samples, 3)
env_3d = Environment.from_samples(data_3d, bin_size=2.0)
# These all work:
bins = env_3d.bin_at(points_3d)
mask = env_3d.contains(points_3d)
neighbors = env_3d.neighbors(bin_idx)
path = env_3d.path_between(source_bin, target_bin)
dist = env_3d.distance_between(points_3d[0], points_3d[1])
bin_dist = float(env_3d.distance_to([target_bin])[source_bin])
✅ Connectivity graphs - Full 3D graph support
✅ Distance calculations - Euclidean distances in 3D
✅ Path finding - Shortest paths through 3D bin connectivity
✅ Composite environments - Merging 3D environments
✅ 3D affine transforms
from neurospatial.ops import from_rotation_matrix, scale_3d, translate_3d
from scipy.spatial.transform import Rotation
# 3D translation
transform = translate_3d(10, 20, 30)
# 3D scaling
S = scale_3d(1.5, 1.5, 1.5)
# 3D rotation using scipy
R = Rotation.from_euler('z', 45, degrees=True).as_matrix()
transform = from_rotation_matrix(R, translation=[10, 20, 30])
# Apply to 3D environment
from neurospatial.ops import apply_transform_to_environment
env_transformed = apply_transform_to_environment(env_3d, transform)
✅ Transform estimation - Estimate rigid, similarity, or affine transforms from point pairs
from neurospatial.ops import estimate_transform
# Works for 2D or 3D point correspondences
transform = estimate_transform(src_points_3d, dst_points_3d, kind="rigid")
What Doesn't Work in 3D¶
❌ Polygon regions
# Polygon regions are 2D only
polygon_coords = np.array([[0, 0], [10, 0], [10, 10], [0, 10]])
env_3d.regions.add("goal", polygon=polygon_coords) # Will raise error
bins = env_3d.bins_in_region("goal") # ValueError: Polygon regions only supported for 2D
❌ Image mask layouts - Binary images are inherently 2D
❌ Hexagonal and triangular layouts - Currently 2D-only tessellations
❌ 3D-specific visualization - No 3D plotting methods yet
Feature Compatibility Matrix¶
| Feature | 1D | 2D | 3D | Notes |
|---|---|---|---|---|
| Core Functionality | ||||
| Spatial binning | ✅ | ✅ | ✅ | |
| Connectivity graphs | ✅ | ✅ | ✅ | |
| Distance calculations | ✅ | ✅ | ✅ | |
| Path finding | ✅ | ✅ | ✅ | |
| Composite environments | ✅ | ✅ | ✅ | |
| Regions | ||||
| Point regions | ✅ | ✅ | ✅ | |
| Polygon regions | ❌ | ✅ | ❌ | 2D only |
| Transforms | ||||
| AffineND | ❌ | ✅ | ✅ | N-D transforms |
| Affine2D | ❌ | ✅ | ❌ | 2D only (legacy) |
| Rotation matrices | ❌ | ✅ | ✅ | scipy integration |
| Scaling | ❌ | ✅ | ✅ | scale_3d() |
| Translation | ❌ | ✅ | ✅ | translate_3d() |
| Transform estimation | ❌ | ✅ | ✅ | Auto-detect dims |
| Layout Engines | ||||
| RegularGridLayout | ❌ | ✅ | ✅ | |
| HexagonalLayout | ❌ | ✅ | ❌ | 2D tessellation |
| TriangularMeshLayout | ❌ | ✅ | ❌ | 2D tessellation |
| GraphLayout | ✅ | ❌ | ❌ | 1D linearization |
| MaskedGridLayout | ❌ | ✅ | ✅ | |
| ImageMaskLayout | ❌ | ✅ | ❌ | Images are 2D |
| ShapelyPolygonLayout | ❌ | ✅ | ❌ | Polygons are 2D |
| Visualization | ||||
| plot() | ✅ | ✅ | ❌ | |
| plot_1d() | ✅ | ❌ | ❌ | 1D only |
| Alignment | ||||
| Probability mapping | ✅ | ✅ | ✅ | N-D rotation matrices |
| Transform estimation | ❌ | ✅ | ✅ | Procrustes analysis |
Best Practices¶
Choosing Dimensionality¶
Use 1D when: - Your spatial data follows a defined path or track - You need linearized position coordinates - Working with sequential navigation (T-maze, linear track)
Use 2D when: - Your spatial data is in an open 2D arena - You need polygon-based regions - Working with standard behavioral experiments (open field, water maze)
Use 3D when: - Your spatial data is truly 3D (flight, swimming with depth, climbing) - You only need basic binning and connectivity - You don't need transforms or polygon regions
Validation¶
Always verify dimensionality before assuming features work:
env = Environment.from_samples(data, bin_size=2.0)
# Check dimensionality
print(f"Dimensions: {env.n_dims}") # 1, 2, or 3
# Check if 1D (linearized)
if env.is_linearized_track:
linear_pos = env.to_linear(nd_position)
else:
bin_idx = env.bin_at(nd_position)
# Check before using 2D-only features
if env.n_dims == 2:
# Safe to use polygon regions, 2D transforms, etc.
env.regions.add("goal", polygon=polygon_coords)
else:
# Use point regions instead
env.regions.add("goal", point=[10, 20, 30])
3D Support Status¶
Implemented:
- ✅ 3D affine transformations (AffineND, Affine3D)
- ✅ 3D rotation matrices (scipy integration)
- ✅ Transform estimation from point pairs (Procrustes analysis)
- ✅ Environment transformation with dimension validation
- ✅ N-D probability mapping with rotation support
Planned for future releases:
- 3D polyhedron regions (using trimesh or similar)
- 3D visualization with matplotlib 3D or plotly
- 3D-specific layout engines (volumetric tessellations)
- Additional 3D examples and tutorials
Track progress: See GitHub Issues for 3D support roadmap
Getting Help¶
- For 1D/2D questions: See main documentation and examples
- For 3D questions: Check this guide for current limitations
- Report issues: https://github.com/user/neurospatial/issues
- Discussions: https://github.com/user/neurospatial/discussions