def test_get_ophys_timestamps(monkeypatch, plane_group, ophys_timestamps, dff_traces, expected, context): """Test the acquisition frame truncation only happens for non-mesoscope data (and raises error for scientifica data with longer trace frames than acquisition frames (ophys_timestamps)).""" def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_behavior_session_id", lambda x: 123) ctx.setattr(BehaviorOphysLimsExtractor, "_get_ids", lambda x: {}) patched_extractor = BehaviorOphysLimsExtractor(123) api = BehaviorOphysLimsApi(extractor=patched_extractor) # Mocking any db calls monkeypatch.setattr(api, "get_sync_data", lambda: {"ophys_frames": ophys_timestamps}) monkeypatch.setattr(api, "get_raw_dff_data", lambda: dff_traces) monkeypatch.setattr(api.extractor, "get_imaging_plane_group", lambda: plane_group) monkeypatch.setattr(api.extractor, "get_plane_group_count", lambda: 2) with context: actual = api.get_ophys_timestamps() if expected is not None: np.testing.assert_array_equal(expected, actual)
def validate_ophys_timestamps(ophys_experiment_id, api=None): api = BehaviorOphysLimsApi() if api is None else api ophys_experiment_dir = api.get_ophys_experiment_dir(ophys_experiment_id) raw_filepath = os.path.join(ophys_experiment_dir, str(ophys_experiment_id)+'.h5') raw_data_shape = get_raw_ophys_file_shape(raw_filepath) ophys_timestamps_shape = api.get_ophys_timestamps(ophys_experiment_id=ophys_experiment_id).shape if raw_data_shape[0] != ophys_timestamps_shape[0]: raise ValidationError('ophys_timestamp length does not match raw data length')
def test_get_behavior_stimulus_file(ophys_experiment_id, compare_val): if compare_val is None: expected_fail = False try: api = BehaviorOphysLimsApi(ophys_experiment_id) api.extractor.get_behavior_stimulus_file() except OneResultExpectedError: expected_fail = True assert expected_fail is True else: api = BehaviorOphysLimsApi(ophys_experiment_id) assert api.extractor.get_behavior_stimulus_file() == compare_val
def test_corrected_fluorescence_trace_order(monkeypatch, tmpdir): """ Test that BehaviorOphysLimsApi.get_corrected_fluorescence_traces can reorder ROIs to align with what is in the cell_specimen_table """ out_fname = os.path.join(tmpdir, "dummy_ftrace_data.h5") rng = np.random.RandomState(1234) n_t = 100 data = rng.random_sample((5, n_t)) roi_names = np.array([5, 3, 4, 2, 1]) with h5py.File(out_fname, "w") as out_file: out_file.create_dataset("data", data=data) out_file.create_dataset("roi_names", data=roi_names.astype(bytes)) cell_data = {"junk": [6, 7, 8, 9, 10], "cell_roi_id": [b"1", b"2", b"3", b"4", b"5"]} cell_table = pd.DataFrame(data=cell_data, index=pd.Index([10, 20, 30, 40, 50], name="cell_specimen_id")) def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id self.get_behavior_session_id = 2 with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_demix_file", lambda *args: out_fname) patched_extractor = BehaviorOphysLimsExtractor(123) ctx.setattr(BehaviorOphysLimsApi, "get_ophys_timestamps", lambda *args: np.zeros(n_t)) ctx.setattr(BehaviorOphysLimsApi, "get_cell_specimen_table", lambda *args: cell_table) api = BehaviorOphysLimsApi(extractor=patched_extractor) f_traces = api.get_corrected_fluorescence_traces() # check that the f_traces data frame was correctly joined # on roi_id colname = "corrected_fluorescence" roi_to_dex = {1: 4, 2: 3, 3: 1, 4: 2, 5: 0} for ii, roi_id in enumerate([1, 2, 3, 4, 5]): cell = (f_traces.loc[f_traces.cell_roi_id == bytes(f"{roi_id}", "utf-8")]) assert cell.index.values[0] == 10*roi_id np.testing.assert_array_almost_equal(cell[colname].values[0], data[roi_to_dex[roi_id]], decimal=10)
def test_get_nwb_filepath(ophys_experiment_id): api = BehaviorOphysLimsApi(ophys_experiment_id) assert api.extractor.get_nwb_filepath() == ( "/allen/programs/braintv/production/visualbehavior/prod0/" "specimen_823826986/ophys_session_859701393/" "ophys_experiment_860030092/behavior_ophys_session_860030092.nwb")
def test_eye_tracking_rig_geometry_returns_single_rig(monkeypatch): """ This test tests that when there are multiple rig geometries for an experiment, that only the most recent is returned """ def dummy_init(self, ophys_experiment_id): self.ophys_experiment_id = ophys_experiment_id with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, '__init__', dummy_init) patched_extractor = BehaviorOphysLimsExtractor(123) api = BehaviorOphysLimsApi(extractor=patched_extractor) resources_dir = Path(os.path.dirname(__file__)) / 'resources' rig_geometry = ( pd.read_pickle(resources_dir / 'rig_geometry_multiple_rig_configs.pkl')) rig_geometry = api.extractor._process_eye_tracking_rig_geometry( rig_geometry=rig_geometry) expected = { 'camera_position_mm': [102.8, 74.7, 31.6], 'led_position': [246.0, 92.3, 52.6], 'monitor_position_mm': [118.6, 86.2, 31.6], 'camera_rotation_deg': [0.0, 0.0, 2.8], 'monitor_rotation_deg': [0.0, 0.0, 0.0], 'equipment': 'CAM2P.5' } assert rig_geometry == expected
def get_session_data(self, ophys_session_id: int) -> BehaviorOphysSession: """Returns a BehaviorOphysSession object that contains methods to analyze a single behavior+ophys session. :param ophys_session_id: id that corresponds to a behavior session :type ophys_session_id: int :rtype: BehaviorOphysSession """ return BehaviorOphysSession(BehaviorOphysLimsApi(ophys_session_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)
def test_corrected_fluorescence_trace_exceptions2(monkeypatch, tmpdir): """ Test that BehaviorOphysLimsApi.get_corrected_fluorescence_traces raises exceptions when the trace file and cell_specimen_table have different ROI IDs Check case where fluorescence traces have an ROI that the cell_specimen_table does not """ out_fname = os.path.join(tmpdir, "dummy_ftrace_data_exc2.h5") rng = np.random.RandomState(1234) n_t = 100 data = rng.random_sample((5, n_t)) roi_names = np.array([1, 5, 3, 4, 2]) with h5py.File(out_fname, "w") as out_file: out_file.create_dataset("data", data=data) out_file.create_dataset("roi_names", data=roi_names.astype(bytes)) cell_data = {"junk": [6, 7, 8, 9, 10, 11], "cell_roi_id": [b"1", b"2", b"3", b"4", b"5", b"6"]} cell_table = pd.DataFrame(data=cell_data, index=pd.Index([10, 20, 30, 40, 50, 60], name="cell_specimen_id")) def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id self.get_behavior_session_id = 2 with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_demix_file", lambda *args: out_fname) patched_extractor = BehaviorOphysLimsExtractor(123) ctx.setattr(BehaviorOphysLimsApi, "get_ophys_timestamps", lambda *args: np.zeros(n_t)) ctx.setattr(BehaviorOphysLimsApi, "get_cell_specimen_table", lambda *args: cell_table) api = BehaviorOphysLimsApi(extractor=patched_extractor) with pytest.raises(RuntimeError): _ = api.get_corrected_fluorescence_traces()
def test_dff_trace_order(monkeypatch, tmpdir): """ Test that BehaviorOphysLimsApi.get_raw_dff_data can reorder ROIs to align with what is in the cell_specimen_table """ out_fname = os.path.join(tmpdir, "dummy_dff_data.h5") rng = np.random.RandomState(1234) n_t = 100 data = rng.random_sample((5, n_t)) roi_names = np.array([5, 3, 4, 2, 1]) with h5py.File(out_fname, "w") as out_file: out_file.create_dataset("data", data=data) out_file.create_dataset("roi_names", data=roi_names.astype(bytes)) def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id self.get_behavior_session_id = 2 with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_dff_file", lambda *args: out_fname) patched_extractor = BehaviorOphysLimsExtractor(123) ctx.setattr(BehaviorOphysLimsApi, "get_cell_roi_ids", lambda *args: np.array([1, 2, 3, 4, 5]).astype(bytes)) api = BehaviorOphysLimsApi(extractor=patched_extractor) dff_traces = api.get_raw_dff_data() # compare the returned traces with the input data # mapping to the order of the monkeypatched cell_roi_id list np.testing.assert_array_almost_equal( dff_traces[0, :], data[4, :], decimal=10) np.testing.assert_array_almost_equal( dff_traces[1, :], data[3, :], decimal=10) np.testing.assert_array_almost_equal( dff_traces[2, :], data[1, :], decimal=10) np.testing.assert_array_almost_equal( dff_traces[3, :], data[2, :], decimal=10) np.testing.assert_array_almost_equal( dff_traces[4, :], data[0, :], decimal=10)
def get_behavior_ophys_experiment( self, ophys_experiment_id: int) -> BehaviorOphysExperiment: """Returns a BehaviorOphysExperiment object that contains methods to analyze a single behavior+ophys session. :param ophys_experiment_id: id that corresponds to an ophys experiment :type ophys_experiment_id: int :rtype: BehaviorOphysExperiment """ return BehaviorOphysExperiment( BehaviorOphysLimsApi(ophys_experiment_id))
def test_get_extended_trials(ophys_experiment_id): api = BehaviorOphysLimsApi(ophys_experiment_id) df = api.get_extended_trials() ets = ExtendedTrialSchema(partial=False, many=True) data_list_cs = df.to_dict("records") data_list_cs_sc = ets.dump(data_list_cs) ets.load(data_list_cs_sc) df_fail = df.drop(["behavior_session_uuid"], axis=1) ets = ExtendedTrialSchema(partial=False, many=True) data_list_cs = df_fail.to_dict("records") data_list_cs_sc = ets.dump(data_list_cs) try: ets.load(data_list_cs_sc) raise RuntimeError("This should have failed with " "marshmallow.schema.ValidationError") except ValidationError: pass
def test_rig_geometry_newer_than_experiment(): """ This test ensures that if the experiment date_of_acquisition is before a rig activate_date that it is not returned as the rig used for the experiment """ # This experiment has rig config more recent than the # experiment date_of_acquisition ophys_experiment_id = 521405260 api = BehaviorOphysLimsApi(ophys_experiment_id) rig_geometry = api.get_eye_tracking_rig_geometry() expected = { 'camera_position_mm': [130.0, 0.0, 0.0], 'led_position': [265.1, -39.3, 1.0], 'monitor_position_mm': [170.0, 0.0, 0.0], 'camera_rotation_deg': [0.0, 0.0, 13.1], 'monitor_rotation_deg': [0.0, 0.0, 0.0], 'equipment': 'CAM2P.1' } assert rig_geometry == expected
def test_get_ophys_timestamps(monkeypatch, plane_group, ophys_timestamps, dff_traces, expected, context): """Test the acquisition frame truncation only happens for non-mesoscope data (and raises error for scientifica data with longer trace frames than acquisition frames (ophys_timestamps)).""" monkeypatch.setattr(BehaviorOphysLimsApi, "get_behavior_session_id", lambda x: 123) monkeypatch.setattr(BehaviorOphysLimsApi, "_get_ids", lambda x: {}) api = BehaviorOphysLimsApi(123) # Mocking any db calls monkeypatch.setattr(api, "get_sync_data", lambda: {"ophys_frames": ophys_timestamps}) monkeypatch.setattr(api, "get_raw_dff_data", lambda: dff_traces) monkeypatch.setattr(api, "get_imaging_plane_group", lambda: plane_group) monkeypatch.setattr(api, "get_plane_group_count", lambda: 2) with context: actual = api.get_ophys_timestamps() if expected is not None: np.testing.assert_array_equal(expected, actual)
def test_legacy_dff_api(): ophys_experiment_id = 792813858 api = BehaviorOphysLimsApi(ophys_experiment_id) session = BehaviorOphysSession(api) _, dff_array = session.get_dff_traces() for csid in session.dff_traces.index.values: dff_trace = session.dff_traces.loc[csid]['dff'] ind = session.get_cell_specimen_indices([csid])[0] np.testing.assert_array_almost_equal(dff_trace, dff_array[ind, :]) assert dff_array.shape[0] == session.dff_traces.shape[0]
def test_trial_response_window_bounds_reward(ophys_experiment_id): api = BehaviorOphysLimsApi(ophys_experiment_id) session = BehaviorOphysSession(api) response_window = session.task_parameters['response_window_sec'] for _, row in session.trials.iterrows(): lick_times = [(t - row.change_time) for t in row.lick_times] if not np.isnan(row.reward_time): # monitor delay is incorporated into the trials table change time # TODO: where is this set in the session object? camstim_change_time = row.change_time - 0.0351 reward_time = (row.reward_time - camstim_change_time) assert response_window[0] < reward_time + 1 / 60 assert reward_time < response_window[1] + 1 / 60 if len(session.licks) > 0: assert lick_times[0] < reward_time
def write_behavior_ophys_nwb(session_data: dict, nwb_filepath: str, skip_eye_tracking: bool): nwb_filepath_inprogress = nwb_filepath + '.inprogress' nwb_filepath_error = nwb_filepath + '.error' # Clean out files from previous runs: for filename in [ nwb_filepath_inprogress, nwb_filepath_error, nwb_filepath ]: if os.path.exists(filename): os.remove(filename) try: json_api = BehaviorOphysJsonApi(data=session_data, skip_eye_tracking=skip_eye_tracking) json_session = BehaviorOphysExperiment(api=json_api) lims_api = BehaviorOphysLimsApi( ophys_experiment_id=session_data['ophys_experiment_id'], skip_eye_tracking=skip_eye_tracking) lims_session = BehaviorOphysExperiment(api=lims_api) logging.info("Comparing a BehaviorOphysExperiment created from JSON " "with a BehaviorOphysExperiment created from LIMS") assert sessions_are_equal(json_session, lims_session, reraise=True) BehaviorOphysNwbApi(nwb_filepath_inprogress).save(json_session) logging.info("Comparing a BehaviorOphysExperiment created from JSON " "with a BehaviorOphysExperiment created from NWB") nwb_api = BehaviorOphysNwbApi(nwb_filepath_inprogress) nwb_session = BehaviorOphysExperiment(api=nwb_api) assert sessions_are_equal(json_session, nwb_session, reraise=True) os.rename(nwb_filepath_inprogress, nwb_filepath) return {'output_path': nwb_filepath} except Exception as e: if os.path.isfile(nwb_filepath_inprogress): os.rename(nwb_filepath_inprogress, nwb_filepath_error) raise e
def validate_last_trial_ends_adjacent_to_flash(ophys_experiment_id, api=None, verbose=False): # ensure that the last trial ends sometime on the last flash/blank cycle # i.e, if this is the last flash/blank iteration (high = stimulus present): # ------- ------- ------- ------- # | | | | | | | | # --------- --------- --------- --------- ------------------------------------------- # ^ ^ # The last trial has to have ended somewhere between the two carrots where: # the first carrot represents the time of the last recorded stimulus flash # the second carrot represents the time at which another flash should have started, after accounting for the possibility of the session ending on an omitted flash api = BehaviorOphysLimsApi() if api is None else api session = BehaviorOphysExperiment(api) # get the flash/blank parameters max_flash_duration = session.stimulus_presentations['duration'].max() max_blank_duration = session.task_parameters['blank_duration_sec'][1] # count number of omitted flashes at the very end of the session N_final_omitted_flashes = session.stimulus_presentations.index.max() - session.stimulus_presentations.query('omitted == False').index.max() # get the start/end time of the last valid (non-omitted) flash last_flash_start = session.stimulus_presentations.query('omitted == False')['start_time'].iloc[-1] last_flash_end = session.stimulus_presentations.query('omitted == False')['stop_time'].iloc[-1] # calculate when the next stimulus should have flashed, after accounting for any omitted flashes at the end of the session next_flash_would_have_started = last_flash_end + max_blank_duration + N_final_omitted_flashes*(max_flash_duration + max_blank_duration) # get the end time of the last trial last_trial_end = session.trials.iloc[-1]['stop_time'] if verbose: print('last flash ended at {}'.format(last_flash_end)) print('another flash should have started by {}'.format(next_flash_would_have_started)) print('last trial ended at {}'.format(last_trial_end)) if not last_flash_start <= last_trial_end <= next_flash_would_have_started: raise ValidationError('The last trial does not end between the start of the last flash and the expected start time of the next flash')
def test_dff_trace_exceptions(monkeypatch, tmpdir): """ Test that BehaviorOphysLimsApi.get_raw_dff_data() raises exceptions when dff trace file and cell_specimen_table contain different ROI IDs """ # check that an exception is raised if dff_traces has an ROI ID # that cell_specimen_table does not out_fname = os.path.join(tmpdir, "dummy_dff_data_for_exceptions.h5") rng = np.random.RandomState(1234) n_t = 100 data = rng.random_sample((5, n_t)) roi_names = np.array([5, 3, 4, 2, 1]) with h5py.File(out_fname, "w") as out_file: out_file.create_dataset("data", data=data) out_file.create_dataset("roi_names", data=roi_names.astype(bytes)) def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id self.get_behavior_session_id = 2 with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_dff_file", lambda *args: out_fname) patched_extractor = BehaviorOphysLimsExtractor(123) ctx.setattr(BehaviorOphysLimsApi, "get_cell_roi_ids", lambda *args: np.array([1, 3, 4, 5]).astype(bytes)) api = BehaviorOphysLimsApi(extractor=patched_extractor) with pytest.raises(RuntimeError): _ = api.get_raw_dff_data() # check that an exception is raised if the cell_specimen_table # has an ROI ID that dff_traces does not out_fname = os.path.join(tmpdir, "dummy_dff_data_for_exceptions2.h5") rng = np.random.RandomState(1234) n_t = 100 data = rng.random_sample((5, n_t)) roi_names = np.array([5, 3, 4, 2, 1]) with h5py.File(out_fname, "w") as out_file: out_file.create_dataset("data", data=data) out_file.create_dataset("roi_names", data=roi_names.astype(bytes)) def dummy_init(self, ophys_experiment_id, **kwargs): self.ophys_experiment_id = ophys_experiment_id self.get_behavior_session_id = 2 with monkeypatch.context() as ctx: ctx.setattr(BehaviorOphysLimsExtractor, "__init__", dummy_init) ctx.setattr(BehaviorOphysLimsExtractor, "get_dff_file", lambda *args: out_fname) patched_extractor = BehaviorOphysLimsExtractor(123) ctx.setattr(BehaviorOphysLimsApi, "get_cell_roi_ids", lambda *args: np.array([1, 2, 3, 4, 5, 6]).astype(bytes)) api = BehaviorOphysLimsApi(extractor=patched_extractor) with pytest.raises(RuntimeError): _ = api.get_raw_dff_data()
next_flash_would_have_started = last_flash_end + max_blank_duration + N_final_omitted_flashes*(max_flash_duration + max_blank_duration) # get the end time of the last trial last_trial_end = session.trials.iloc[-1]['stop_time'] if verbose: print('last flash ended at {}'.format(last_flash_end)) print('another flash should have started by {}'.format(next_flash_would_have_started)) print('last trial ended at {}'.format(last_trial_end)) if not last_flash_start <= last_trial_end <= next_flash_would_have_started: raise ValidationError('The last trial does not end between the start of the last flash and the expected start time of the next flash') if __name__ == "__main__": api = BehaviorOphysLimsApi() ophys_experiment_id_list = [775614751, 778644591, 787461073, 782675436, 783928214, 783927872, 787501821, 787498309, 788490510, 788488596, 788489531, 789359614, 790149413, 790709081, 791119849, 791453282, 791980891, 792813858, 792812544, 792816531, 792815735, 794381992, 794378505, 795076128, 795073741, 795952471, 795952488, 795953296, 795948257, 796106850, 796106321, 796108483, 796105823, 796308505, 797255551, 795075034, 798403387, 798404219, 799366517, 799368904, 799368262, 803736273, 805100431, 805784331, 805784313, 806456687, 806455766, 806989729, 807753318, 807752719, 807753334, 807753920, 796105304, 784482326, 779335436, 782675457, 791974731, 791979236, 800034837, 802649986, 806990245, 808621958, 808619526, 808619543, 808621034, 808621015] for ophys_experiment_id in ophys_experiment_id_list: validation_functions_to_run = [
def setup_class(cls): cls.bd = BehaviorLimsApi(976012750) cls.od = BehaviorOphysLimsApi(976255949)
def test_process_ophys_plane_timestamps( timestamps, plane_group, group_count, expected): actual = BehaviorOphysLimsApi._process_ophys_plane_timestamps( timestamps, plane_group, group_count) np.testing.assert_array_equal(expected, actual)
def test_get_cell_roi_table(ophys_experiment_id): api = BehaviorOphysLimsApi(ophys_experiment_id) assert len(api.get_cell_specimen_table()) == 128