From 55f83af7f0ab04858ce6793fc599cfcfe9442275 Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Fri, 20 Jul 2018 12:27:48 -0700 Subject: [PATCH 1/3] works around missing intervalsms issue --- tests/validation/test_core.py | 42 +++++++++++++++++++ .../translator/foraging/__init__.py | 21 +++++++++- .../translator/foraging2/__init__.py | 23 +++++++++- .../translator/foraging2/extract.py | 5 ++- visual_behavior/utilities.py | 17 ++++++++ visual_behavior/validation/core.py | 23 ++++++++++ visual_behavior/validation/qc.py | 1 + 7 files changed, 126 insertions(+), 6 deletions(-) diff --git a/tests/validation/test_core.py b/tests/validation/test_core.py index ef1935839..66b14dd86 100644 --- a/tests/validation/test_core.py +++ b/tests/validation/test_core.py @@ -2,6 +2,48 @@ import pandas as pd from visual_behavior.validation.core import * +def test_parse_log(): + + EXPECTED = dict( + levelname="ERROR", + name="package.module", + message="This is the error" + ) + + parsed = parse_log("{levelname}::{name}::{message}".format(**EXPECTED)) + assert parsed == EXPECTED + +def test_count_read_errors(): + + EMPTY = dict( + log=[] + ) + results = count_read_errors(EMPTY) + print(results) + assert 'ERROR' not in results + + INFO = dict( + log=["INFO::package.module::informative message"] + ) + + results = count_read_errors(INFO) + print(results) + assert 'ERROR' not in results + assert results['INFO']==1 + +def test_validate_no_read_errors(): + + WITH_ERRORS = dict( + log=["ERROR::package.module::error message"] + ) + + WITHOUT_ERRORS = dict( + log=["INFO::package.module::informative message"] + ) + + assert validate_no_read_errors(WITH_ERRORS)==False + assert validate_no_read_errors(WITHOUT_ERRORS)==True + def test_validate_running_data(): # good data: length matches time and not all values the same diff --git a/visual_behavior/translator/foraging/__init__.py b/visual_behavior/translator/foraging/__init__.py index f39573986..25d6db473 100644 --- a/visual_behavior/translator/foraging/__init__.py +++ b/visual_behavior/translator/foraging/__init__.py @@ -3,7 +3,11 @@ import numpy as np from scipy.signal import medfilt from .extract import get_end_time -from ...utilities import calc_deriv, rad_to_dist, local_time +from ...utilities import calc_deriv, rad_to_dist, local_time, ListHandler, DoubleColonFormatter + +import logging + +logger = logging.getLogger(__name__) warnings.warn( "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): - currently doesn't require or check that the `task` field in the experiment data is "DoC" (Detection of Change) """ + + log_messages = [] + handler = ListHandler(log_messages) + handler.setFormatter( + DoubleColonFormatter + ) + logger.addHandler( + handler + ) + if time is None: time = load_time(data) - return { + core_data = { "time": time, "metadata": load_metadata(data), "licks": load_licks(data, time=time), @@ -42,6 +56,9 @@ def data_to_change_detection_core(data, time=None): "visual_stimuli": load_visual_stimuli(data, time=time), } + core_data['log'] = log_messages + return core_data + def load_metadata(data): fields = ( diff --git a/visual_behavior/translator/foraging2/__init__.py b/visual_behavior/translator/foraging2/__init__.py index fd7f42987..27e511d77 100644 --- a/visual_behavior/translator/foraging2/__init__.py +++ b/visual_behavior/translator/foraging2/__init__.py @@ -1,5 +1,5 @@ import pandas as pd -from ...utilities import local_time +from ...utilities import local_time, ListHandler, DoubleColonFormatter from ...devices import get_rig_id from .extract import get_trial_log, get_stimuli, get_pre_change_time, \ @@ -18,6 +18,10 @@ from .extract_stimuli import get_visual_stimuli +import logging + +logger = logging.getLogger(__name__) + def data_to_change_detection_core(data): """Core data structure to be used across all analysis code? @@ -37,7 +41,18 @@ def data_to_change_detection_core(data): - currently doesn't require or check that the `task` field in the experiment data is "DoC" (Detection of Change) """ - return { + + log_messages = [] + handler = ListHandler(log_messages) + handler.setFormatter( + DoubleColonFormatter + ) + + logger.addHandler( + handler + ) + + core_data = { "metadata": data_to_metadata(data), "time": data_to_time(data), "licks": data_to_licks(data), @@ -47,6 +62,10 @@ def data_to_change_detection_core(data): "visual_stimuli": data_to_visual_stimuli(data), } + core_data['log'] = log_messages + + return core_data + def expand_dict(out_dict, from_dict, index): """there is obviously a better way... diff --git a/visual_behavior/translator/foraging2/extract.py b/visual_behavior/translator/foraging2/extract.py index 1b4ce8e0c..144bb7376 100644 --- a/visual_behavior/translator/foraging2/extract.py +++ b/visual_behavior/translator/foraging2/extract.py @@ -709,8 +709,9 @@ def get_running_speed(exp_data, smooth=False, time=None): dx = medfilt(dx, kernel_size=5) # remove big, single frame spikes in encoder values dx = np.cumsum(dx) # wheel rotations - if len(time) != len(dx): - raise ValueError("dx and time must be the same length") + if len(time) < len(dx): + logger.error('intervalsms record appears to be missing entries') + dx = dx[:len(time)] speed = calc_deriv(dx, time) speed = rad_to_dist(speed) diff --git a/visual_behavior/utilities.py b/visual_behavior/utilities.py index ab7a5df31..80620d0fd 100644 --- a/visual_behavior/utilities.py +++ b/visual_behavior/utilities.py @@ -1,5 +1,6 @@ from __future__ import print_function from dateutil import tz +import logging import numpy as np import pandas as pd from scipy.stats import norm @@ -159,3 +160,19 @@ def local_time(iso_timestamp, timezone=None): if not datetime.tzinfo: datetime = datetime.replace(tzinfo=tz.gettz('America/Los_Angeles')) return datetime.isoformat() + + +class ListHandler(logging.Handler): + """docstring for ListHandler.""" + def __init__(self, log_list): + super(ListHandler, self).__init__() + self.log_list = log_list + + def emit(self, record): + entry = self.format(record) + self.log_list.append(entry) + + +DoubleColonFormatter = logging.Formatter( + "%(levelname)s::%(name)s::%(message)s", +) diff --git a/visual_behavior/validation/core.py b/visual_behavior/validation/core.py index 9e4edc385..4ab6bc7db 100644 --- a/visual_behavior/validation/core.py +++ b/visual_behavior/validation/core.py @@ -3,6 +3,29 @@ from .extended_trials import get_first_lick_relative_to_scheduled_change +def parse_log(log_record): + levelname, name, message = log_record.split('::') + return dict( + levelname=levelname, + name=name, + message=message, + ) + + +def count_read_errors(core_data): + log = [parse_log(log_record) for log_record in core_data['log']] + log = pd.DataFrame(log, columns=['levelname', 'name', 'message']) + return log.groupby('levelname').size().to_dict() + + +def validate_no_read_errors(core_data): + error_counts = count_read_errors(core_data) + + n_errors = error_counts.get('ERROR', 0) + error_counts.get('CRITICAL', 0) + + return (n_errors == 0) + + def validate_running_data(core_data): ''' for each sampling frame, the value of the encoder should be known diff --git a/visual_behavior/validation/qc.py b/visual_behavior/validation/qc.py index a23c0a4b5..c9ae1f1c9 100644 --- a/visual_behavior/validation/qc.py +++ b/visual_behavior/validation/qc.py @@ -94,6 +94,7 @@ def define_validation_functions(core_data): cd.validate_licks: (core_data,), # this one doesn't take trials cd.validate_minimal_dropped_frames: (core_data,), # this one doesn't take trials # f2.validate_frame_intervals_exists:(data), # this one doesn't take trials + cd.validate_no_read_errors: (core_data,), } return validation_functions From a789cd07323b2f930ce53cb691a58ff97b7ec1f0 Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Fri, 20 Jul 2018 12:29:51 -0700 Subject: [PATCH 2/3] =?UTF-8?q?Bump=20version:=200.4.2=20=E2=86=92=200.4.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- visual_behavior/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 32b798885..be43fe294 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.2 +current_version = 0.4.3 commit = True tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? diff --git a/setup.py b/setup.py index 8559d327b..3056f3fe7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setuptools.setup( name="visual-behavior", - version="0.4.2", + version="0.4.3", author="Justin Kiggins", author_email="justink@alleninstitute.org", description="analysis package for visual behavior", diff --git a/visual_behavior/__init__.py b/visual_behavior/__init__.py index df1243329..f6b7e267c 100644 --- a/visual_behavior/__init__.py +++ b/visual_behavior/__init__.py @@ -1 +1 @@ -__version__ = "0.4.2" +__version__ = "0.4.3" From aafb611a3266780e77a86093a99ff69f17336637 Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Fri, 20 Jul 2018 12:32:31 -0700 Subject: [PATCH 3/3] fix lint --- visual_behavior/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visual_behavior/utilities.py b/visual_behavior/utilities.py index 80620d0fd..dd29dd35a 100644 --- a/visual_behavior/utilities.py +++ b/visual_behavior/utilities.py @@ -167,7 +167,7 @@ class ListHandler(logging.Handler): def __init__(self, log_list): super(ListHandler, self).__init__() self.log_list = log_list - + def emit(self, record): entry = self.format(record) self.log_list.append(entry)