Skip to content

Commit b99a537

Browse files
authored
Merge pull request #245 from AllenInstitute/fix/legacy_metadata
legacy file support
2 parents c223f81 + 3fb93b8 commit b99a537

12 files changed

Lines changed: 185 additions & 59 deletions

File tree

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.3.0
2+
current_version = 0.4.0
33
commit = True
44
tag = True
55

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
setuptools.setup(
66
name="visual-behavior",
7-
version="0.3.0",
7+
version="0.4.0",
88
author="Justin Kiggins",
99
author_email="justink@alleninstitute.org",
1010
description="analysis package for visual behavior",

tests/conftest.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,23 +181,6 @@ def exemplar_extended_trials_fixture():
181181
@pytest.fixture(scope="module")
182182
def trials_df_fixture():
183183
trials = pd.read_pickle(os.path.join(TESTING_RES_DIR, "trials.pkl"))
184-
trials["scheduled_change_time"] = trials.apply(
185-
lambda row: row["scheduled_change_time"] - row["starttime"],
186-
axis=1
187-
) # change scheduled_change_time from time relative to experiment start to time relative to trial start
188-
del trials['stim_on_frames']
189-
del trials['publish_time']
190-
trials['endframe'] = trials['startframe'].shift(periods=-1)
191-
trials.at[trials.index[-1],'endframe']=94157
192-
193-
def nan_to_empty_string(val):
194-
if pd.isnull(val):
195-
return ''
196-
return val
197-
trials["change_image_category"] = trials["change_image_category"].apply(nan_to_empty_string) # use empty string instead of NoneType
198-
trials["change_image_name"] = trials["change_image_name"].apply(nan_to_empty_string) # use empty string instead of NoneType
199-
trials["initial_image_category"] = trials["initial_image_category"].apply(nan_to_empty_string) # use empty string instead of NoneType
200-
trials["initial_image_name"] = trials["initial_image_name"].apply(nan_to_empty_string) # use empty string instead of NoneType
201184
return trials
202185

203186

tests/res/trials.pkl

-942 KB
Binary file not shown.

tests/translator/foraging/test_foraging_translator.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def test_load_time(behavioral_session_output_fixture):
2626

2727
def test_load_trials(behavioral_session_output_fixture, trials_df_fixture):
2828
trials = foraging.load_trials(behavioral_session_output_fixture)
29+
30+
trials_df_fixture['change_frame'] += 1
31+
2932
pd.testing.assert_frame_equal(
3033
trials,
3134
trials_df_fixture,
@@ -96,6 +99,13 @@ def test_load_running_speed(behavioral_session_output_fixture):
9699
# 3: -100.3045892838318,
97100
# 4: -218.06477955362016
98101
# },
102+
'frame': {
103+
0: 0,
104+
1: 1,
105+
2: 2,
106+
3: 3,
107+
4: 4
108+
},
99109
'speed': {
100110
0: 0.0,
101111
1: 0.14556417360329282,
@@ -111,10 +121,6 @@ def test_load_running_speed(behavioral_session_output_fixture):
111121
4: 0.2000794094055891
112122
}
113123
},
114-
columns=[
115-
u'speed',
116-
u'time',
117-
]
118124
)
119125

120126
pd.testing.assert_frame_equal(

tests/translator/foraging2/test_foraging2_extract.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,11 @@ def test_get_licks(monkeypatch, foraging2_data_fixture):
283283
(
284284
{},
285285
pd.DataFrame(data={
286-
"time": np.array([0.0, 0.016, 0.032, 0.048, 0.064, ]),
287-
"speed (cm/s)": np.array([0, 0, 0, 0, 0, ]),
288-
"acceleration (cm/s^2)": np.array([0, 0, 0, 0, 0, ]),
289-
"jerk (cm/s^3)": np.array([0, 0, 0, 0, 0, ]),
286+
"time": [0.0, 0.016, 0.032, 0.048, 0.064, ],
287+
"speed": [0, 0, 0, 0, 0, ],
288+
"frame": [0, 1, 2, 3, 4, ]
289+
# "acceleration (cm/s^2)": np.array([0, 0, 0, 0, 0, ]),
290+
# "jerk (cm/s^3)": np.array([0, 0, 0, 0, 0, ]),
290291
}),
291292
),
292293
])
Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,70 @@
1-
from visual_behavior.translator import schemas
1+
2+
from visual_behavior.schemas.core import MetadataSchema, StimulusSchema, \
3+
RunningSchema, LickSchema, RewardSchema, TrialSchema
4+
from visual_behavior.schemas.extended_trials import ExtendedTrialSchema
5+
from visual_behavior.translator import foraging2, foraging
6+
from visual_behavior.translator.core import create_extended_dataframe
7+
from visual_behavior.uuid_utils import create_session_uuid
28

39

