Skip to content

Commit f8857ad

Browse files
authored
Merge pull request #1156 from AllenInstitute/rc/1.2.0
rc/1.2.0
2 parents 6f73cc1 + 2e1c680 commit f8857ad

File tree

58 files changed

+5278
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+5278
-816
lines changed

allensdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
#
3636
import logging
3737

38-
__version__ = '1.1.1'
38+
__version__ = '1.2.0'
3939

4040
try:
4141
from logging import NullHandler

allensdk/api/cache.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,51 @@
4242
import pandas.io.json as pj
4343

4444
import functools
45-
from functools import wraps
45+
from functools import wraps, _make_key
4646
import os
4747
import logging
4848
import csv
4949

5050

5151
def memoize(f):
52-
memodict = dict()
52+
"""
53+
Creates an unbound cache of function calls and results. Note that arguments
54+
of different types are not cached separately (so f(3.0) and f(3) are not
55+
treated as distinct calls)
56+
57+
Arguments to the cached function must be hashable.
58+
59+
View the cache size with f.cache_size().
60+
Clear the cache with f.cache_clear().
61+
Access the underlying function with f.__wrapped__.
62+
"""
63+
cache = {}
64+
sentinel = object() # unique object for cache misses
65+
make_key = _make_key # efficient key building from function args
66+
cache_get = cache.get
67+
cache_len = cache.__len__
68+
69+
@wraps(f)
70+
def wrapper(*args, **kwargs):
71+
key = make_key(args, kwargs, typed=False) # Don't consider 3.0 and 3 different
72+
result = cache_get(key, sentinel)
73+
if result is not sentinel:
74+
return result
75+
result = f(*args, **kwargs)
76+
cache[key] = result
77+
return result
78+
79+
def cache_clear():
80+
cache.clear()
5381

54-
@wraps(f)
55-
def wrapper(*args, **kwargs):
56-
key = (args, tuple(kwargs.items()))
82+
def cache_size():
83+
return cache_len()
5784

58-
if key not in memodict:
59-
memodict[key] = f(*args, **kwargs)
85+
wrapper.cache_clear = cache_clear
86+
wrapper.cache_size = cache_size
6087

61-
return memodict[key]
88+
return wrapper
6289

63-
return wrapper
6490

6591
class Cache(object):
6692
_log = logging.getLogger('allensdk.api.cache')

allensdk/api/caching_utilities.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pathlib import Path
33
import warnings
44
import os
5+
import logging
56

67
from typing import overload, Callable, Any, Union, Optional, TypeVar
78

