canns 0.13.1__py3-none-any.whl → 0.14.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.
- canns/analyzer/data/__init__.py +5 -1
- canns/analyzer/data/asa/__init__.py +27 -12
- canns/analyzer/data/asa/cohospace.py +336 -10
- canns/analyzer/data/asa/config.py +3 -0
- canns/analyzer/data/asa/embedding.py +48 -45
- canns/analyzer/data/asa/path.py +104 -2
- canns/analyzer/data/asa/plotting.py +88 -19
- canns/analyzer/data/asa/tda.py +11 -4
- canns/analyzer/data/cell_classification/__init__.py +97 -0
- canns/analyzer/data/cell_classification/core/__init__.py +26 -0
- canns/analyzer/data/cell_classification/core/grid_cells.py +633 -0
- canns/analyzer/data/cell_classification/core/grid_modules_leiden.py +288 -0
- canns/analyzer/data/cell_classification/core/head_direction.py +347 -0
- canns/analyzer/data/cell_classification/core/spatial_analysis.py +431 -0
- canns/analyzer/data/cell_classification/io/__init__.py +5 -0
- canns/analyzer/data/cell_classification/io/matlab_loader.py +417 -0
- canns/analyzer/data/cell_classification/utils/__init__.py +39 -0
- canns/analyzer/data/cell_classification/utils/circular_stats.py +383 -0
- canns/analyzer/data/cell_classification/utils/correlation.py +318 -0
- canns/analyzer/data/cell_classification/utils/geometry.py +442 -0
- canns/analyzer/data/cell_classification/utils/image_processing.py +416 -0
- canns/analyzer/data/cell_classification/visualization/__init__.py +19 -0
- canns/analyzer/data/cell_classification/visualization/grid_plots.py +292 -0
- canns/analyzer/data/cell_classification/visualization/hd_plots.py +200 -0
- canns/analyzer/metrics/__init__.py +2 -1
- canns/analyzer/visualization/core/config.py +46 -4
- canns/data/__init__.py +6 -1
- canns/data/datasets.py +154 -1
- canns/data/loaders.py +37 -0
- canns/pipeline/__init__.py +13 -9
- canns/pipeline/__main__.py +6 -0
- canns/pipeline/asa/runner.py +105 -41
- canns/pipeline/asa_gui/__init__.py +68 -0
- canns/pipeline/asa_gui/__main__.py +6 -0
- canns/pipeline/asa_gui/analysis_modes/__init__.py +42 -0
- canns/pipeline/asa_gui/analysis_modes/base.py +39 -0
- canns/pipeline/asa_gui/analysis_modes/batch_mode.py +21 -0
- canns/pipeline/asa_gui/analysis_modes/cohomap_mode.py +56 -0
- canns/pipeline/asa_gui/analysis_modes/cohospace_mode.py +194 -0
- canns/pipeline/asa_gui/analysis_modes/decode_mode.py +52 -0
- canns/pipeline/asa_gui/analysis_modes/fr_mode.py +81 -0
- canns/pipeline/asa_gui/analysis_modes/frm_mode.py +92 -0
- canns/pipeline/asa_gui/analysis_modes/gridscore_mode.py +123 -0
- canns/pipeline/asa_gui/analysis_modes/pathcompare_mode.py +199 -0
- canns/pipeline/asa_gui/analysis_modes/tda_mode.py +112 -0
- canns/pipeline/asa_gui/app.py +29 -0
- canns/pipeline/asa_gui/controllers/__init__.py +6 -0
- canns/pipeline/asa_gui/controllers/analysis_controller.py +59 -0
- canns/pipeline/asa_gui/controllers/preprocess_controller.py +89 -0
- canns/pipeline/asa_gui/core/__init__.py +15 -0
- canns/pipeline/asa_gui/core/cache.py +14 -0
- canns/pipeline/asa_gui/core/runner.py +1936 -0
- canns/pipeline/asa_gui/core/state.py +324 -0
- canns/pipeline/asa_gui/core/worker.py +260 -0
- canns/pipeline/asa_gui/main_window.py +184 -0
- canns/pipeline/asa_gui/models/__init__.py +7 -0
- canns/pipeline/asa_gui/models/config.py +14 -0
- canns/pipeline/asa_gui/models/job.py +31 -0
- canns/pipeline/asa_gui/models/presets.py +21 -0
- canns/pipeline/asa_gui/resources/__init__.py +16 -0
- canns/pipeline/asa_gui/resources/dark.qss +167 -0
- canns/pipeline/asa_gui/resources/light.qss +163 -0
- canns/pipeline/asa_gui/resources/styles.qss +130 -0
- canns/pipeline/asa_gui/utils/__init__.py +1 -0
- canns/pipeline/asa_gui/utils/formatters.py +15 -0
- canns/pipeline/asa_gui/utils/io_adapters.py +40 -0
- canns/pipeline/asa_gui/utils/validators.py +41 -0
- canns/pipeline/asa_gui/views/__init__.py +1 -0
- canns/pipeline/asa_gui/views/help_content.py +171 -0
- canns/pipeline/asa_gui/views/pages/__init__.py +6 -0
- canns/pipeline/asa_gui/views/pages/analysis_page.py +565 -0
- canns/pipeline/asa_gui/views/pages/preprocess_page.py +492 -0
- canns/pipeline/asa_gui/views/panels/__init__.py +1 -0
- canns/pipeline/asa_gui/views/widgets/__init__.py +21 -0
- canns/pipeline/asa_gui/views/widgets/artifacts_tab.py +44 -0
- canns/pipeline/asa_gui/views/widgets/drop_zone.py +80 -0
- canns/pipeline/asa_gui/views/widgets/file_list.py +27 -0
- canns/pipeline/asa_gui/views/widgets/gridscore_tab.py +308 -0
- canns/pipeline/asa_gui/views/widgets/help_dialog.py +27 -0
- canns/pipeline/asa_gui/views/widgets/image_tab.py +50 -0
- canns/pipeline/asa_gui/views/widgets/image_viewer.py +97 -0
- canns/pipeline/asa_gui/views/widgets/log_box.py +16 -0
- canns/pipeline/asa_gui/views/widgets/pathcompare_tab.py +200 -0
- canns/pipeline/asa_gui/views/widgets/popup_combo.py +25 -0
- canns/pipeline/gallery/__init__.py +15 -5
- canns/pipeline/gallery/__main__.py +11 -0
- canns/pipeline/gallery/app.py +705 -0
- canns/pipeline/gallery/runner.py +790 -0
- canns/pipeline/gallery/state.py +51 -0
- canns/pipeline/gallery/styles.tcss +123 -0
- canns/pipeline/launcher.py +81 -0
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/METADATA +11 -1
- canns-0.14.0.dist-info/RECORD +163 -0
- canns-0.14.0.dist-info/entry_points.txt +5 -0
- canns/pipeline/_base.py +0 -50
- canns-0.13.1.dist-info/RECORD +0 -89
- canns-0.13.1.dist-info/entry_points.txt +0 -3
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/WHEEL +0 -0
- {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MATLAB Data Loader
|
|
3
|
+
|
|
4
|
+
Functions and classes for loading neuroscience data from MATLAB .mat files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy as np
|
|
13
|
+
import scipy.io
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class TuningCurve:
|
|
18
|
+
"""
|
|
19
|
+
Represents a tuning curve (e.g., head direction or spatial tuning).
|
|
20
|
+
|
|
21
|
+
Attributes
|
|
22
|
+
----------
|
|
23
|
+
bins : np.ndarray
|
|
24
|
+
Bin centers (e.g., angles for HD, positions for spatial)
|
|
25
|
+
rates : np.ndarray
|
|
26
|
+
Firing rates in each bin (Hz)
|
|
27
|
+
mvl : float, optional
|
|
28
|
+
Mean Vector Length (for directional tuning)
|
|
29
|
+
center_of_mass : float, optional
|
|
30
|
+
Preferred direction/position
|
|
31
|
+
peak_rate : float, optional
|
|
32
|
+
Maximum firing rate
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
bins: np.ndarray
|
|
36
|
+
rates: np.ndarray
|
|
37
|
+
mvl: float | None = None
|
|
38
|
+
center_of_mass: float | None = None
|
|
39
|
+
peak_rate: float | None = None
|
|
40
|
+
|
|
41
|
+
def __post_init__(self):
|
|
42
|
+
"""Compute derived properties."""
|
|
43
|
+
if self.peak_rate is None:
|
|
44
|
+
self.peak_rate = np.max(self.rates) if len(self.rates) > 0 else 0.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Unit:
|
|
49
|
+
"""
|
|
50
|
+
Represents a single neural unit (neuron).
|
|
51
|
+
|
|
52
|
+
Attributes
|
|
53
|
+
----------
|
|
54
|
+
unit_id : int or str
|
|
55
|
+
Unique identifier for this unit
|
|
56
|
+
spike_times : np.ndarray
|
|
57
|
+
Spike times in seconds
|
|
58
|
+
spike_indices : np.ndarray, optional
|
|
59
|
+
Indices into session time array
|
|
60
|
+
hd_tuning : TuningCurve, optional
|
|
61
|
+
Head direction tuning curve
|
|
62
|
+
pos_tuning : TuningCurve, optional
|
|
63
|
+
Spatial position tuning (2D rate map)
|
|
64
|
+
theta_tuning : TuningCurve, optional
|
|
65
|
+
Theta phase tuning
|
|
66
|
+
is_grid : bool, optional
|
|
67
|
+
Whether this is a grid cell
|
|
68
|
+
is_hd : bool, optional
|
|
69
|
+
Whether this is a head direction cell
|
|
70
|
+
gridness_score : float, optional
|
|
71
|
+
Grid cell score
|
|
72
|
+
metadata : dict
|
|
73
|
+
Additional metadata
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
unit_id: Any
|
|
77
|
+
spike_times: np.ndarray
|
|
78
|
+
spike_indices: np.ndarray | None = None
|
|
79
|
+
hd_tuning: TuningCurve | None = None
|
|
80
|
+
pos_tuning: TuningCurve | None = None
|
|
81
|
+
theta_tuning: TuningCurve | None = None
|
|
82
|
+
is_grid: bool | None = None
|
|
83
|
+
is_hd: bool | None = None
|
|
84
|
+
gridness_score: float | None = None
|
|
85
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MATFileLoader:
|
|
89
|
+
"""
|
|
90
|
+
Loader for MATLAB .mat files containing neuroscience data.
|
|
91
|
+
|
|
92
|
+
Handles both MATLAB v5/v7 files (via scipy.io) and v7.3+ files (via h5py).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def load(filepath: str) -> dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Load a .mat file, automatically detecting the version.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
filepath : str
|
|
103
|
+
Path to .mat file
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
data : dict
|
|
108
|
+
Dictionary containing the loaded data
|
|
109
|
+
|
|
110
|
+
Examples
|
|
111
|
+
--------
|
|
112
|
+
>>> loader = MATFileLoader()
|
|
113
|
+
>>> data = loader.load("example.mat")
|
|
114
|
+
>>> print(data.keys())
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
# Try scipy.io first (works for v5/v7)
|
|
118
|
+
data = scipy.io.loadmat(filepath, struct_as_record=False, squeeze_me=True)
|
|
119
|
+
# Remove MATLAB metadata
|
|
120
|
+
data = {k: v for k, v in data.items() if not k.startswith("__")}
|
|
121
|
+
return data
|
|
122
|
+
except NotImplementedError:
|
|
123
|
+
# Fall back to h5py for v7.3+
|
|
124
|
+
return MATFileLoader._load_h5py(filepath)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _load_h5py(filepath: str) -> dict[str, Any]:
|
|
128
|
+
"""Load MATLAB v7.3+ file using h5py."""
|
|
129
|
+
data = {}
|
|
130
|
+
with h5py.File(filepath, "r") as f:
|
|
131
|
+
for key in f.keys():
|
|
132
|
+
if not key.startswith("#"):
|
|
133
|
+
data[key] = MATFileLoader._h5py_to_numpy(f[key])
|
|
134
|
+
return data
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _h5py_to_numpy(h5_obj):
|
|
138
|
+
"""Recursively convert h5py objects to numpy arrays or dicts."""
|
|
139
|
+
if isinstance(h5_obj, h5py.Dataset):
|
|
140
|
+
return np.array(h5_obj)
|
|
141
|
+
elif isinstance(h5_obj, h5py.Group):
|
|
142
|
+
return {k: MATFileLoader._h5py_to_numpy(v) for k, v in h5_obj.items()}
|
|
143
|
+
else:
|
|
144
|
+
return h5_obj
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def load_unit_data(filepath: str) -> list[Unit]:
|
|
148
|
+
"""
|
|
149
|
+
Load unit data from a .mat file.
|
|
150
|
+
|
|
151
|
+
Expected structure (from unit_data_25953.mat):
|
|
152
|
+
- units: struct array with fields:
|
|
153
|
+
- id or spikeInds or spikeTimes
|
|
154
|
+
- rmf.hd, rmf.pos, rmf.theta (tuning structures)
|
|
155
|
+
- isGrid (boolean)
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
filepath : str
|
|
160
|
+
Path to unit data .mat file
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
units : list of Unit
|
|
165
|
+
List of Unit objects
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> loader = MATFileLoader()
|
|
170
|
+
>>> units = loader.load_unit_data("../results/unit_data_25953.mat")
|
|
171
|
+
>>> print(f"Loaded {len(units)} units")
|
|
172
|
+
>>> print(f"Grid cells: {sum(u.is_grid for u in units if u.is_grid)}")
|
|
173
|
+
"""
|
|
174
|
+
data = MATFileLoader.load(filepath)
|
|
175
|
+
|
|
176
|
+
# Find the units structure
|
|
177
|
+
if "units" in data:
|
|
178
|
+
units_struct = data["units"]
|
|
179
|
+
else:
|
|
180
|
+
raise ValueError("Could not find 'units' in .mat file")
|
|
181
|
+
|
|
182
|
+
# Handle both array of structs and single struct
|
|
183
|
+
if not isinstance(units_struct, np.ndarray):
|
|
184
|
+
units_struct = [units_struct]
|
|
185
|
+
|
|
186
|
+
units = []
|
|
187
|
+
for i, unit_data in enumerate(units_struct):
|
|
188
|
+
try:
|
|
189
|
+
unit = MATFileLoader._parse_unit_struct(unit_data, unit_id=i)
|
|
190
|
+
units.append(unit)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
warnings.warn(f"Failed to parse unit {i}: {e}", stacklevel=2)
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
return units
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def _parse_unit_struct(unit_struct, unit_id: Any = None) -> Unit:
|
|
199
|
+
"""Parse a MATLAB unit structure into a Unit object."""
|
|
200
|
+
# Extract spike times
|
|
201
|
+
spike_times = None
|
|
202
|
+
spike_indices = None
|
|
203
|
+
|
|
204
|
+
if hasattr(unit_struct, "spikeTimes"):
|
|
205
|
+
spike_times = np.asarray(unit_struct.spikeTimes).ravel()
|
|
206
|
+
elif hasattr(unit_struct, "spikeInds"):
|
|
207
|
+
spike_indices = np.asarray(unit_struct.spikeInds).ravel()
|
|
208
|
+
|
|
209
|
+
# Extract unit ID
|
|
210
|
+
if unit_id is None:
|
|
211
|
+
if hasattr(unit_struct, "id"):
|
|
212
|
+
unit_id = unit_struct.id
|
|
213
|
+
else:
|
|
214
|
+
unit_id = 0
|
|
215
|
+
|
|
216
|
+
# Extract tuning curves from rmf (rate map field) structure
|
|
217
|
+
hd_tuning = None
|
|
218
|
+
pos_tuning = None
|
|
219
|
+
theta_tuning = None
|
|
220
|
+
|
|
221
|
+
if hasattr(unit_struct, "rmf"):
|
|
222
|
+
rmf = unit_struct.rmf
|
|
223
|
+
|
|
224
|
+
# Head direction tuning
|
|
225
|
+
if hasattr(rmf, "hd"):
|
|
226
|
+
hd_tuning = MATFileLoader._parse_tuning_curve(rmf.hd)
|
|
227
|
+
|
|
228
|
+
# Position tuning
|
|
229
|
+
if hasattr(rmf, "pos"):
|
|
230
|
+
pos_tuning = MATFileLoader._parse_tuning_curve(rmf.pos)
|
|
231
|
+
|
|
232
|
+
# Theta phase tuning
|
|
233
|
+
if hasattr(rmf, "theta"):
|
|
234
|
+
theta_tuning = MATFileLoader._parse_tuning_curve(rmf.theta)
|
|
235
|
+
|
|
236
|
+
# Extract classification flags
|
|
237
|
+
is_grid = None
|
|
238
|
+
if hasattr(unit_struct, "isGrid"):
|
|
239
|
+
is_grid = bool(unit_struct.isGrid)
|
|
240
|
+
|
|
241
|
+
# Create Unit object
|
|
242
|
+
unit = Unit(
|
|
243
|
+
unit_id=unit_id,
|
|
244
|
+
spike_times=spike_times if spike_times is not None else np.array([]),
|
|
245
|
+
spike_indices=spike_indices,
|
|
246
|
+
hd_tuning=hd_tuning,
|
|
247
|
+
pos_tuning=pos_tuning,
|
|
248
|
+
theta_tuning=theta_tuning,
|
|
249
|
+
is_grid=is_grid,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return unit
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _parse_tuning_curve(tuning_struct) -> TuningCurve | None:
|
|
256
|
+
"""Parse a MATLAB tuning structure into a TuningCurve object."""
|
|
257
|
+
if tuning_struct is None:
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Extract bins and rates
|
|
262
|
+
bins = None
|
|
263
|
+
rates = None
|
|
264
|
+
mvl = None
|
|
265
|
+
center_of_mass = None
|
|
266
|
+
|
|
267
|
+
if hasattr(tuning_struct, "z"):
|
|
268
|
+
# 'z' typically contains the tuning curve values
|
|
269
|
+
rates = np.asarray(tuning_struct.z)
|
|
270
|
+
|
|
271
|
+
# For 1D tuning curves, create default bins
|
|
272
|
+
if rates.ndim == 1:
|
|
273
|
+
bins = np.linspace(-np.pi, np.pi, len(rates))
|
|
274
|
+
else:
|
|
275
|
+
# For 2D rate maps, bins might not be meaningful
|
|
276
|
+
bins = np.arange(rates.shape[0])
|
|
277
|
+
|
|
278
|
+
if hasattr(tuning_struct, "mvl"):
|
|
279
|
+
mvl = float(tuning_struct.mvl)
|
|
280
|
+
|
|
281
|
+
if hasattr(tuning_struct, "centerOfMass"):
|
|
282
|
+
center_of_mass = float(tuning_struct.centerOfMass)
|
|
283
|
+
|
|
284
|
+
if rates is not None:
|
|
285
|
+
return TuningCurve(bins=bins, rates=rates, mvl=mvl, center_of_mass=center_of_mass)
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
warnings.warn(f"Failed to parse tuning curve: {e}", stacklevel=2)
|
|
289
|
+
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def load_example_cells(filepath: str) -> list[Unit]:
|
|
294
|
+
"""
|
|
295
|
+
Load example cell data from exampleIdCells.mat format.
|
|
296
|
+
|
|
297
|
+
Expected structure:
|
|
298
|
+
- res: struct array with fields:
|
|
299
|
+
- recName, id
|
|
300
|
+
- hdTuning, posTuning
|
|
301
|
+
- tempAcorr (temporal autocorrelation)
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
filepath : str
|
|
306
|
+
Path to example cells .mat file
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
units : list of Unit
|
|
311
|
+
List of Unit objects
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
--------
|
|
315
|
+
>>> loader = MATFileLoader()
|
|
316
|
+
>>> cells = loader.load_example_cells("../results/exampleIdCells.mat")
|
|
317
|
+
>>> print(f"Loaded {len(cells)} example cells")
|
|
318
|
+
"""
|
|
319
|
+
data = MATFileLoader.load(filepath)
|
|
320
|
+
|
|
321
|
+
# Find the result structure
|
|
322
|
+
if "res" in data:
|
|
323
|
+
res_struct = data["res"]
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError("Could not find 'res' in .mat file")
|
|
326
|
+
|
|
327
|
+
if not isinstance(res_struct, np.ndarray):
|
|
328
|
+
res_struct = [res_struct]
|
|
329
|
+
|
|
330
|
+
units = []
|
|
331
|
+
for i, cell_data in enumerate(res_struct):
|
|
332
|
+
try:
|
|
333
|
+
# Extract basic info
|
|
334
|
+
unit_id = cell_data.id if hasattr(cell_data, "id") else i
|
|
335
|
+
rec_name = cell_data.recName if hasattr(cell_data, "recName") else ""
|
|
336
|
+
|
|
337
|
+
# Extract HD tuning
|
|
338
|
+
hd_tuning = None
|
|
339
|
+
if hasattr(cell_data, "hdTuning"):
|
|
340
|
+
hd_struct = cell_data.hdTuning
|
|
341
|
+
if hasattr(hd_struct, "z") and hasattr(hd_struct, "mvl"):
|
|
342
|
+
rates = np.asarray(hd_struct.z).ravel()
|
|
343
|
+
bins = np.linspace(-np.pi, np.pi, len(rates))
|
|
344
|
+
mvl = float(hd_struct.mvl)
|
|
345
|
+
hd_tuning = TuningCurve(bins=bins, rates=rates, mvl=mvl)
|
|
346
|
+
|
|
347
|
+
# Extract position tuning
|
|
348
|
+
pos_tuning = None
|
|
349
|
+
if hasattr(cell_data, "posTuning"):
|
|
350
|
+
pos_struct = cell_data.posTuning
|
|
351
|
+
if hasattr(pos_struct, "z"):
|
|
352
|
+
rates = np.asarray(pos_struct.z)
|
|
353
|
+
# For 2D rate maps, bins are spatial coordinates
|
|
354
|
+
bins = np.arange(rates.shape[0])
|
|
355
|
+
pos_tuning = TuningCurve(bins=bins, rates=rates)
|
|
356
|
+
|
|
357
|
+
# Create Unit
|
|
358
|
+
unit = Unit(
|
|
359
|
+
unit_id=unit_id,
|
|
360
|
+
spike_times=np.array([]), # Not provided in example format
|
|
361
|
+
hd_tuning=hd_tuning,
|
|
362
|
+
pos_tuning=pos_tuning,
|
|
363
|
+
metadata={"recording": rec_name},
|
|
364
|
+
)
|
|
365
|
+
units.append(unit)
|
|
366
|
+
|
|
367
|
+
except Exception as e:
|
|
368
|
+
warnings.warn(f"Failed to parse example cell {i}: {e}", stacklevel=2)
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
return units
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
if __name__ == "__main__":
|
|
375
|
+
# Simple tests (will only work if data files exist)
|
|
376
|
+
print("Testing MATLAB data loader...")
|
|
377
|
+
|
|
378
|
+
loader = MATFileLoader()
|
|
379
|
+
|
|
380
|
+
# Test 1: Try to load example cells
|
|
381
|
+
try:
|
|
382
|
+
print("\nTest 1 - Loading example cells:")
|
|
383
|
+
example_path = "../results/exampleIdCells.mat"
|
|
384
|
+
cells = loader.load_example_cells(example_path)
|
|
385
|
+
print(f" Loaded {len(cells)} example cells")
|
|
386
|
+
|
|
387
|
+
if len(cells) > 0:
|
|
388
|
+
cell = cells[0]
|
|
389
|
+
print(f" First cell ID: {cell.unit_id}")
|
|
390
|
+
if cell.hd_tuning:
|
|
391
|
+
print(f" HD tuning MVL: {cell.hd_tuning.mvl:.3f}")
|
|
392
|
+
print(f" HD tuning curve shape: {cell.hd_tuning.rates.shape}")
|
|
393
|
+
|
|
394
|
+
except FileNotFoundError:
|
|
395
|
+
print(" File not found (expected if running outside results directory)")
|
|
396
|
+
except Exception as e:
|
|
397
|
+
print(f" Error: {e}")
|
|
398
|
+
|
|
399
|
+
# Test 2: Try to load unit data
|
|
400
|
+
try:
|
|
401
|
+
print("\nTest 2 - Loading unit data:")
|
|
402
|
+
unit_path = "../results/unit_data_25953.mat"
|
|
403
|
+
units = loader.load_unit_data(unit_path)
|
|
404
|
+
print(f" Loaded {len(units)} units")
|
|
405
|
+
|
|
406
|
+
if len(units) > 0:
|
|
407
|
+
grid_cells = [u for u in units if u.is_grid]
|
|
408
|
+
print(f" Grid cells: {len(grid_cells)}")
|
|
409
|
+
if len(grid_cells) > 0:
|
|
410
|
+
print(f" First grid cell ID: {grid_cells[0].unit_id}")
|
|
411
|
+
|
|
412
|
+
except FileNotFoundError:
|
|
413
|
+
print(" File not found (expected if running outside results directory)")
|
|
414
|
+
except Exception as e:
|
|
415
|
+
print(f" Error: {e}")
|
|
416
|
+
|
|
417
|
+
print("\nData loader tests completed!")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Utility modules."""
|
|
2
|
+
|
|
3
|
+
from .circular_stats import circ_dist, circ_dist2, circ_mean, circ_r, circ_rtest, circ_std
|
|
4
|
+
from .correlation import autocorrelation_2d, normalized_xcorr2, pearson_correlation
|
|
5
|
+
from .geometry import cart2pol, fit_ellipse, pol2cart, polyarea, squared_distance, wrap_to_pi
|
|
6
|
+
from .image_processing import (
|
|
7
|
+
dilate_image,
|
|
8
|
+
find_contours_at_level,
|
|
9
|
+
find_regional_maxima,
|
|
10
|
+
gaussian_filter_2d,
|
|
11
|
+
label_connected_components,
|
|
12
|
+
regionprops,
|
|
13
|
+
rotate_image,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"circ_r",
|
|
18
|
+
"circ_mean",
|
|
19
|
+
"circ_std",
|
|
20
|
+
"circ_dist",
|
|
21
|
+
"circ_dist2",
|
|
22
|
+
"circ_rtest",
|
|
23
|
+
"pearson_correlation",
|
|
24
|
+
"normalized_xcorr2",
|
|
25
|
+
"autocorrelation_2d",
|
|
26
|
+
"fit_ellipse",
|
|
27
|
+
"squared_distance",
|
|
28
|
+
"polyarea",
|
|
29
|
+
"wrap_to_pi",
|
|
30
|
+
"cart2pol",
|
|
31
|
+
"pol2cart",
|
|
32
|
+
"rotate_image",
|
|
33
|
+
"find_regional_maxima",
|
|
34
|
+
"find_contours_at_level",
|
|
35
|
+
"gaussian_filter_2d",
|
|
36
|
+
"dilate_image",
|
|
37
|
+
"label_connected_components",
|
|
38
|
+
"regionprops",
|
|
39
|
+
]
|