410
"""test the schemas vs the outputs here
511
"""
12+
13+
def _test_core_data_schemas(core_data):
14+
15+
# metadata
16+
17+
# core dataframes
18+
dataframe_schemas = (
19+
(StimulusSchema, core_data['visual_stimuli']),
20+
(RunningSchema, core_data['running']),
21+
(LickSchema, core_data['licks']),
22+
(RewardSchema, core_data['rewards']),
23+
(TrialSchema, core_data['trials']),
24+
)
25+
26+
for Schema, data in dataframe_schemas:
27+
errors = Schema(many=True).validate(data.to_dict(orient='records'))
28+
29+
for row, row_errors in errors.items():
30+
assert len(row_errors)==0, (Schema, data, row_errors)
31+
32+
extended_trials = create_extended_dataframe(
33+
trials=core_data['trials'],
34+
metadata=core_data['metadata'],
35+
licks=core_data['licks'],
36+
time=core_data['time'],
37+
)
38+
39+
errors = ExtendedTrialSchema(many=True).validate(
40+
extended_trials.to_dict(orient='records')
41+
)
42+
43+
for row, row_errors in errors.items():
44+
assert len(row_errors)==0, row_errors
45+
46+
errors = MetadataSchema().validate(core_data['metadata'])
47+
assert len(errors)==0, errors.keys()
48+
49+
50+
def test_foraging2_translator_schema(foraging2_data_stage4_2018_05_10):
51+
core_data = foraging2.data_to_change_detection_core(
52+
foraging2_data_stage4_2018_05_10
53+
)
54+
core_data['metadata']['behavior_session_uuid'] = create_session_uuid(
55+
core_data['metadata']['mouseid'],
56+
core_data['metadata']['startdatetime'],
57+
)
58+
_test_core_data_schemas(core_data)
59+
60+
def test_foraging_translator_schema(behavioral_session_output_fixture):
61+
core_data = foraging.data_to_change_detection_core(
62+
behavioral_session_output_fixture
63+
)
64+
65+
core_data['metadata']['behavior_session_uuid'] = create_session_uuid(
66+
str(core_data['metadata']['mouseid']),
67+
core_data['metadata']['startdatetime'],
68+
)
69+
70+
_test_core_data_schemas(core_data)

visual_behavior/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.0"
1+
__version__ = "0.4.0"

