Skip to content

Commit

Permalink
Merge pull request #1156 from AllenInstitute/rc/1.2.0
Browse files Browse the repository at this point in the history
rc/1.2.0
  • Loading branch information
NileGraddis authored Nov 22, 2019
2 parents 6f73cc1 + 2e1c680 commit f8857ad
Show file tree
Hide file tree
Showing 58 changed files with 5,278 additions and 816 deletions.
2 changes: 1 addition & 1 deletion allensdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
#
import logging

__version__ = '1.1.1'
__version__ = '1.2.0'

try:
from logging import NullHandler
Expand Down
44 changes: 35 additions & 9 deletions allensdk/api/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,51 @@
import pandas.io.json as pj

import functools
from functools import wraps
from functools import wraps, _make_key
import os
import logging
import csv


def memoize(f):
memodict = dict()
"""
Creates an unbound cache of function calls and results. Note that arguments
of different types are not cached separately (so f(3.0) and f(3) are not
treated as distinct calls)
Arguments to the cached function must be hashable.
View the cache size with f.cache_size().
Clear the cache with f.cache_clear().
Access the underlying function with f.__wrapped__.
"""
cache = {}
sentinel = object() # unique object for cache misses
make_key = _make_key # efficient key building from function args
cache_get = cache.get
cache_len = cache.__len__

@wraps(f)
def wrapper(*args, **kwargs):
key = make_key(args, kwargs, typed=False) # Don't consider 3.0 and 3 different
result = cache_get(key, sentinel)
if result is not sentinel:
return result
result = f(*args, **kwargs)
cache[key] = result
return result

def cache_clear():
cache.clear()

@wraps(f)
def wrapper(*args, **kwargs):
key = (args, tuple(kwargs.items()))
def cache_size():
return cache_len()

if key not in memodict:
memodict[key] = f(*args, **kwargs)
wrapper.cache_clear = cache_clear
wrapper.cache_size = cache_size

return memodict[key]
return wrapper

return wrapper

class Cache(object):
_log = logging.getLogger('allensdk.api.cache')
Expand Down
13 changes: 9 additions & 4 deletions allensdk/api/caching_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
import warnings
import os
import logging

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

