Skip to content

Commit

Permalink
Merge pull request #1451 from AllenInstitute/rc/1.7.0
Browse files Browse the repository at this point in the history
rc/1.7.0
  • Loading branch information
pickles-bread-and-butter authored Apr 29, 2020
2 parents e33b364 + 5f406dc commit be4dfb1
Show file tree
Hide file tree
Showing 21 changed files with 4,435 additions and 167 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# Change Log
All notable changes to this project will be documented in this file.

## [1.7.0] = 2020-04-29

### Added
- Internal users can now access `eye_tracking` ellipse fit data from behavior + ophys Session objects
- A new mixin for managing processing parameters for Session objects
- Added support for additional sync file line labels

### Changed
- Monitor delay calculation is updated to properly handle photodiode streams that end
on a rising edge. We are no longer providing a default delay value in case of error.

### Bug Fixes
- experiment\_table from behavior project cache has NaNs in the 'imaging\_depth' column for MultiScope experiments due to incorrect join in behavior\_project\_lims\_api.py and 4 other places where ophys\_sessions was incorrectly queried for imaging\_depth\_id
- get_keys method for sync datasets was returning the wrong line labels and creating incorrect key, value pairs for
data loading from sync files

## [1.6.0] = 2020-03-23

### Added
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# these users will be notified whenever a pr is opened
* @nilegraddis @njmei @kschelonka @sgratiy
* @nilegraddis @njmei @kschelonka @sgratiy @isaak.willett
2 changes: 1 addition & 1 deletion allensdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import logging


__version__ = '1.6.0'
__version__ = '1.7.0'


try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ def get_average_projection(self):

def get_segmentation_mask_image(self):
raise NotImplementedError

def get_eye_tracking_data(self):
raise NotImplementedError
113 changes: 93 additions & 20 deletions allensdk/brain_observatory/behavior/behavior_ophys_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import Any
import logging


from allensdk.brain_observatory.session_api_utils import ParamsMixin
from allensdk.internal.api.behavior_ophys_api import BehaviorOphysLimsApi
from allensdk.brain_observatory.behavior.behavior_ophys_api\
.behavior_ophys_nwb_api import BehaviorOphysNwbApi
Expand All @@ -19,15 +21,19 @@
from allensdk.brain_observatory.running_speed import RunningSpeed


class BehaviorOphysSession(object):
class BehaviorOphysSession(ParamsMixin):
"""Represents data from a single Visual Behavior Ophys imaging session.
Can be initialized with an api that fetches data, or by using class methods
`from_lims` and `from_nwb_path`.
"""

@classmethod
def from_lims(cls, ophys_experiment_id: int) -> "BehaviorOphysSession":
return cls(api=BehaviorOphysLimsApi(ophys_experiment_id))
def from_lims(cls, ophys_experiment_id: int,
eye_tracking_z_threshold: float = 3.0,
eye_tracking_dilation_frames: int = 2) -> "BehaviorOphysSession":
return cls(api=BehaviorOphysLimsApi(ophys_experiment_id),
eye_tracking_z_threshold=eye_tracking_z_threshold,
eye_tracking_dilation_frames=eye_tracking_dilation_frames)