@@ -87,18 +88,23 @@ def call_caching(
8788
The result of calling read
8889
8990
"""
91+
logger = logging.getLogger("call_caching")
9092

9193
try:
9294
if not lazy or read is None:
95+
logger.info("Fetching data from remote")
9396
data = fetch()
9497
if pre_write is not None:
9598
data = pre_write(data)
99+
logger.info("Writing data to cache")
96100
write(data)
97101

98-
if read is not None:
102+
if read is not None:
103+
logger.info("Reading data from cache")
99104
return read()
100-
101-
except Exception:
105+
except Exception as e:
106+
if isinstance(e, FileNotFoundError):
107+
logger.info("No cache file found.")
102108
if cleanup is not None and not lazy:
103109
cleanup()
104110

@@ -150,7 +156,6 @@ def one_file_call_caching(
150156
Path at which the data will be stored
151157
152158
"""
153-
154159
def safe_unlink():
155160
try:
156161
os.unlink(path)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from typing import Any, Optional, List, Dict, Type, Tuple
2+
import logging
3+
import pandas as pd
4+
import numpy as np
5+
import inspect
6+
7+
from allensdk.internal.api.behavior_data_lims_api import BehaviorDataLimsApi
8+
from allensdk.brain_observatory.behavior.internal import BehaviorBase
9+
from allensdk.brain_observatory.running_speed import RunningSpeed
10+
11+
BehaviorDataApi = Type[BehaviorBase]
12+
13+
14+
class BehaviorDataSession(object):
15+
def __init__(self, api: Optional[BehaviorDataApi] = None):
16+
self.api = api
17+
18+
@classmethod
19+
def from_lims(cls, behavior_session_id: int) -> "BehaviorDataSession":
20+
return cls(api=BehaviorDataLimsApi(behavior_session_id))
21+
22+
@classmethod
23+
def from_nwb_path(
24+
cls, nwb_path: str, **api_kwargs: Any) -> "BehaviorDataSession":
25+
return NotImplementedError
26+
27+
@property
28+
def behavior_session_id(self) -> int:
29+
"""Unique identifier for this experimental session.
30+
:rtype: int
31+
"""
32+
return self.api.behavior_session_id
33+
34+
@property
35+
def ophys_session_id(self) -> Optional[int]:
36+
"""The unique identifier for the ophys session associated
37+
with this behavior session (if one exists)
38+
:rtype: int
39+
"""
40+
return self.api.ophys_session_id
41+
42+
@property
43+
def ophys_experiment_ids(self) -> Optional[List[int]]:
44+
"""The unique identifiers for the ophys experiment(s) associated
45+
with this behavior session (if one exists)
46+
:rtype: int
47+
"""
48+
return self.api.ophys_experiment_ids
49+
50+
@property
51+
def licks(self) -> pd.DataFrame:
52+
"""Get lick data from pkl file.
53+
54+
Returns
55+
-------
56+
np.ndarray
57+
A dataframe containing lick timestamps.
58+
"""
59+
return self.api.get_licks()
60+
61+
@property
62+
def rewards(self) -> pd.DataFrame:
63+
"""Get reward data from pkl file.
64+
65+
Returns
66+
-------
67+
pd.DataFrame
68+
A dataframe containing timestamps of delivered rewards.
69+
"""
70+
return self.api.get_rewards()
71+
72+
@property
73+
def running_data_df(self) -> pd.DataFrame:
74+
"""Get running speed data.
75+
76+
Returns
77+
-------
78+
pd.DataFrame
79+
Dataframe containing various signals used to compute running speed.
80+
"""
81+
return self.api.get_running_data_df()
82+
83+
@property
84+
def running_speed(self) -> RunningSpeed:
85+
"""Get running speed using timestamps from
86+
self.get_stimulus_timestamps.
87+
88+
NOTE: Do not correct for monitor delay.
89+
90+
Returns
91+
-------
92+
RunningSpeed (NamedTuple with two fields)
93+
timestamps : np.ndarray
94+
Timestamps of running speed data samples
95+
values : np.ndarray
96+
Running speed of the experimental subject (in cm / s).
97+
"""
98+
return self.api.get_running_speed()
99+
100+
@property
101+
def stimulus_presentations(self) -> pd.DataFrame:
102+
"""Get stimulus presentation data.
103+
104+
NOTE: Uses timestamps that do not account for monitor delay.
105+
106+
Returns
107+
-------
108+
pd.DataFrame
109+
Table whose rows are stimulus presentations
110+
(i.e. a given image, for a given duration, typically 250 ms)
111+
and whose columns are presentation characteristics.
112+
"""
113+
return self.api.get_stimulus_presentations()
114+
115+
@property
116+
def stimulus_templates(self) -> Dict[str, np.ndarray]:
117+
"""Get stimulus templates (movies, scenes) for behavior session.
118+
119+
Returns
120+
-------
121+
Dict[str, np.ndarray]
122+
A dictionary containing the stimulus images presented during the
123+
session. Keys are data set names, and values are 3D numpy arrays.
124+
"""
125+
return self.api.get_stimulus_templates()
126+
127+
@property
128+
def stimulus_timestamps(self) -> np.ndarray:
129+
"""Get stimulus timestamps from pkl file.
130+
131+
NOTE: Located with behavior_session_id
132+
133+
Returns
134+
-------
135+
np.ndarray
136+
Timestamps associated with stimulus presentations on the monitor
137+
that do no account for monitor delay.
138+
"""
139+
return self.api.get_stimulus_timestamps()
140+
141+
@property
142+
def task_parameters(self) -> dict:
143+
"""Get task parameters from pkl file.
144+
145+
Returns
146+
-------
147+
dict
148+
A dictionary containing parameters used to define the task runtime
149+
behavior.
150+
"""
151+
return self.api.get_task_parameters()
152+
153+
@property
154+
def trials(self) -> pd.DataFrame:
155+
"""Get trials from pkl file
156+
157+
Returns
158+
-------
159+
pd.DataFrame
160+
A dataframe containing behavioral trial start/stop times,
161+
and trial data
162+
"""
163+
return self.api.get_trials()
164+
165+
@property
166+
def metadata(self) -> Dict[str, Any]:
167+
"""Return metadata about the session.
168+
:rtype: dict
169+
"""
170+
return self.api.get_metadata()
171+
172+
def cache_clear(self) -> None:
173+
"""Convenience method to clear the api cache, if applicable."""
174+
try:
175+
self.api.cache_clear()
176+
except AttributeError:
177+
logging.getLogger("BehaviorOphysSession").warning(
178+
"Attempted to clear API cache, but method `cache_clear`"
179+
f" does not exist on {self.api.__class__.__name__}")
180+
181+
def list_api_methods(self) -> List[Tuple[str, str]]:
182+
"""Convenience method to expose list of API `get` methods. These methods
183+
can be accessed by referencing the API used to initialize this
184+
BehaviorDataSession via its `api` instance attribute.
185+
:rtype: list of tuples, where the first value in the tuple is the
186+
method name, and the second value is the method docstring.
187+
"""
188+
methods = [m for m in inspect.getmembers(self.api, inspect.ismethod)
189+
if m[0].startswith("get_")]
190+
docs = [inspect.getdoc(m[1]) or "" for m in methods]
191+
method_names = [m[0] for m in methods]
192+
return list(zip(method_names, docs))

0 commit comments

Comments
 (0)