Skip to content

Commit 8f9d28d

Browse files
authored
Merge pull request #294 from AllenInstitute/fix/running
missing intervalsms workaround
2 parents fbadcc9 + aafb611 commit 8f9d28d

File tree

10 files changed

+129
-9
lines changed

10 files changed

+129
-9
lines changed

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.4.2
2+
current_version = 0.4.3
33
commit = True
44
tag = False
55
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<n>\d+))?

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.4.2",
7+
version="0.4.3",
88
author="Justin Kiggins",
99
author_email="[email protected]",
1010
description="analysis package for visual behavior",

tests/validation/test_core.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,48 @@
22
import pandas as pd
33
from visual_behavior.validation.core import *
44

5+
def test_parse_log():
6+
7+
EXPECTED = dict(
8+
levelname="ERROR",
9+
name="package.module",
10+
message="This is the error"
11+
)
12+
13+
parsed = parse_log("{levelname}::{name}::{message}".format(**EXPECTED))
14+
assert parsed == EXPECTED
15+
16+
def test_count_read_errors():
17+
18+
EMPTY = dict(
19+
log=[]
20+
)
21+
results = count_read_errors(EMPTY)
22+
print(results)
23+
assert 'ERROR' not in results
24+
25+
INFO = dict(
26+
log=["INFO::package.module::informative message"]
27+
)
28+
29+
results = count_read_errors(INFO)
30+
print(results)
31+
assert 'ERROR' not in results
32+
assert results['INFO']==1
33+
34+
def test_validate_no_read_errors():
35+
36+
WITH_ERRORS = dict(
37+
log=["ERROR::package.module::error message"]
38+
)
39+
40+
WITHOUT_ERRORS = dict(
41+
log=["INFO::package.module::informative message"]
42+
)
43+
44+
assert validate_no_read_errors(WITH_ERRORS)==False
45+
assert validate_no_read_errors(WITHOUT_ERRORS)==True
46+
547

648
def test_validate_running_data():
749
# good data: length matches time and not all values the same

visual_behavior/__init__.py

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

visual_behavior/translator/foraging/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import numpy as np
44
from scipy.signal import medfilt
55
from .extract import get_end_time
6-
from ...utilities import calc_deriv, rad_to_dist, local_time
6+
from ...utilities import calc_deriv, rad_to_dist, local_time, ListHandler, DoubleColonFormatter
7+
8+
import logging
9+
10+
logger = logging.getLogger(__name__)
711

