Skip to content

Commit 17255cf

Browse files
committed
restrict npsl metadata discovery to http transfer
1 parent de428dd commit 17255cf

8 files changed

Lines changed: 70 additions & 248 deletions

File tree

‎.pre-commit-config.yaml‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ repos:
99
rev: v0.19.0
1010
hooks:
1111
- id: yamlfmt
12-
- repo: https://github.com/psf/black
13-
rev: 25.1.0
12+
- repo: https://github.com/astral-sh/ruff-pre-commit
13+
# Ruff version.
14+
rev: v0.15.14
1415
hooks:
15-
- id: black
16-
types_or: [python, jupyter]
16+
# Run the linter.
17+
- id: ruff-check
18+
# Run the formatter.
19+
- id: ruff-format
1720
- repo: https://github.com/kynan/nbstripout
1821
rev: 0.9.1
1922
hooks:

‎bil/api/__init__.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
from __future__ import annotations
66

7-
from typing import Any
8-
from . import abstracts
9-
from . import formatS
10-
from . import formatNPSL
7+
from . import abstracts, formatNPSL, formatS
118
from .utils import fetch
129

1310
IMPLEMENTATIONS: dict[str, type[formatS.Study] | type[formatNPSL.Study]] = {

‎bil/api/abstracts.py‎

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import functools
88
from abc import ABC, abstractmethod
99
from functools import cached_property
10-
from typing import Any, Callable, Iterable, TypeVar
1110
from pathlib import Path
11+
from typing import Any, Callable, Iterable, TypeVar
1212

13+
import h5py
1314
import numpy as np
1415
import pandas as pd
15-
import h5py
1616

1717
from .utils import fetch
18+
from .utils.subject import Subject
1819

1920
SDR_VERSIONED_URL: str = (
2021
"https://stacks.stanford.edu/v{version}/file/zz618yg1930/version/{version}/data"
@@ -219,13 +220,19 @@ def around(self, time_ms: int, t_before: int, t_after: int) -> "Span":
219220
Returns:
220221
New aligned span.
221222
"""
223+
if t_before == 0 and t_after == 0:
224+
raise ValueError("Must specify non-zero t_before or t_after!")
225+
if t_before < 0:
226+
raise ValueError("Cannot have negative t_before")
227+
if t_after < 0:
228+
raise ValueError("Cannot have negative t_after")
222229
start = time_ms - t_before
223-
end = time_ms + t_after + 1
230+
end = time_ms + t_after
224231

225232
span = self
226233
if self.start + end > self.stop:
227234
span = span.extend(t_after=self.start + end - self.stop)
228-
end = -1
235+
end = -1 # go to end of span
229236
if start < 0:
230237
span = span.extend(t_before=abs(start))
231238
if end != -1:
@@ -280,9 +287,7 @@ def spanset_cls(self) -> type["SpanSet"]:
280287
def spanarray_cls(self) -> type["SpanSet"]:
281288
"""SpanArray instantiation class."""
282289

283-
def __init__(
284-
self, spans: list[Span], **kwargs: Any # pylint: disable=unused-argument
285-
) -> None:
290+
def __init__(self, spans: list[Span], **kwargs: Any) -> None:
286291
"""Initialize SpanSet.
287292
288293
Args:
@@ -412,7 +417,12 @@ def around(
412417
target_t = list(time_ms)[index]
413418
else:
414419
target_t = time_ms
415-
span_arr.append(span.around(int(target_t), t_before, t_after))
420+
try:
421+
# Check to make sure we are number-like
422+
target_t = int(str(target_t))
423+
except Exception as e:
424+
raise ValueError from e
425+
span_arr.append(span.around(target_t, t_before, t_after))
416426
return self._make_spanarray(span_arr)
417427

418428
def map(self, func: Callable, *args: Any, **kwargs: Any) -> list[Any]:
@@ -449,8 +459,7 @@ class StudyMixin(ABC):
449459
quiet: Verbosity flag.
450460
"""
451461

452-
# For linting purposes!
453-
# span_cls: type[Span]
462+
head: str
454463

455464
def __init__(
456465
self, study_id: str, download_dir: str, quiet: bool = False, **kwargs: Any
@@ -505,7 +514,7 @@ def __getitem__(self, key: Any) -> Any:
505514
"""
506515
if self.df is None:
507516
return self.by_time(key)
508-
return super().__getitem__(key) # type: ignore[misc]
517+
return super().__getitem__(key) # ty: ignore[unresolved-attribute]
509518

510519
@cached_property
511520
@abstractmethod
@@ -573,6 +582,7 @@ class HeadH5Study(StudyMixin):
573582
"""Base class for studies with remote HDF5 head files."""
574583

575584
data_paths: dict[str, list[str]] = {}
585+
subject: Subject
576586

577587
def __init__(
578588
self,
@@ -664,7 +674,7 @@ def _make_span(self, row: pd.Series) -> Span:
664674
"""
665675
start = int(row["ms_start"])
666676
stop = int(row["ms_end"])
667-
return self.span_cls(
677+
return self.span_cls( # ty: ignore[unresolved-attribute]
668678
start=start, stop=stop, study_id=self.study_id, study=self, metadata=row
669679
)
670680

@@ -699,17 +709,21 @@ def __init__(
699709
"""
700710
self.deposition_version = deposition_version
701711
super().__init__(
702-
study_id=study_id,
703-
download_dir=f"{download_dir}/{study_id}",
704-
quiet=quiet,
705-
spans=(),
712+
study_id=study_id, # ty: ignore[unknown-argument]
713+
download_dir=f"{download_dir}/{study_id}", # ty: ignore[unknown-argument]
714+
quiet=quiet, # ty: ignore[unknown-argument]
715+
spans=(), # ty: ignore[unknown-argument]
706716
**kwargs,
707-
) # type: ignore[call-arg]
717+
)
708718

709719
@cached_property
710720
def fetcher(self) -> fetch.FetcherHTTPS:
711721
"""HTTPS fetch manager."""
712-
return fetch.FetcherHTTPS(self.url, self.download_dir, quiet=self.quiet)
722+
return fetch.FetcherHTTPS(
723+
self.url, # ty: ignore[unresolved-attribute]
724+
self.download_dir, # ty: ignore[unresolved-attribute]
725+
quiet=self.quiet, # ty: ignore[unresolved-attribute]
726+
)
713727

714728
@property
715729
def _default_url(self) -> str:
@@ -735,7 +749,7 @@ def __init__(self, spans: list[Span], *args: Any, **kwargs: Any) -> None:
735749
raise ValueError("SpanArray requires spans of the same length")
736750

737751
# init
738-
super().__init__(spans=spans_list, *args, **kwargs) # type: ignore[call-arg]
752+
super().__init__(spans=spans_list, *args, **kwargs) # ty: ignore[unknown-argument]
739753

740754
def _wrap(self, func: str, *args: Any, **kwargs: Any) -> np.ndarray:
741755
"""Wrap results into numpy array.
@@ -748,7 +762,7 @@ def _wrap(self, func: str, *args: Any, **kwargs: Any) -> np.ndarray:
748762
Returns:
749763
Numpy array of results.
750764
"""
751-
return np.array(super()._wrap(func, *args, **kwargs)) # type: ignore[misc]
765+
return np.array(super()._wrap(func, *args, **kwargs)) # ty: ignore[unresolved-attribute]
752766

753767

754768
class DataCatalog:

‎bil/api/formatNPSL.py‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@
66
"""
77

88
from __future__ import annotations
9+
910
from functools import cached_property
10-
from typing import Any, TYPE_CHECKING
11-
import numpy as np
11+
from typing import TYPE_CHECKING, Any
12+
1213
import h5py
14+
import numpy as np
1315
import pandas as pd
1416

1517
from . import abstracts
1618
from .utils import subject
17-
from .utils import b_helpers
1819

1920
if TYPE_CHECKING:
20-
from .abstracts import StudyMixin
21+
from .abstracts import HeadH5Study
2122

2223
SUBJECTS: dict[str, subject.Subject] = {
2324
"L": subject.Subject("L", [subject.UTAH], ["m1"]),
@@ -51,7 +52,7 @@ def raster(self, region: str | None = None) -> np.ndarray:
5152
class Span(DataCatalog, abstracts.Span):
5253
"""Span implementation for format NPSL."""
5354

54-
study: StudyMixin
55+
study: HeadH5Study
5556

5657
def _get(self, h5_dataset: h5py.Dataset) -> np.ndarray:
5758
"""Slice and return data from an HDF5 dataset.
@@ -85,9 +86,9 @@ def _get_regions(self, prefix: str, region: str | None = None) -> np.ndarray:
8586
else:
8687
suffixes = [""] # Default fallback
8788
elif region.lower() in ["m1", "pmd"]:
88-
assert (
89-
region in self.study.subject.regions
90-
), f"subject {self.study.subject.name} does not have region {region}!"
89+
assert region in self.study.subject.regions, (
90+
f"subject {self.study.subject.name} does not have region {region}!"
91+
)
9192
# Find index of region to determine suffix
9293
idx = self.study.subject.regions.index(region.lower())
9394
suffixes = ["" if idx == 0 else str(idx + 1)]
@@ -218,15 +219,12 @@ def _init_meta(self) -> None:
218219
"""Initialize metadata by parsing the HDF5 head file."""
219220
if self._df is not None:
220221
return
221-
if self.fetcher.check_file_exists("df/trial.csv"):
222-
path = self.fetcher.get_file("df/trial.csv")
223-
dataframe = pd.read_csv(path)
224-
dataframe = dataframe.sort_values(by="number").reset_index(drop=True)
225-
self.df = dataframe
226-
return
227-
df = b_helpers.df_from_h5(self.head, self.study_id)
228-
df = df.sort_values(by="number").reset_index(drop=True)
229-
self.df = df
222+
223+
path = self.fetcher.get_file("trial.csv")
224+
dataframe = pd.read_csv(path)
225+
dataframe = dataframe.sort_values(by="number").reset_index(drop=True)
226+
self.df = dataframe
227+
return
230228

231229

232230
class Study(abstracts.PublicMixin, StudyBase, SpanSet):

‎bil/api/formatS.py‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@
33
"""
44

55
from __future__ import annotations
6-
from typing import Any, TYPE_CHECKING
6+
77
import sqlite3
88
import warnings
99
from functools import cached_property
1010
from pathlib import Path
11+
from typing import TYPE_CHECKING, Any
1112

12-
import yaml
13+
import h5py
1314
import numpy as np
1415
import pandas as pd
15-
import h5py
16+
import yaml
1617

1718
from . import abstracts
1819
from .abstracts import needs_data
1920
from .utils import subject
2021

2122
if TYPE_CHECKING:
22-
from .abstracts import StudyMixin
23+
from .abstracts import HeadH5Study
2324

2425
# Channel mapping for Utah arrays, accounting for spatial orientation
2526
U_M1_MAP = subject.UTAH_MAP.copy()[::-1, ::-1].T
@@ -246,7 +247,7 @@ class Span(DataCatalog, abstracts.Span):
246247
neural data and behavioral signals.
247248
"""
248249

249-
study: StudyMixin
250+
study: HeadH5Study
250251

251252
def _stream(self, region: str | None = None) -> np.ndarray:
252253
"""Identify relevant neural data streams.
@@ -746,10 +747,11 @@ def _init_meta(self) -> None:
746747
"""Fetch and load trial metadata."""
747748
if self._df is not None:
748749
return
749-
assert self.fetcher.check_file_exists(
750-
"df/trial.df.xz"
751-
), f"trial information not available; does the dataset {self.study_id} exist?"
750+
assert self.fetcher.check_file_exists("df/trial.df.xz"), (
751+
f"trial information not available; does the dataset {self.study_id} exist?"
752+
)
752753
path = self.fetcher.get_file("df/trial.df.xz")
753754
dataframe = pd.read_pickle(path)
755+
dataframe = pd.DataFrame(dataframe)
754756
dataframe = dataframe.sort_values(by="number").reset_index(drop=True)
755757
self.df = dataframe

0 commit comments

Comments
 (0)