neurospatial 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neurospatial/__init__.py +35 -0
- neurospatial/_constants.py +75 -0
- neurospatial/_logging.py +202 -0
- neurospatial/alignment.py +565 -0
- neurospatial/calibration.py +68 -0
- neurospatial/composite.py +1152 -0
- neurospatial/distance.py +258 -0
- neurospatial/environment.py +2182 -0
- neurospatial/io.py +513 -0
- neurospatial/layout/__init__.py +26 -0
- neurospatial/layout/base.py +221 -0
- neurospatial/layout/engines/__init__.py +0 -0
- neurospatial/layout/engines/graph.py +402 -0
- neurospatial/layout/engines/hexagonal.py +323 -0
- neurospatial/layout/engines/image_mask.py +143 -0
- neurospatial/layout/engines/masked_grid.py +111 -0
- neurospatial/layout/engines/regular_grid.py +221 -0
- neurospatial/layout/engines/shapely_polygon.py +229 -0
- neurospatial/layout/engines/triangular_mesh.py +463 -0
- neurospatial/layout/factories.py +179 -0
- neurospatial/layout/helpers/__init__.py +0 -0
- neurospatial/layout/helpers/graph.py +510 -0
- neurospatial/layout/helpers/hexagonal.py +630 -0
- neurospatial/layout/helpers/regular_grid.py +612 -0
- neurospatial/layout/helpers/triangular_mesh.py +242 -0
- neurospatial/layout/helpers/utils.py +1036 -0
- neurospatial/layout/mixins.py +393 -0
- neurospatial/layout/validation.py +416 -0
- neurospatial/py.typed +0 -0
- neurospatial/regions/__init__.py +42 -0
- neurospatial/regions/core.py +508 -0
- neurospatial/regions/io.py +939 -0
- neurospatial/regions/ops.py +346 -0
- neurospatial/regions/plot.py +159 -0
- neurospatial/spatial.py +188 -0
- neurospatial/transforms.py +798 -0
- neurospatial-0.1.0.dist-info/METADATA +402 -0
- neurospatial-0.1.0.dist-info/RECORD +40 -0
- neurospatial-0.1.0.dist-info/WHEEL +4 -0
- neurospatial-0.1.0.dist-info/licenses/LICENSE +21 -0
neurospatial/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from neurospatial.alignment import (
|
|
4
|
+
get_2d_rotation_matrix,
|
|
5
|
+
map_probabilities_to_nearest_target_bin,
|
|
6
|
+
)
|
|
7
|
+
from neurospatial.distance import distance_field, pairwise_distances
|
|
8
|
+
from neurospatial.environment import Environment
|
|
9
|
+
from neurospatial.layout.factories import (
|
|
10
|
+
get_layout_parameters,
|
|
11
|
+
list_available_layouts,
|
|
12
|
+
)
|
|
13
|
+
from neurospatial.layout.validation import validate_environment
|
|
14
|
+
from neurospatial.spatial import map_points_to_bins
|
|
15
|
+
from neurospatial.transforms import (
|
|
16
|
+
apply_transform_to_environment,
|
|
17
|
+
estimate_transform,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Add NullHandler to prevent "No handler found" warnings if user doesn't configure logging
|
|
21
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Environment",
|
|
25
|
+
"apply_transform_to_environment",
|
|
26
|
+
"distance_field",
|
|
27
|
+
"estimate_transform",
|
|
28
|
+
"get_2d_rotation_matrix",
|
|
29
|
+
"get_layout_parameters",
|
|
30
|
+
"list_available_layouts",
|
|
31
|
+
"map_points_to_bins",
|
|
32
|
+
"map_probabilities_to_nearest_target_bin",
|
|
33
|
+
"pairwise_distances",
|
|
34
|
+
"validate_environment",
|
|
35
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Numerical constants and tolerances for neurospatial.
|
|
2
|
+
|
|
3
|
+
This module defines all numerical tolerances used throughout the package
|
|
4
|
+
for consistent behavior across geometric operations, equality checks, and
|
|
5
|
+
numerical stability.
|
|
6
|
+
|
|
7
|
+
All magic numbers should be imported from this module to ensure consistency
|
|
8
|
+
and make it easier to tune parameters globally.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
# Geometric tolerances
|
|
14
|
+
# --------------------
|
|
15
|
+
# Used for: point-in-polygon tests, boundary detection, polygon simplification
|
|
16
|
+
|
|
17
|
+
#: Geometric tolerance for distance comparisons (meters/cm)
|
|
18
|
+
#: Points within this distance are considered coincident
|
|
19
|
+
GEOMETRIC_TOLERANCE = 1e-9
|
|
20
|
+
|
|
21
|
+
#: Relative tolerance for np.isclose() comparisons
|
|
22
|
+
RELATIVE_TOLERANCE = 1e-9
|
|
23
|
+
|
|
24
|
+
#: Absolute tolerance for np.isclose() comparisons
|
|
25
|
+
ABSOLUTE_TOLERANCE = 1e-9
|
|
26
|
+
|
|
27
|
+
#: Point tolerance for region operations (polygon vertices, boundary detection)
|
|
28
|
+
#: Used to determine if two points should be considered the same location
|
|
29
|
+
POINT_TOLERANCE = 1e-8
|
|
30
|
+
|
|
31
|
+
# Numerical stability
|
|
32
|
+
# -------------------
|
|
33
|
+
# Used for: division by zero prevention, log(0) prevention
|
|
34
|
+
|
|
35
|
+
#: Small epsilon to prevent division by zero
|
|
36
|
+
EPSILON = 1e-10
|
|
37
|
+
|
|
38
|
+
#: Minimum denominator for inverse distance weighting
|
|
39
|
+
#: Prevents division by zero when query point equals a data point
|
|
40
|
+
IDW_MIN_DISTANCE = 1e-8
|
|
41
|
+
|
|
42
|
+
# Angle conventions
|
|
43
|
+
# -----------------
|
|
44
|
+
|
|
45
|
+
#: Angle range for 2D edge angles (radians)
|
|
46
|
+
ANGLE_2D_RANGE = (-np.pi, np.pi)
|
|
47
|
+
|
|
48
|
+
#: Tolerance for angle wraparound comparisons
|
|
49
|
+
ANGLE_TOLERANCE = 1e-8
|
|
50
|
+
|
|
51
|
+
# KDTree configuration
|
|
52
|
+
# --------------------
|
|
53
|
+
|
|
54
|
+
#: Default leaf size for scipy/sklearn KDTree queries
|
|
55
|
+
#: Smaller values = faster queries, slower construction
|
|
56
|
+
#: Larger values = slower queries, faster construction
|
|
57
|
+
#: 16 is a good balance for typical spatial queries
|
|
58
|
+
KDTREE_LEAF_SIZE = 16
|
|
59
|
+
|
|
60
|
+
#: Leaf size for composite environment KDTrees (larger for merged data)
|
|
61
|
+
#: Composite environments have more bins, so we use a larger leaf size
|
|
62
|
+
KDTREE_COMPOSITE_LEAF_SIZE = 40
|
|
63
|
+
|
|
64
|
+
# Distance thresholds
|
|
65
|
+
# -------------------
|
|
66
|
+
|
|
67
|
+
#: Maximum distance multiplier for bin containment checks
|
|
68
|
+
#: Points further than (bin_size * BIN_CONTAINMENT_FACTOR) from bin center
|
|
69
|
+
#: are considered outside the bin
|
|
70
|
+
#: Conservative value of 0.7 works well for rectangular grid cells
|
|
71
|
+
BIN_CONTAINMENT_FACTOR = 0.7
|
|
72
|
+
|
|
73
|
+
#: Maximum distance multiplier for nearest neighbor queries
|
|
74
|
+
#: Used to detect when a point is outside all bins
|
|
75
|
+
NEAREST_BIN_DISTANCE_THRESHOLD = 2.0
|
neurospatial/_logging.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Structured logging infrastructure for neurospatial.
|
|
2
|
+
|
|
3
|
+
This module provides a centralized logging system for the neurospatial package.
|
|
4
|
+
By default, logging is disabled (NullHandler), but users can enable it by
|
|
5
|
+
configuring the root logger.
|
|
6
|
+
|
|
7
|
+
Examples
|
|
8
|
+
--------
|
|
9
|
+
Enable logging to console::
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
|
|
15
|
+
from neurospatial import Environment
|
|
16
|
+
|
|
17
|
+
env = Environment.from_samples(data, bin_size=2.0)
|
|
18
|
+
# INFO:neurospatial:Building layout: regular_grid
|
|
19
|
+
# INFO:neurospatial:Environment created: 245 bins, 2 dims
|
|
20
|
+
|
|
21
|
+
Enable logging with more detail::
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(
|
|
26
|
+
level=logging.DEBUG,
|
|
27
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Filter to only neurospatial logs::
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
|
|
34
|
+
neurospatial_logger = logging.getLogger("neurospatial")
|
|
35
|
+
neurospatial_logger.setLevel(logging.INFO)
|
|
36
|
+
handler = logging.StreamHandler()
|
|
37
|
+
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
|
|
38
|
+
neurospatial_logger.addHandler(handler)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
# Create package logger
|
|
45
|
+
logger = logging.getLogger("neurospatial")
|
|
46
|
+
|
|
47
|
+
# Add NullHandler to prevent "No handler found" warnings
|
|
48
|
+
# Users must explicitly enable logging
|
|
49
|
+
logger.addHandler(logging.NullHandler())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def log_layout_build(layout_type: str, params: dict[str, Any]) -> None:
|
|
53
|
+
"""Log layout engine build operation.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
layout_type : str
|
|
58
|
+
Type of layout being built (e.g., 'regular_grid', 'hexagonal').
|
|
59
|
+
params : dict
|
|
60
|
+
Parameters used to build the layout.
|
|
61
|
+
"""
|
|
62
|
+
logger.info(
|
|
63
|
+
f"Building layout: {layout_type}",
|
|
64
|
+
extra={"layout_type": layout_type, "params": params},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def log_graph_validation(n_nodes: int, n_edges: int, n_dims: int) -> None:
|
|
69
|
+
"""Log graph validation operation.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
n_nodes : int
|
|
74
|
+
Number of nodes in the connectivity graph.
|
|
75
|
+
n_edges : int
|
|
76
|
+
Number of edges in the connectivity graph.
|
|
77
|
+
n_dims : int
|
|
78
|
+
Number of spatial dimensions.
|
|
79
|
+
"""
|
|
80
|
+
logger.debug(
|
|
81
|
+
f"Validating connectivity graph: {n_nodes} nodes, {n_edges} edges, {n_dims}D",
|
|
82
|
+
extra={"n_nodes": n_nodes, "n_edges": n_edges, "n_dims": n_dims},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def log_environment_created(
|
|
87
|
+
env_type: str, n_bins: int, n_dims: int, env_name: str | None = None
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Log environment creation.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
env_type : str
|
|
94
|
+
Type of environment (layout type tag).
|
|
95
|
+
n_bins : int
|
|
96
|
+
Number of bins in the environment.
|
|
97
|
+
n_dims : int
|
|
98
|
+
Number of spatial dimensions.
|
|
99
|
+
env_name : str or None
|
|
100
|
+
Optional name of the environment.
|
|
101
|
+
"""
|
|
102
|
+
name_str = f" '{env_name}'" if env_name else ""
|
|
103
|
+
logger.info(
|
|
104
|
+
f"Environment created{name_str}: {n_bins} bins, {n_dims}D",
|
|
105
|
+
extra={
|
|
106
|
+
"type": env_type,
|
|
107
|
+
"n_bins": n_bins,
|
|
108
|
+
"n_dims": n_dims,
|
|
109
|
+
"env_name": env_name,
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def log_composite_build(n_subenvs: int, total_bins: int, n_bridges: int) -> None:
|
|
115
|
+
"""Log composite environment construction.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
n_subenvs : int
|
|
120
|
+
Number of sub-environments merged.
|
|
121
|
+
total_bins : int
|
|
122
|
+
Total number of bins in composite.
|
|
123
|
+
n_bridges : int
|
|
124
|
+
Number of bridge edges created.
|
|
125
|
+
"""
|
|
126
|
+
logger.info(
|
|
127
|
+
f"CompositeEnvironment created: {n_subenvs} sub-envs, "
|
|
128
|
+
f"{total_bins} total bins, {n_bridges} bridges",
|
|
129
|
+
extra={
|
|
130
|
+
"n_subenvs": n_subenvs,
|
|
131
|
+
"total_bins": total_bins,
|
|
132
|
+
"n_bridges": n_bridges,
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def log_region_added(
|
|
138
|
+
region_name: str, region_kind: str, env_name: str | None = None
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Log region addition to environment.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
region_name : str
|
|
145
|
+
Name of the region added.
|
|
146
|
+
region_kind : str
|
|
147
|
+
Type of region ('point' or 'polygon').
|
|
148
|
+
env_name : str or None
|
|
149
|
+
Optional name of the environment.
|
|
150
|
+
"""
|
|
151
|
+
env_str = f" to '{env_name}'" if env_name else ""
|
|
152
|
+
logger.debug(
|
|
153
|
+
f"Region '{region_name}' ({region_kind}) added{env_str}",
|
|
154
|
+
extra={
|
|
155
|
+
"region_name": region_name,
|
|
156
|
+
"region_kind": region_kind,
|
|
157
|
+
"env_name": env_name,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def log_spatial_query(query_type: str, n_points: int, n_results: int) -> None:
|
|
163
|
+
"""Log spatial query operation.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
query_type : str
|
|
168
|
+
Type of query ('bin_at', 'contains', 'neighbors', etc.).
|
|
169
|
+
n_points : int
|
|
170
|
+
Number of points queried.
|
|
171
|
+
n_results : int
|
|
172
|
+
Number of results returned.
|
|
173
|
+
"""
|
|
174
|
+
logger.debug(
|
|
175
|
+
f"Spatial query '{query_type}': {n_points} points -> {n_results} results",
|
|
176
|
+
extra={"query_type": query_type, "n_points": n_points, "n_results": n_results},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def log_performance_warning(
|
|
181
|
+
operation: str, duration_ms: float, threshold_ms: float
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Log performance warning when operation exceeds threshold.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
operation : str
|
|
188
|
+
Name of the operation.
|
|
189
|
+
duration_ms : float
|
|
190
|
+
Actual duration in milliseconds.
|
|
191
|
+
threshold_ms : float
|
|
192
|
+
Expected threshold in milliseconds.
|
|
193
|
+
"""
|
|
194
|
+
logger.warning(
|
|
195
|
+
f"Performance warning: {operation} took {duration_ms:.1f}ms "
|
|
196
|
+
f"(expected <{threshold_ms:.1f}ms)",
|
|
197
|
+
extra={
|
|
198
|
+
"operation": operation,
|
|
199
|
+
"duration_ms": duration_ms,
|
|
200
|
+
"threshold_ms": threshold_ms,
|
|
201
|
+
},
|
|
202
|
+
)
|