Skip to content

Commit 28b6a2d

Browse files
committed
vis/eval: various fixes
1 parent dbc8337 commit 28b6a2d

10 files changed

Lines changed: 182 additions & 108 deletions

File tree

src/dartsort/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@
7676
get_logger,
7777
set_log_level,
7878
)
79-
from .util.motion import MotionInfo, get_motion_info, try_load_motion_info
79+
from .util.motion import (
80+
MotionInfo,
81+
detect_for_motion,
82+
get_motion_info,
83+
try_load_motion_info,
84+
)
8085
from .util.noise_util import EmbeddedNoise
8186
from .util.preprocess_util import preprocess
8287
from .util.py_util import databag, ensure_path

src/dartsort/clustering/agglomerate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ def _calc_coentropy(
11561156
u = cands[i]
11571157
q = resps[i]
11581158
log_q = np.log(q)
1159-
np.nan_to_num(log_q, copy=False, neginf=0.0)
1159+
np.nan_to_num(log_q, copy=False, neginf=0.0, posinf=np.inf)
11601160
dh = q * log_q
11611161

11621162
ui0 = u[0]

src/dartsort/clustering/density.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def get_smoothed_density_ratio(
188188
else:
189189
dens_ = dens[0]
190190
dens_ /= dens[1]
191-
np.nan_to_num(dens_, out=dens_) # type: ignore
191+
np.nan_to_num(dens_, out=dens_, posinf=np.inf, neginf=-np.inf) # type: ignore
192192
return dens_
193193

194194

@@ -407,7 +407,7 @@ def kdt_density(
407407
desc=f"KDTdens[{n_jobs}]",
408408
):
409409
density[i0:i1] = dens
410-
np.nan_to_num(density, copy=False)
410+
np.nan_to_num(density, posinf=np.inf, neginf=-np.inf, copy=False)
411411
return density
412412

413413

src/dartsort/clustering/merge.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,21 +358,47 @@ def get_deconv_resid_decrease_iter(
358358
)
359359

360360

361-
def combine_templates(template_data_a, template_data_b):
361+
def combine_templates(template_data_a: TemplateData, template_data_b: TemplateData):
362362
rgeom = template_data_a.registered_geom
363363
if rgeom is not None:
364+
assert template_data_b.registered_geom is not None
364365
if not np.array_equal(rgeom, template_data_b.registered_geom):
365366
raise ValueError(
366367
f"Template data had different registered geoms: "
367368
f"{template_data_a.registered_geom=} {template_data_b.registered_geom=}"
368369
)
369370

371+
ta = template_data_a.templates
372+
tb = template_data_b.templates
373+
assert ta.shape[2] == tb.shape[2]
374+
if ta.shape[1] > tb.shape[1]:
375+
i0 = (
376+
template_data_a.trough_offset_samples
377+
- template_data_b.trough_offset_samples
378+
)
379+
assert i0 >= 0
380+
i1 = i0 + template_data_b.spike_length_samples
381+
ta = ta[:, i0:i1]
382+
trough_offset_samples = template_data_b.trough_offset_samples
383+
sampling_frequency = template_data_b.sampling_frequency
384+
elif tb.shape[1] > ta.shape[1]:
385+
i0 = (
386+
template_data_b.trough_offset_samples
387+
- template_data_a.trough_offset_samples
388+
)
389+
assert i0 >= 0
390+
i1 = i0 + template_data_a.spike_length_samples
391+
tb = tb[:, i0:i1]
392+
trough_offset_samples = template_data_a.trough_offset_samples
393+
sampling_frequency = template_data_a.sampling_frequency
394+
else:
395+
trough_offset_samples = template_data_a.trough_offset_samples
396+
sampling_frequency = template_data_a.sampling_frequency
397+
370398
ids_a = template_data_a.unit_ids
371399
ids_b = template_data_b.unit_ids + ids_a.max() + 1
372400
unit_ids = np.concatenate((ids_a, ids_b))
373-
templates = np.concatenate(
374-
(template_data_a.templates, template_data_b.templates), axis=0
375-
)
401+
templates = np.concatenate((ta, tb), axis=0)
376402
spike_counts = np.concatenate(
377403
(template_data_a.spike_counts, template_data_b.spike_counts)
378404
)
@@ -389,8 +415,8 @@ def combine_templates(template_data_a, template_data_b):
389415
spike_counts=spike_counts,
390416
registered_geom=rgeom,
391417
spike_counts_by_channel=spike_counts_by_channel,
392-
trough_offset_samples=template_data_a.trough_offset_samples,
393-
sampling_frequency=template_data_a.sampling_frequency,
418+
trough_offset_samples=trough_offset_samples,
419+
sampling_frequency=sampling_frequency,
394420
)
395421

396422
cross_mask = np.logical_and(

src/dartsort/evaluate/analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def from_sorting(
136136
tpca = get_tpca(
137137
sorting, featurization_pipeline_pt=featurization_pipeline_pt
138138
)
139-
except ValueError:
139+
except (ValueError, FileNotFoundError):
140140
tpca = None
141141
else:
142142
tpca = None

src/dartsort/evaluate/comparison.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ def _unit_info_dataframe(self, force_distances=False, perf_only=False):
259259
except ValueError:
260260
pass
261261
if self.has_templates and (force_distances or self.compute_distances):
262-
dist = np.nan_to_num(self.template_distances, nan=np.inf).min(axis=1)
262+
dist = np.nan_to_num(
263+
self.template_distances, nan=np.inf, posinf=np.inf, neginf=-np.inf
264+
).min(axis=1)
263265
df["min_temp_dist"] = dist
264266
rec = []
265267
for uid in df.index:
@@ -334,7 +336,9 @@ def unit_matched_misalignment_rms(self):
334336
if udt.size:
335337
match_dt_rms[j] = np.sqrt(np.square(udt).mean())
336338
except ValueError as e:
337-
warnings.warn(f"ValueError in misalignment. SI matching bug. {e=}", stacklevel=2)
339+
warnings.warn(
340+
f"ValueError in misalignment. SI matching bug. {e=}", stacklevel=2
341+
)
338342
return match_dt_rms
339343

340344
def matched_misalignment(self, gt_unit_id):
@@ -477,7 +481,8 @@ def get_spikes_by_category(self, gt_unit, tested_unit=None):
477481
warnings.warn(
478482
f"Strange match sizes for {gt_unit=} {tested_unit=}: "
479483
f"{matched_gt_indices.shape=} {matched_tested_indices.shape=} "
480-
f"{matched_tested_mask.sum()=}", stacklevel=2
484+
f"{matched_tested_mask.sum()=}",
485+
stacklevel=2,
481486
)
482487
else:
483488
matched_tested_indices = np.zeros(shape=(0,), dtype=np.int64)
@@ -741,7 +746,8 @@ def __init__(
741746
self.other_analyses = other_analyses
742747
self.other_names = [oa.name for oa in other_analyses]
743748
self.other_templates = [
744-
(oa.name or f"Test{c}") for oa, c in zip(other_analyses, self.default_ids, strict=True)
749+
(oa.name or f"Test{c}")
750+
for oa, c in zip(other_analyses, self.default_ids, strict=True)
745751
]
746752
self.other_sortings = [oa.sorting for oa in other_analyses]
747753

src/dartsort/evaluate/hybridkit.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from copy import deepcopy
3+
from dataclasses import replace
34
from pathlib import Path
45
from typing import Any, Literal
56

@@ -14,6 +15,7 @@
1415
from ..util.job_util import ensure_computation_config
1516
from ..util.motion import MotionInfo, resample_motion
1617
from ..util.py_util import databag
18+
from .analysis import DARTsortAnalysis
1719
from .sim_template_tools import TemplateLibrarySimulator, get_template_simulator
1820
from .simlib import InjectSpikesPreprocessor
1921

@@ -28,6 +30,28 @@ class HybridDataset:
2830
motion: MotionInfo
2931
metadata: dict[str, Any]
3032

33+
def gt_analysis(
34+
self, trough_offset: int = 42, spike_length: int = 121
35+
) -> DARTsortAnalysis:
36+
if spike_length != self.gt_templates.spike_length_samples:
37+
i0 = self.gt_templates.trough_offset_samples - trough_offset
38+
assert i0 >= 0
39+
template_data = replace(
40+
self.gt_templates,
41+
templates=self.gt_templates.templates[:, i0 : i0 + spike_length],
42+
trough_offset_samples=trough_offset,
43+
)
44+
else:
45+
assert trough_offset == self.gt_templates.spike_length_samples
46+
template_data = self.gt_templates
47+
return DARTsortAnalysis.from_sorting(
48+
recording=self.recording,
49+
sorting=self.gt_sorting,
50+
template_data=template_data,
51+
motion=self.motion,
52+
name="GT",
53+
)
54+
3155

3256
def load_hybrid_recording(folder: str | Path) -> HybridDataset | None:
3357
folder = ensure_path(folder)

src/dartsort/templates/templates.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import gc
22
from dataclasses import replace
33
from pathlib import Path
4-
from typing import ClassVar
4+
from typing import ClassVar, Self
55

66
import numpy as np
77
import torch
@@ -83,19 +83,19 @@ def __post_init__(self):
8383
assert v.shape[0] == ntemp
8484

8585
@property
86-
def spike_length_samples(self):
86+
def spike_length_samples(self) -> int:
8787
return self.templates.shape[1]
8888

89-
def snrs_by_channel(self):
89+
def snrs_by_channel(self) -> np.ndarray:
9090
amp_vecs = np.nan_to_num(np.ptp(self.templates, axis=1), nan=-np.inf)
9191
if self.spike_counts_by_channel is not None:
9292
amp_vecs *= np.sqrt(self.spike_counts_by_channel)
9393
return amp_vecs
9494

95-
def main_channels(self):
95+
def main_channels(self) -> np.ndarray:
9696
return self.snrs_by_channel().argmax(axis=1)
9797

98-
def template_locations(self, mode="channel", radius=100.0):
98+
def template_locations(self, mode="channel", radius=100.0) -> np.ndarray:
9999
assert mode in ("localization", "channel")
100100

101101
if mode == "channel":
@@ -112,11 +112,11 @@ def template_locations(self, mode="channel", radius=100.0):
112112
rdepths = np.c_[rdepths["x"], rdepths["z_abs"]]
113113
return rdepths
114114

115-
def registered_depths_um(self, mode="channel", radius=100.0):
115+
def registered_depths_um(self, mode="channel", radius=100.0) -> np.ndarray:
116116
return self.template_locations(mode=mode, radius=radius)[:, 1]
117117

118118
@classmethod
119-
def from_npz(cls, npz_path):
119+
def from_npz(cls, npz_path) -> Self:
120120
with np.load(npz_path, allow_pickle=True) as data:
121121
data = dict(**data)
122122
data["whiten_strategy"] = str(data["whiten_strategy"])
@@ -182,7 +182,7 @@ def to_npz(self, npz_path):
182182
to_save[f"__prop_{k}"] = p
183183
np.savez(npz_path, **to_save) # type: ignore
184184

185-
def __getitem__(self, subset):
185+
def __getitem__(self, subset) -> Self:
186186
if not np.array_equal(self.unit_ids, np.arange(len(self.unit_ids))):
187187
subset_ixs = np.searchsorted(self.unit_ids, subset, side="right") - 1
188188
matched = self.unit_ids[subset_ixs] == subset
@@ -212,7 +212,7 @@ def __getitem__(self, subset):
212212
whiten_strategy=self.whiten_strategy,
213213
)
214214

215-
def coarsen(self):
215+
def coarsen(self) -> Self:
216216
"""Weighted average all templates that share a unit id."""
217217
# update templates
218218
unit_ids_unique, flat_ids = np.unique(self.unit_ids, return_inverse=True)
@@ -231,10 +231,10 @@ def coarsen(self):
231231
tsvd=self.tsvd,
232232
)
233233

234-
def unit_mask(self, unit_id):
234+
def unit_mask(self, unit_id) -> np.ndarray:
235235
return np.isin(self.unit_ids, unit_id)
236236

237-
def unit_templates(self, unit_id):
237+
def unit_templates(self, unit_id) -> np.ndarray:
238238
return self.templates[self.unit_mask(unit_id)]
239239

240240
def __init_subclass__(cls):
@@ -258,7 +258,7 @@ def from_config(
258258
featurization_basis=None,
259259
computation_cfg: ComputationConfig | None = None,
260260
show_progress: bool = True,
261-
) -> "TemplateData":
261+
) -> Self:
262262
# load if saved already and not overwriting
263263
if save_folder is not None:
264264
save_folder = Path(save_folder)
@@ -326,7 +326,7 @@ def _from_config(
326326
whitener: Whitener | None = None,
327327
tsvd=None,
328328
computation_cfg: ComputationConfig | None = None,
329-
) -> "TemplateData":
329+
) -> Self:
330330
raise NotImplementedError
331331

332332

@@ -353,4 +353,4 @@ def _try_reload_svd(
353353
logger.dartsortdebug(f"Reloading TSVD from {tnpz}")
354354
else:
355355
logger.dartsortdebug(f"No TSVD to reload in {tnpz}")
356-
return tsvd
356+
return tsvd # ty: ignore[invalid-return-type]

0 commit comments

Comments
 (0)