812
warnings.warn(
913
"support for the loading stimulus_code outputs will be deprecated in a future version",
@@ -29,10 +33,20 @@ def data_to_change_detection_core(data, time=None):
2933
- currently doesn't require or check that the `task` field in the
3034
experiment data is "DoC" (Detection of Change)
3135
"""
36+
37+
log_messages = []
38+
handler = ListHandler(log_messages)
39+
handler.setFormatter(
40+
DoubleColonFormatter
41+
)
42+
logger.addHandler(
43+
handler
44+
)
45+
3246
if time is None:
3347
time = load_time(data)
3448

35-
return {
49+
core_data = {
3650
"time": time,
3751
"metadata": load_metadata(data),
3852
"licks": load_licks(data, time=time),
@@ -42,6 +56,9 @@ def data_to_change_detection_core(data, time=None):
4256
"visual_stimuli": load_visual_stimuli(data, time=time),
4357
}
4458

59+
core_data['log'] = log_messages
60+
return core_data
61+
4562

4663
def load_metadata(data):
4764
fields = (

visual_behavior/translator/foraging2/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pandas as pd
2-
from ...utilities import local_time
2+
from ...utilities import local_time, ListHandler, DoubleColonFormatter
33

44
from ...devices import get_rig_id
55
from .extract import get_trial_log, get_stimuli, get_pre_change_time, \
@@ -18,6 +18,10 @@
1818

1919
from .extract_stimuli import get_visual_stimuli
2020

21+
import logging
22+
23+
logger = logging.getLogger(__name__)
24+
2125

2226
def data_to_change_detection_core(data):
2327
"""Core data structure to be used across all analysis code?
@@ -37,7 +41,18 @@ def data_to_change_detection_core(data):
3741
- currently doesn't require or check that the `task` field in the
3842
experiment data is "DoC" (Detection of Change)
3943
"""
40-
return {
44+
45+
log_messages = []
46+
handler = ListHandler(log_messages)
47+
handler.setFormatter(
48+
DoubleColonFormatter
49+
)
50+
51+
logger.addHandler(
52+
handler
53+
)
54+
55+
core_data = {
4156
"metadata": data_to_metadata(data),
4257
"time": data_to_time(data),
4358
"licks": data_to_licks(data),
@@ -47,6 +62,10 @@ def data_to_change_detection_core(data):
4762
"visual_stimuli": data_to_visual_stimuli(data),
4863
}
4964

65+
core_data['log'] = log_messages
66+
67+
return core_data
68+
5069

5170
def expand_dict(out_dict, from_dict, index):
5271
"""there is obviously a better way...

visual_behavior/translator/foraging2/extract.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,9 @@ def get_running_speed(exp_data, smooth=False, time=None):
709709
dx = medfilt(dx, kernel_size=5) # remove big, single frame spikes in encoder values
710710
dx = np.cumsum(dx) # wheel rotations
711711

712-
if len(time) != len(dx):
713-
raise ValueError("dx and time must be the same length")
712+
if len(time) < len(dx):
713+
logger.error('intervalsms record appears to be missing entries')
714+
dx = dx[:len(time)]
714715

715716
speed = calc_deriv(dx, time)
716717
speed = rad_to_dist(speed)

visual_behavior/utilities.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import print_function
22
from dateutil import tz
3+
import logging
34
import numpy as np
45
import pandas as pd
56
from scipy.stats import norm
@@ -159,3 +160,19 @@ def local_time(iso_timestamp, timezone=None):
159160
if not datetime.tzinfo:
160161
datetime = datetime.replace(tzinfo=tz.gettz('America/Los_Angeles'))
161162
return datetime.isoformat()
163+
164+
165+
class ListHandler(logging.Handler):
166+
"""docstring for ListHandler."""
167+
def __init__(self, log_list):
168+
super(ListHandler, self).__init__()
169+
self.log_list = log_list
170+
171+
def emit(self, record):
172+
entry = self.format(record)
173+
self.log_list.append(entry)
174+
175+
176+
DoubleColonFormatter = logging.Formatter(
177+
"%(levelname)s::%(name)s::%(message)s",
178+
)

visual_behavior/validation/core.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@
33
from .extended_trials import get_first_lick_relative_to_scheduled_change
44

55

6+
def parse_log(log_record):
7+
levelname, name, message = log_record.split('::')
8+
return dict(
9+
levelname=levelname,
10+
name=name,
11+
message=message,
12+
)
13+
14+
15+
def count_read_errors(core_data):
16+
log = [parse_log(log_record) for log_record in core_data['log']]
17+
log = pd.DataFrame(log, columns=['levelname', 'name', 'message'])
18+
return log.groupby('levelname').size().to_dict()
19+
20+
21+
def validate_no_read_errors(core_data):
22+
error_counts = count_read_errors(core_data)
23+
24+
n_errors = error_counts.get('ERROR', 0) + error_counts.get('CRITICAL', 0)
25+
26+
return (n_errors == 0)
27+
28+
629
def validate_running_data(core_data):
730
'''
831
for each sampling frame, the value of the encoder should be known

visual_behavior/validation/qc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def define_validation_functions(core_data):
9494
cd.validate_licks: (core_data,), # this one doesn't take trials
9595
cd.validate_minimal_dropped_frames: (core_data,), # this one doesn't take trials
9696
# f2.validate_frame_intervals_exists:(data), # this one doesn't take trials
97+
cd.validate_no_read_errors: (core_data,),
9798
}
9899

99100
return validation_functions

0 commit comments

Comments
 (0)