Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ticket/nnnn/sync/stim/aligner #2354

Merged
merged 5 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions allensdk/brain_observatory/behavior/behavior_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import \
JsonReadableInterface, NwbReadableInterface, \
LimsReadableInterface
Expand Down Expand Up @@ -98,7 +98,7 @@ def from_json(cls,

behavior_session_id = BehaviorSessionId.from_json(
dict_repr=session_data)
stimulus_file = StimulusFile.from_json(dict_repr=session_data)
stimulus_file = BehaviorStimulusFile.from_json(dict_repr=session_data)
stimulus_timestamps = StimulusTimestamps.from_json(
dict_repr=session_data)
running_acquisition = RunningAcquisition.from_json(
Expand Down Expand Up @@ -174,7 +174,7 @@ def from_lims(cls, behavior_session_id: int,
monitor_delay = cls._get_monitor_delay()

behavior_session_id = BehaviorSessionId(behavior_session_id)
stimulus_file = StimulusFile.from_lims(
stimulus_file = BehaviorStimulusFile.from_lims(
db=lims_db, behavior_session_id=behavior_session_id.value)
if stimulus_timestamps is None:
stimulus_timestamps = StimulusTimestamps.from_stimulus_file(
Expand Down Expand Up @@ -878,7 +878,7 @@ def metadata(self) -> Dict[str, Any]:

@classmethod
def _read_data_from_stimulus_file(
cls, stimulus_file: StimulusFile,
cls, stimulus_file: BehaviorStimulusFile,
stimulus_timestamps: StimulusTimestamps):
"""Helper method to read data from stimulus file"""
licks = Licks.from_stimulus_file(
Expand Down
6 changes: 5 additions & 1 deletion allensdk/brain_observatory/behavior/data_files/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
from allensdk.brain_observatory.behavior.data_files.stimulus_file import StimulusFile # noqa E501, F401
from allensdk.brain_observatory.behavior.data_files.stimulus_file import ( # noqa F401
BehaviorStimulusFile,
ReplayStimulusFile,
MappingStimulusFile)

from allensdk.brain_observatory.behavior.data_files.sync_file import SyncFile # noqa E501, F401
140 changes: 128 additions & 12 deletions allensdk/brain_observatory/behavior/data_files/stimulus_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from allensdk.core import DataObject

# Query returns path to StimulusPickle file for given behavior session
STIMULUS_FILE_QUERY_TEMPLATE = """
BEHAVIOR_STIMULUS_FILE_QUERY_TEMPLATE = """
SELECT
wkf.storage_directory || wkf.filename AS stim_file
FROM
Expand All @@ -37,7 +37,45 @@ def from_lims_cache_key(cls, db, behavior_session_id: int):
return hashkey(behavior_session_id)


class StimulusFile(DataFile):
class _NumFramesMixin(object):
"""
Mixin to implement num_frames for a generic (i.e. non-behavior)
StimulusFile
"""

def _validate_frame_data(self) -> None:
"""
Check that self.data['intervalsms'] is present and
that self.data['items']['behavior']['intervalsms'] is empty
"""
msg = ""
if 'intervalsms' not in self.data:
msg += "self.data['intervalsms'] not present\n"
if "items" in self.data:
if "behavior" in self.data["items"]:
if "intervalsms" in self.data["items"]["behavior"]:
val = self.data["items"]["behavior"]["intervalsms"]
if len(val) > 0:
msg += ("len(self.data['items']['behavior']"
f"['intervalsms'] == {len(val)}; "
"expected zero\n")
if len(msg) > 0:
full_msg = f"When getting num_frames from {type(self)}\n"
full_msg += msg
full_msg += f"\nfilepath: {self.filepath}"
raise RuntimeError(full_msg)

return None

def num_frames(self) -> int:
"""
Return the number of frames associated with this StimulusFile
"""
self._validate_frame_data()
return len(self.data['intervalsms']) + 1


class _StimulusFile(DataFile):
"""A DataFile which contains methods for accessing and loading visual
behavior stimulus *.pkl files.

Expand All @@ -46,41 +84,119 @@ class StimulusFile(DataFile):
trials, and timing for all of the above.
"""

@classmethod
def file_path_key(cls) -> str:
"""
The key in the dict_repr that maps to the path
to this StimulusFile's pickle file on disk.
"""
raise NotImplementedError()

def __init__(self, filepath: Union[str, Path]):
super().__init__(filepath=filepath)

@classmethod
@cached(cache=LRUCache(maxsize=10), key=from_json_cache_key)
def from_json(cls, dict_repr: dict) -> "StimulusFile":
filepath = dict_repr["behavior_stimulus_file"]
def from_json(cls, dict_repr: dict) -> "_StimulusFile":
filepath = dict_repr[cls.file_path_key()]
return cls(filepath=filepath)

def to_json(self) -> Dict[str, str]:
return {"behavior_stimulus_file": str(self.filepath)}
return {self.file_path_key(): str(self.filepath)}

@classmethod
@cached(cache=LRUCache(maxsize=10), key=from_lims_cache_key)
def from_lims(
cls, db: PostgresQueryMixin,
behavior_session_id: Union[int, str]
) -> "StimulusFile":
query = STIMULUS_FILE_QUERY_TEMPLATE.format(
behavior_session_id=behavior_session_id
)
filepath = db.fetchone(query, strict=True)
return cls(filepath=filepath)
) -> "_StimulusFile":
raise NotImplementedError()

@staticmethod
def load_data(filepath: Union[str, Path]) -> dict:
filepath = safe_system_path(file_name=filepath)
return pd.read_pickle(filepath)

def num_frames(self) -> int:
"""
Return the number of frames associated with this StimulusFile
"""
raise NotImplementedError()


class BehaviorStimulusFile(_StimulusFile):

@classmethod
def file_path_key(cls) -> str:
return "behavior_stimulus_file"

@classmethod
@cached(cache=LRUCache(maxsize=10), key=from_lims_cache_key)
def from_lims(
cls, db: PostgresQueryMixin,
behavior_session_id: Union[int, str]
) -> "BehaviorStimulusFile":
query = BEHAVIOR_STIMULUS_FILE_QUERY_TEMPLATE.format(
behavior_session_id=behavior_session_id
)
filepath = db.fetchone(query, strict=True)
return cls(filepath=filepath)

def _validate_frame_data(self):
"""
Make sure that self.data['intervalsms'] does not exist and that
self.data['items']['behavior']['intervalsms'] does exist.
"""
msg = ""
if "intervalsms" in self.data:
msg += "self.data['intervalsms'] present; did not expect that\n"
if "items" not in self.data:
msg += "self.data['items'] not present\n"
else:
if "behavior" not in self.data["items"]:
msg += "self.data['items']['behavior'] not present\n"
else:
if "intervalsms" not in self.data["items"]["behavior"]:
msg += ("self.data['items']['behavior']['intervalsms'] "
"not present\n")

if len(msg) > 0:
full_msg = f"When getting num_frames from {type(self)}\n"
full_msg += msg
full_msg += f"\nfilepath: {self.filepath}"
raise RuntimeError(full_msg)

return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to explicitly return None as far as I know.


def num_frames(self) -> int:
"""
Return the number of frames associated with this StimulusFile
"""
self._validate_frame_data()
return len(self.data['items']['behavior']['intervalsms']) + 1


class ReplayStimulusFile(_NumFramesMixin, _StimulusFile):

@classmethod
def file_path_key(cls) -> str:
return "replay_stimulus_file"


class MappingStimulusFile(_NumFramesMixin, _StimulusFile):

@classmethod
def file_path_key(cls) -> str:
return "mapping_stimulus_file"


class StimulusFileReadableInterface(abc.ABC):
"""Marks a data object as readable from stimulus file"""
@classmethod
@abc.abstractmethod
def from_stimulus_file(cls, stimulus_file: StimulusFile) -> "DataObject":
def from_stimulus_file(
cls,
stimulus_file: BehaviorStimulusFile) -> "DataObject":
"""Populate a DataObject from the stimulus file

Returns
Expand Down
4 changes: 2 additions & 2 deletions allensdk/brain_observatory/behavior/data_objects/licks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
from pynwb import NWBFile, TimeSeries, ProcessingModule

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.brain_observatory.behavior.data_objects import StimulusTimestamps
from allensdk.core import \
Expand All @@ -31,7 +31,7 @@ def __init__(self, licks: pd.DataFrame):
super().__init__(name='licks', value=licks)

@classmethod
def from_stimulus_file(cls, stimulus_file: StimulusFile,
def from_stimulus_file(cls, stimulus_file: BehaviorStimulusFile,
stimulus_timestamps: StimulusTimestamps) -> "Licks":
"""Get lick data from pkl file.
This function assumes that the first sensor in the list of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import numpy as np
from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject

from allensdk.brain_observatory.behavior.data_objects import BehaviorSessionId
Expand Down Expand Up @@ -208,7 +208,7 @@ def from_lims(
equipment = Equipment.from_lims(
behavior_session_id=behavior_session_id.value, lims_db=lims_db)

stimulus_file = StimulusFile.from_lims(
stimulus_file = BehaviorStimulusFile.from_lims(
db=lims_db, behavior_session_id=behavior_session_id.value)
stimulus_frame_rate = StimulusFrameRate.from_stimulus_file(
stimulus_file=stimulus_file)
Expand Down Expand Up @@ -238,7 +238,7 @@ def from_json(cls, dict_repr: dict) -> "BehaviorMetadata":
behavior_session_id = BehaviorSessionId.from_json(dict_repr=dict_repr)
equipment = Equipment.from_json(dict_repr=dict_repr)

stimulus_file = StimulusFile.from_json(dict_repr=dict_repr)
stimulus_file = BehaviorStimulusFile.from_json(dict_repr=dict_repr)
stimulus_frame_rate = StimulusFrameRate.from_stimulus_file(
stimulus_file=stimulus_file)
session_type = SessionType.from_stimulus_file(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.core import \
NwbReadableInterface
Expand All @@ -20,7 +20,8 @@ def __init__(self, behavior_session_uuid: Optional[uuid.UUID]):

@classmethod
def from_stimulus_file(
cls, stimulus_file: StimulusFile) -> "BehaviorSessionUUID":
cls,
stimulus_file: BehaviorStimulusFile) -> "BehaviorSessionUUID":
id = stimulus_file.data.get('session_uuid')
if id:
id = uuid.UUID(id)
Expand All @@ -34,7 +35,7 @@ def from_nwb(cls, nwbfile: NWBFile) -> "BehaviorSessionUUID":

def validate(self, behavior_session_id: int,
foraging_id: int,
stimulus_file: StimulusFile) -> "BehaviorSessionUUID":
stimulus_file: BehaviorStimulusFile) -> "BehaviorSessionUUID":
"""
Sanity check to ensure that pkl file data matches up with
the behavior session that the pkl file has been associated with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytz
from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.core import \
JsonReadableInterface, LimsReadableInterface, NwbReadableInterface
Expand Down Expand Up @@ -48,7 +48,7 @@ def from_lims(
def from_nwb(cls, nwbfile: NWBFile) -> "DateOfAcquisition":
return cls(date_of_acquisition=nwbfile.session_start_time)

def validate(self, stimulus_file: StimulusFile,
def validate(self, stimulus_file: BehaviorStimulusFile,
behavior_session_id: int) -> "DateOfAcquisition":
"""raise a warning if the date differs too much from the
datetime obtained from the behavior stimulus (*.pkl) file."""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.core import \
NwbReadableInterface
Expand All @@ -17,7 +17,7 @@ def __init__(self, session_type: str):
@classmethod
def from_stimulus_file(
cls,
stimulus_file: StimulusFile) -> "SessionType":
stimulus_file: BehaviorStimulusFile) -> "SessionType":
try:
stimulus_name = \
stimulus_file.data["items"]["behavior"]["cl_params"]["stage"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pynwb import NWBFile

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.core import \
NwbReadableInterface
Expand All @@ -22,7 +22,7 @@ def __init__(self, stimulus_frame_rate: float):
@classmethod
def from_stimulus_file(
cls,
stimulus_file: StimulusFile) -> "StimulusFrameRate":
stimulus_file: BehaviorStimulusFile) -> "StimulusFrameRate":

# in this data object, we only care about the difference between
# timestamps, so we can set the monitor_delay to any
Expand Down
4 changes: 2 additions & 2 deletions allensdk/brain_observatory/behavior/data_objects/rewards.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pandas as pd
from pynwb import NWBFile, TimeSeries, ProcessingModule

from allensdk.brain_observatory.behavior.data_files import StimulusFile
from allensdk.brain_observatory.behavior.data_files import BehaviorStimulusFile
from allensdk.core import DataObject
from allensdk.brain_observatory.behavior.data_objects import StimulusTimestamps
from allensdk.core import \
Expand All @@ -21,7 +21,7 @@ def __init__(self, rewards: pd.DataFrame):

@classmethod
def from_stimulus_file(
cls, stimulus_file: StimulusFile,
cls, stimulus_file: BehaviorStimulusFile,
stimulus_timestamps: StimulusTimestamps) -> "Rewards":
"""Get reward data from pkl file, based on timestamps
(not sync file).
Expand Down
Loading