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.
Files changed (40) hide show
  1. neurospatial/__init__.py +35 -0
  2. neurospatial/_constants.py +75 -0
  3. neurospatial/_logging.py +202 -0
  4. neurospatial/alignment.py +565 -0
  5. neurospatial/calibration.py +68 -0
  6. neurospatial/composite.py +1152 -0
  7. neurospatial/distance.py +258 -0
  8. neurospatial/environment.py +2182 -0
  9. neurospatial/io.py +513 -0
  10. neurospatial/layout/__init__.py +26 -0
  11. neurospatial/layout/base.py +221 -0
  12. neurospatial/layout/engines/__init__.py +0 -0
  13. neurospatial/layout/engines/graph.py +402 -0
  14. neurospatial/layout/engines/hexagonal.py +323 -0
  15. neurospatial/layout/engines/image_mask.py +143 -0
  16. neurospatial/layout/engines/masked_grid.py +111 -0
  17. neurospatial/layout/engines/regular_grid.py +221 -0
  18. neurospatial/layout/engines/shapely_polygon.py +229 -0
  19. neurospatial/layout/engines/triangular_mesh.py +463 -0
  20. neurospatial/layout/factories.py +179 -0
  21. neurospatial/layout/helpers/__init__.py +0 -0
  22. neurospatial/layout/helpers/graph.py +510 -0
  23. neurospatial/layout/helpers/hexagonal.py +630 -0
  24. neurospatial/layout/helpers/regular_grid.py +612 -0
  25. neurospatial/layout/helpers/triangular_mesh.py +242 -0
  26. neurospatial/layout/helpers/utils.py +1036 -0
  27. neurospatial/layout/mixins.py +393 -0
  28. neurospatial/layout/validation.py +416 -0
  29. neurospatial/py.typed +0 -0
  30. neurospatial/regions/__init__.py +42 -0
  31. neurospatial/regions/core.py +508 -0
  32. neurospatial/regions/io.py +939 -0
  33. neurospatial/regions/ops.py +346 -0
  34. neurospatial/regions/plot.py +159 -0
  35. neurospatial/spatial.py +188 -0
  36. neurospatial/transforms.py +798 -0
  37. neurospatial-0.1.0.dist-info/METADATA +402 -0
  38. neurospatial-0.1.0.dist-info/RECORD +40 -0
  39. neurospatial-0.1.0.dist-info/WHEEL +4 -0
  40. neurospatial-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -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
@@ -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
+ )