visual_behavior/schemas/core.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ class RewardSchema(TimeSeriesSchema):
2020
""" schema for water reward presentations
2121
2222
"""
23-
volume = fields.Float(
24-
description='Volume of water dispensed on this reward presentation in mL',
25-
required=True,
26-
)
23+
# volume = fields.Float(
24+
# description='Volume of water dispensed on this reward presentation in mL',
25+
# required=True,
26+
# )
2727
# lickspout = fields.Int(
2828
# description='The water line on which this reward was dispensed',
2929
# required=True,
@@ -208,13 +208,13 @@ class StimulusSchema(TimeSeriesSchema):
208208
required=True,
209209
allow_none=True,
210210
)
211-
contrast = fields.Float(
212-
description='The contrast of a grating stimulus',
211+
orientation = fields.Float(
212+
description='The orientation of a grating stimulus',
213213
required=True,
214214
allow_none=True,
215215
)
216-
orientation = fields.Float(
217-
description='The orientation of a grating stimulus',
216+
end_frame = fields.Int(
217+
description='The last frame of this stimulus, non-inclusive',
218218
required=True,
219219
)
220220

@@ -298,6 +298,54 @@ class MetadataSchema(Schema):
298298
description='total number of stimulus frames',
299299
required=True,
300300
)
301+
auto_reward_vol = fields.Float(
302+
description='volume provided during autoreward trials',
303+
required=True,
304+
)
305+
max_session_duration = fields.Float(
306+
description='maximum duration in minutes of a session',
307+
required=True,
308+
)
309+
min_no_lick_time = fields.Float(
310+
description='minimum time where there should be no licks before the start of a trial',
311+
required=True,
312+
)
313+
free_reward_trials = fields.Int(
314+
description='number of free reward trials to start the session',
315+
required=True,
316+
)
317+
abort_on_early_response = fields.Bool(
318+
description='if True, abort trials on early responses',
319+
required=True,
320+
)
321+
even_sampling_enabled = fields.Bool(
322+
description='if True, images should be sample evenly from the change matrix',
323+
required=True,
324+
)
325+
failure_repeats = fields.Int(
326+
description='maximum number of times to repeat parameters after a false alarm',
327+
required=True,
328+
)
329+
initial_blank_duration = fields.Float(
330+
description='duration of grey screen at start of each trial, in seconds',
331+
required=True,
332+
)
333+
catch_frequency = fields.Float(
334+
description='fraction of trials that should be catch trials',
335+
required=True,
336+
)
337+
warm_up_trials = fields.Int(
338+
description='number of warm up trials at start of session',
339+
required=True,
340+
)
341+
stimulus_window = fields.Float(
342+
description='start and stop times of stimulus window',
343+
required=True,
344+
)
345+
volume_limit = fields.Float(
346+
description='maximum volume of water to deliver in a session, in mL',
347+
required=True,
348+
)
301349

302350

303351
# class ChangeDetectionSessionCoreSchema(Schema):

visual_behavior/translator/foraging/__init__.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pandas as pd
33
import numpy as np
44
from scipy.signal import medfilt
5+
from .extract import get_end_time
56
from ...utilities import calc_deriv, rad_to_dist, local_time
67

78
warnings.warn(
@@ -81,6 +82,25 @@ def load_metadata(data):
8182
timezone='America/Los_Angeles',
8283
)
8384

85+
metadata['auto_reward_vol'] = 0.05 # hard coded
86+
metadata['max_session_duration'] = 60.0 # hard coded
87+
metadata['min_no_lick_time'] = data['minimum_no_lick_time']
88+
metadata['abort_on_early_response'] = data['ignore_false_alarms'] == False
89+
metadata['even_sampling_enabled'] = data['image_category_sampling_mode'] == 'even_sampling'
90+
metadata['failure_repeats'] = data['max_number_trial_repeats']
91+
metadata['catch_frequency'] = data['catch_frequency']
92+
metadata['volume_limit'] = data['volumelimit']
93+
metadata['initial_blank_duration'] = data['initial_blank']
94+
metadata['warm_up_trials'] = data['warmup_trials']
95+
metadata['stimulus_window'] = data['trial_duration'] - data['delta_minimum']
96+
97+
block_length = data['lick_detect_training_block_length']
98+
99+
try:
100+
metadata['free_reward_trials'] = block_length[0]
101+
except TypeError:
102+
metadata['free_reward_trials'] = block_length
103+
84104
return metadata
85105

86106

@@ -97,6 +117,9 @@ def load_trials(data, time=None):
97117
98118
"""
99119

120+
if time is None:
121+
time = load_time(data)
122+
100123
columns = (
101124
'auto_rewarded',
102125
'change_contrast',
@@ -164,7 +187,8 @@ def stringify(x):
164187
for col in forced_string:
165188
trials[col] = trials[col].map(stringify)
166189

167-
trials['change_frame'] = trials['change_frame'].map(lambda x: int(x) if np.isfinite(x) else None)
190+
trials['change_frame'] = trials['change_frame'].map(lambda x: int(x) if np.isfinite(x) else None) + 1
191+
168192
trials["change_image_category"] = trials["change_image_category"].apply(lambda x: x if x else '') # use empty string instead of NoneType
169193
trials["change_image_name"] = trials["change_image_name"].apply(lambda x: x if x else '') # use empty string instead of NoneType
170194
trials["initial_image_category"] = trials["initial_image_category"].apply(lambda x: x if x else '') # use empty string instead of NoneType
@@ -175,7 +199,10 @@ def stringify(x):
175199

176200
# add endframe column as startframe of last frame. Last endframe is last frame of session
177201
trials['endframe'] = trials['startframe'].shift(periods=-1)
178-
trials.at[trials.index[-1], 'endframe'] = len(load_time(data)) - 1
202+
trials.at[trials.index[-1], 'endframe'] = len(time) - 1
203+
204+
trials['endtime'] = get_end_time(trials, time)
205+
trials['trial_length'] = trials['endtime'] - trials['starttime']
179206

180207
return trials
181208

@@ -272,6 +299,7 @@ def load_running_speed(data, smooth=False, time=None):
272299

273300
running_speed = pd.DataFrame({
274301
'time': time,
302+
'frame': range(len(time)),
275303
'speed': speed,
276304
# 'acceleration (cm/s^2)': accel,
277305
# 'jerk (cm/s^3)': jerk,
@@ -329,15 +357,18 @@ def load_visual_stimuli(data, time=None):
329357
stimdf = pd.DataFrame(data['stimuluslog'])
330358

331359
stimdf = find_ends(stimdf)
332-
stimdf['end'] = stimdf['frames_to_end'] + stimdf['frame']
360+
stimdf['end_frame'] = stimdf['frames_to_end'] + stimdf['frame']
361+
362+
stimdf['frame'] += 1
363+
stimdf['end_frame'] += 1
333364

334365
def find_time(fr):
335366
try:
336367
return time[fr]
337368
except IndexError:
338369
return np.nan
339370

340-
stimdf['end_time'] = stimdf['end'].map(find_time)
371+
stimdf['end_time'] = stimdf['end_frame'].map(find_time)
341372
stimdf['duration'] = stimdf['end_time'] - stimdf['time']
342373

343374
onset_mask = (
@@ -348,13 +379,13 @@ def find_time(fr):
348379
) > 0
349380

350381
if pd.isnull(stimdf['image_name']).any() == False: # 'image_category' in stimdf.columns:
351-
cols = ['frame', 'time', 'duration', 'image_category', 'image_name']
382+
cols = ['frame', 'end_frame', 'time', 'duration', 'image_category', 'image_name']
352383
stimuli = stimdf[onset_mask][cols]
353384
stimuli['orientation'] = None
354385
stimuli['contrast'] = None
355386

356387
elif 'ori' in stimdf.columns:
357-
cols = ['frame', 'time', 'duration', 'ori', 'contrast']
388+
cols = ['frame', 'end_frame', 'time', 'duration', 'ori', 'contrast']
358389
stimuli = stimdf[onset_mask][cols]
359390
stimuli.rename(columns={'ori': 'orientation'}, inplace=True)
360391
stimuli['image_category'] = None

0 commit comments

Comments
 (0)