Expand Down Expand Up @@ -87,18 +88,23 @@ def call_caching(
The result of calling read
"""
logger = logging.getLogger("call_caching")

try:
if not lazy or read is None:
logger.info("Fetching data from remote")
data = fetch()
if pre_write is not None:
data = pre_write(data)
logger.info("Writing data to cache")
write(data)

if read is not None:
if read is not None:
logger.info("Reading data from cache")
return read()

except Exception:
except Exception as e:
if isinstance(e, FileNotFoundError):
logger.info("No cache file found.")
if cleanup is not None and not lazy:
cleanup()

Expand Down Expand Up @@ -150,7 +156,6 @@ def one_file_call_caching(
Path at which the data will be stored
"""

def safe_unlink():
try:
os.unlink(path)
Expand Down
192 changes: 192 additions & 0 deletions allensdk/brain_observatory/behavior/behavior_data_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
from typing import Any, Optional, List, Dict, Type, Tuple
import logging
import pandas as pd
import numpy as np
import inspect

from allensdk.internal.api.behavior_data_lims_api import BehaviorDataLimsApi
from allensdk.brain_observatory.behavior.internal import BehaviorBase
from allensdk.brain_observatory.running_speed import RunningSpeed

BehaviorDataApi = Type[BehaviorBase]


class BehaviorDataSession(object):
def __init__(self, api: Optional[BehaviorDataApi] = None):
self.api = api

@classmethod
def from_lims(cls, behavior_session_id: int) -> "BehaviorDataSession":
return cls(api=BehaviorDataLimsApi(behavior_session_id))

@classmethod
def from_nwb_path(
cls, nwb_path: str, **api_kwargs: Any) -> "BehaviorDataSession":
return NotImplementedError

@property
def behavior_session_id(self) -> int:
"""Unique identifier for this experimental session.
:rtype: int
"""
return self.api.behavior_session_id

@property
def ophys_session_id(self) -> Optional[int]:
"""The unique identifier for the ophys session associated
with this behavior session (if one exists)
:rtype: int
"""
return self.api.ophys_session_id

@property
def ophys_experiment_ids(self) -> Optional[List[int]]:
"""The unique identifiers for the ophys experiment(s) associated
with this behavior session (if one exists)
:rtype: int
"""
return self.api.ophys_experiment_ids

@property
def licks(self) -> pd.DataFrame:
"""Get lick data from pkl file.
Returns
-------
np.ndarray
A dataframe containing lick timestamps.
"""
return self.api.get_licks()

@property
def rewards(self) -> pd.DataFrame:
"""Get reward data from pkl file.
Returns
-------
pd.DataFrame
A dataframe containing timestamps of delivered rewards.
"""
return self.api.get_rewards()

@property
def running_data_df(self) -> pd.DataFrame:
"""Get running speed data.
Returns
-------
pd.DataFrame
Dataframe containing various signals used to compute running speed.
"""
return self.api.get_running_data_df()

@property
def running_speed(self) -> RunningSpeed:
"""Get running speed using timestamps from
self.get_stimulus_timestamps.
NOTE: Do not correct for monitor delay.
Returns
-------
RunningSpeed (NamedTuple with two fields)
timestamps : np.ndarray
Timestamps of running speed data samples
values : np.ndarray
Running speed of the experimental subject (in cm / s).
"""
return self.api.get_running_speed()

@property
def stimulus_presentations(self) -> pd.DataFrame:
"""Get stimulus presentation data.
NOTE: Uses timestamps that do not account for monitor delay.
Returns
-------
pd.DataFrame
Table whose rows are stimulus presentations
(i.e. a given image, for a given duration, typically 250 ms)
and whose columns are presentation characteristics.
"""
return self.api.get_stimulus_presentations()

@property
def stimulus_templates(self) -> Dict[str, np.ndarray]:
"""Get stimulus templates (movies, scenes) for behavior session.
Returns
-------
Dict[str, np.ndarray]
A dictionary containing the stimulus images presented during the
session. Keys are data set names, and values are 3D numpy arrays.
"""
return self.api.get_stimulus_templates()

@property
def stimulus_timestamps(self) -> np.ndarray:
"""Get stimulus timestamps from pkl file.
NOTE: Located with behavior_session_id
Returns
-------
np.ndarray
Timestamps associated with stimulus presentations on the monitor
that do no account for monitor delay.
"""
return self.api.get_stimulus_timestamps()

@property
def task_parameters(self) -> dict:
"""Get task parameters from pkl file.
Returns
-------
dict
A dictionary containing parameters used to define the task runtime
behavior.
"""
return self.api.get_task_parameters()

@property
def trials(self) -> pd.DataFrame:
"""Get trials from pkl file
Returns
-------
pd.DataFrame
A dataframe containing behavioral trial start/stop times,
and trial data
"""
return self.api.get_trials()

@property
def metadata(self) -> Dict[str, Any]:
"""Return metadata about the session.
:rtype: dict
"""
return self.api.get_metadata()

def cache_clear(self) -> None:
"""Convenience method to clear the api cache, if applicable."""
try:
self.api.cache_clear()
except AttributeError:
logging.getLogger("BehaviorOphysSession").warning(
"Attempted to clear API cache, but method `cache_clear`"
f" does not exist on {self.api.__class__.__name__}")

def list_api_methods(self) -> List[Tuple[str, str]]:
"""Convenience method to expose list of API `get` methods. These methods
can be accessed by referencing the API used to initialize this
BehaviorDataSession via its `api` instance attribute.
:rtype: list of tuples, where the first value in the tuple is the
method name, and the second value is the method docstring.
"""
methods = [m for m in inspect.getmembers(self.api, inspect.ismethod)
if m[0].startswith("get_")]
docs = [inspect.getdoc(m[1]) or "" for m in methods]
method_names = [m[0] for m in methods]
return list(zip(method_names, docs))
Loading

0 comments on commit f8857ad

Please sign in to comment.