@classmethod
def from_nwb_path(
Expand All @@ -37,7 +43,25 @@ def from_nwb_path(
return cls(api=BehaviorOphysNwbApi.from_path(
path=nwb_path, **api_kwargs))

def __init__(self, api=None):
def __init__(self, api=None,
eye_tracking_z_threshold: float = 3.0,
eye_tracking_dilation_frames: int = 2):
"""
Parameters
----------
api : object, optional
The backend api used by the session object to get behavior ophys
data, by default None.
eye_tracking_z_threshold : float, optional
Determines the z-score threshold used for processing
`eye_tracking` data, by default 3.0.
eye_tracking_dilation_frames : int, optional
Determines the number of adjacent frames that will be marked
as 'likely_blink' when performing blink detection for
`eye_tracking` data, by default 2
"""
super().__init__(ignore={'api'})

self.api = api
# Initialize attributes to be lazily evaluated
self._stimulus_timestamps = None
Expand All @@ -56,9 +80,14 @@ def __init__(self, api=None):
self._corrected_fluorescence_traces = None
self._motion_correction = None
self._segmentation_mask_image = None
self._eye_tracking = None

# eye_tracking params
self._eye_tracking_z_threshold = eye_tracking_z_threshold
self._eye_tracking_dilation_frames = eye_tracking_dilation_frames

# Using properties rather than initializing attributes to take advantage
# of API-level cache and not introduce a lot of overhead when the
# of API-level cache and not introduce a lot of overhead when the
# class is initialized (sometimes these calls can take a while)
@property
def ophys_experiment_id(self) -> int:
Expand All @@ -76,7 +105,7 @@ def max_projection(self) -> Image:

@property
def stimulus_timestamps(self) -> np.ndarray:
"""Timestamps associated with stimulus presentations on the
"""Timestamps associated with stimulus presentations on the
monitor (corrected for monitor delay).
:rtype: numpy.ndarray
"""
Expand All @@ -93,7 +122,7 @@ def ophys_timestamps(self) -> np.ndarray:
"""Timestamps associated with frames captured by the microscope
:rtype: numpy.ndarray
"""
if self._ophys_timestamps is None:
if self._ophys_timestamps is None:
self._ophys_timestamps = self.api.get_ophys_timestamps()
return self._ophys_timestamps

Expand Down Expand Up @@ -305,6 +334,50 @@ def segmentation_mask_image(self) -> Image:
def segmentation_mask_image(self, value):
self._segmentation_mask_image = value

@property
def eye_tracking(self) -> pd.DataFrame:
"""A dataframe containing ellipse fit parameters for the eye, pupil
and corneal reflection (cr). Fits are derived from tracking points
from a DeepLabCut model applied to video frames of a subject's
right eye. Raw tracking points and raw video frames are not exposed
by the SDK.
Notes:
- All columns starting with 'pupil_' represent ellipse fit parameters
relating to the pupil.
- All columns starting with 'eye_' represent ellipse fit parameters
relating to the eyelid.
- All columns starting with 'cr_' represent ellipse fit parameters
relating to the corneal reflection, which is caused by an infrared
LED positioned near the eye tracking camera.
- All positions are in units of pixels.
- All areas are in units of pixels^2
- All values are in the coordinate space of the eye tracking camera,
NOT the coordinate space of the stimulus display (i.e. this is not
gaze location), with (0, 0) being the upper-left corner of the
eye-tracking image.
- The 'likely_blink' column is True for any row (frame) where the pupil
fit failed OR eye fit failed OR an outlier fit was identified.
- All ellipse fits are derived from tracking points that were output by
a DeepLabCut model that was trained on hand-annotated data frome a
subset of imaging sessions on optical physiology rigs.
- Raw DeepLabCut tracking points are not publicly available.
:rtype: pandas.DataFrame
"""
params = {'eye_tracking_dilation_frames', 'eye_tracking_z_threshold'}

if (self._eye_tracking is None) or self.needs_data_refresh(params):
self._eye_tracking = self.api.get_eye_tracking(z_threshold=self._eye_tracking_z_threshold,
dilation_frames=self._eye_tracking_dilation_frames)
self.clear_updated_params(params)

return self._eye_tracking

@eye_tracking.setter
def eye_tracking(self, value):
self._eye_tracking = value

def cache_clear(self) -> None:
"""Convenience method to clear the api cache, if applicable."""
try:
Expand All @@ -320,7 +393,7 @@ def get_roi_masks(self, cell_specimen_ids=None):
Parameters
----------
cell_specimen_ids : array-like of int, optional
ROI masks for these cell specimens will be returned. The default behavior is to return masks for all
ROI masks for these cell specimens will be returned. The default behavior is to return masks for all
cell specimens.
Returns
Expand Down Expand Up @@ -356,7 +429,7 @@ def _get_roi_masks_by_cell_roi_id(self, cell_roi_ids=None):
----------
cell_roi_ids : array-like of int, optional
ROI masks for these rois will be returned. The default behavior is to return masks for all rois.
Returns
-------
result : xr.DataArray
Expand Down Expand Up @@ -399,12 +472,12 @@ def _get_roi_masks_by_cell_roi_id(self, cell_roi_ids=None):
dims=("cell_roi_id", "row", "column"),
coords={
"cell_roi_id": cell_roi_ids,
"row": np.arange(full_image_shape[0])*spacing[0],
"column": np.arange(full_image_shape[1])*spacing[1]
"row": np.arange(full_image_shape[0]) * spacing[0],
"column": np.arange(full_image_shape[1]) * spacing[1]
},
attrs={
"spacing":spacing,
"unit":unit
"spacing": spacing,
"unit": unit
}
).squeeze(drop=True)

Expand Down Expand Up @@ -479,9 +552,9 @@ def get_segmentation_mask_image(self):
masks = self._get_roi_masks_by_cell_roi_id()
mask_image_data = masks.any(dim='cell_roi_id').astype(int)
mask_image = Image(
data = mask_image_data.values,
spacing = masks.attrs['spacing'],
unit = masks.attrs['unit']
data=mask_image_data.values,
spacing=masks.attrs['spacing'],
unit=masks.attrs['unit']
)
return mask_image

Expand Down Expand Up @@ -510,15 +583,15 @@ def get_rolling_performance_df(self):
# Hit rate raw:
hit_rate_raw = get_hit_rate(hit=self.trials.hit, miss=self.trials.miss, aborted=self.trials.aborted)
performance_metrics_df['hit_rate_raw'] = pd.Series(hit_rate_raw, index=not_aborted_index)

# Hit rate with trial count correction:
hit_rate = get_trial_count_corrected_hit_rate(hit=self.trials.hit, miss=self.trials.miss, aborted=self.trials.aborted)
performance_metrics_df['hit_rate'] = pd.Series(hit_rate, index=not_aborted_index)

# False-alarm rate raw:
false_alarm_rate_raw = get_false_alarm_rate(false_alarm=self.trials.false_alarm, correct_reject=self.trials.correct_reject, aborted=self.trials.aborted)
performance_metrics_df['false_alarm_rate_raw'] = pd.Series(false_alarm_rate_raw, index=not_aborted_index)

# False-alarm rate with trial count correction:
false_alarm_rate = get_trial_count_corrected_false_alarm_rate(false_alarm=self.trials.false_alarm, correct_reject=self.trials.correct_reject, aborted=self.trials.aborted)
performance_metrics_df['false_alarm_rate'] = pd.Series(false_alarm_rate, index=not_aborted_index)
Expand Down Expand Up @@ -563,8 +636,8 @@ def get_performance_metrics(self, engaged_trial_reward_rate_threshold=2):

def _translate_roi_mask(mask, row_offset, col_offset):
return np.roll(
mask,
shift=(row_offset, col_offset),
mask,
shift=(row_offset, col_offset),
axis=(0, 1)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def _get_experiment_table(
JOIN (
{self._build_line_from_donor_query(line="driver")}
) driver on driver.donor_id = d.id
LEFT JOIN imaging_depths id ON id.id = os.imaging_depth_id
LEFT JOIN imaging_depths id ON id.id = oe.imaging_depth_id
JOIN structures st ON st.id = oe.targeted_structure_id
JOIN equipment ON equipment.id = os.equipment_id
{experiment_query};
Expand Down
Loading

0 comments on commit be4dfb1

Please sign in to comment.