From 9d06b6631fd0b067849ec9315a3223729d0985b6 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 11:01:36 -0500 Subject: [PATCH 01/33] Add h5py to requirements --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c15428f..8b3eab02 100644 --- a/setup.py +++ b/setup.py @@ -150,7 +150,8 @@ def parse_setuppy_commands(): 'pandas>=0.23.4', 'lightgbm>=2.3.0', 'pywavelets', - 'scikit-learn' # needed for lightgbm + 'scikit-learn', # needed for lightgbm + 'h5py' # currently for gait classifier dataset loading ] if sys.version_info < (3, 7): From f735db4ba6c65200d164b25e3153617e42f9b54b Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 14:49:43 -0500 Subject: [PATCH 02/33] Move vertical accel axis computation to a per-bout level --- src/skimu/gait/gait.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index c57d3f15..5aa39e03 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -278,10 +278,6 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N if (1 / dt) < 20.0: raise LowFrequencyError(f"Frequency ({1/dt:.2f}Hz) is too low (<20Hz)") - # figure out vertical axis - acc_mean = mean(accel, axis=0) - v_axis = argmax(abs(acc_mean)) - # original scale. compute outside loop. # 1.25 comes from original paper, corresponds to desired frequency # 0.4 comes from using the 'gaus1' wavelet @@ -298,7 +294,7 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N } # auxiliary dictionary for storing values for computing gait metrics gait_aux = { - 'vert axis': v_axis, + 'vert axis': [], 'accel': [], 'vert velocity': [], 'vert position': [], @@ -322,6 +318,11 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N for ibout, bout in enumerate(gait_bouts): bstart = start + bout[0] + # figure out vertical axis on a per-bout basis + acc_mean = mean(accel[bstart:start + bout[1]], axis=0) + v_axis = argmax(abs(acc_mean)) + gait_aux['vert axis'].extend(v_axis) + ic, fc, vert_acc = get_gait_events( accel[bstart:start + bout[1], v_axis], dt, time[bstart:start + bout[1]], sign(acc_mean[v_axis]), scale_original, @@ -354,6 +355,7 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N for key in gait: gait[key] = array(gait[key]) # convert inertial data index to an array + gait_aux['vert axis'] = array(gait_aux['vert axis']) gait_aux['inertial data i'] = array(gait_aux['inertial data i']) # loop over metrics and compute From 9d2f46805865c0b6ce93aeddc0f0aac8066f1e19 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 14:53:57 -0500 Subject: [PATCH 03/33] vertical axis saved in gait_aux for each gait bout --- src/skimu/gait/gait.py | 2 +- src/skimu/gait/gait_metrics/gait_metrics.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index 5aa39e03..9ee60c6f 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -321,7 +321,6 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N # figure out vertical axis on a per-bout basis acc_mean = mean(accel[bstart:start + bout[1]], axis=0) v_axis = argmax(abs(acc_mean)) - gait_aux['vert axis'].extend(v_axis) ic, fc, vert_acc = get_gait_events( accel[bstart:start + bout[1], v_axis], dt, time[bstart:start + bout[1]], @@ -339,6 +338,7 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N gait_aux['accel'].append(accel[bstart:start + bout[1], :]) # add the index for the corresponding accel/velocity/position gait_aux['inertial data i'].extend([len(gait_aux['accel']) - 1] * sib) + gait_aux['vert axis'].extend([v_axis] * sib) # save some default per bout metrics gait['Bout N'].extend([ibout + 1] * sib) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 3a126417..52cd8558 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -336,7 +336,7 @@ def _predict(self, dt, leg_length, gait, gait_aux): for i, idx in enumerate(nonzero(mask)[0]): gait[self.k_][idx] = _autocovariance( # index the accel, then the list of views, then the vertical axis - gait_aux['accel'][gait_aux['inertial data i'][idx]][:, gait_aux['vert axis']], + gait_aux['accel'][gait_aux['inertial data i'][idx]][:, gait_aux['vert axis'][idx]], i1[i], i2[i], i3[i], biased=False ) @@ -369,7 +369,7 @@ def _predict(self, dt, leg_length, gait, gait_aux): for i, idx in enumerate(nonzero(mask)[0]): gait[self.k_][idx] = _autocovariance( - gait_aux['accel'][gait_aux['inertial data i'][idx]][:, gait_aux['vert axis']], + gait_aux['accel'][gait_aux['inertial data i'][idx]][:, gait_aux['vert axis'][idx]], i1[i], i2[i], i3[i], biased=False ) @@ -414,9 +414,8 @@ def _predict(self, dt, leg_length, gait, gait_aux): i1 = gait['IC'][mask] i2 = gait['IC'][mask_ofst] - va = gait_aux['vert axis'] # shorthand - for i, idx in enumerate(nonzero(mask)[0]): + va = gait_aux['vert axis'][idx] # shorthand F = abs(fft.rfft( gait_aux['accel'][gait_aux['inertial data i'][idx]][i1[i]:i2[i], va], n=1024 @@ -645,10 +644,11 @@ def _predict(self, dt, leg_length, gait, gait_aux): stepreg = zeros(len(gait_aux['accel']), dtype=float_) for i, acc in enumerate(gait_aux['accel']): + va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] lag = int( round(nanmean(gait['PARAM:step time'][gait_aux['inertial data i'] == i]) / dt) ) - acf = _autocovariancefunction(acc[:, gait_aux['vert axis']], int(4.5 / dt)) + acf = _autocovariancefunction(acc[:, va], int(4.5 / dt)) pks, _ = find_peaks(acf) idx = argmin(abs(pks - lag)) @@ -699,10 +699,11 @@ def _predict(self, dt, leg_length, gait, gait_aux): # acf = _autocovariancefunction(acc[:, gait_aux['vert axis']], int(4.5 / dt)) # compute the average number of samples per stride, this *should* be the # lag over the bout for sequential strides + va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] lag = int( round(nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt) ) - acf = _autocovariancefunction(acc[:, gait_aux['vert axis']], int(4.5 / dt)) + acf = _autocovariancefunction(acc[:, va], int(4.5 / dt)) pks, _ = find_peaks(acf) idx = argmin(abs(pks - lag)) From e0cd8fbc075cd154b39dcbca4494f1e9e87ecd67 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 15:03:04 -0500 Subject: [PATCH 04/33] Update vertical axis for testing --- test/gait/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gait/conftest.py b/test/gait/conftest.py index e89bccc1..f553f514 100644 --- a/test/gait/conftest.py +++ b/test/gait/conftest.py @@ -228,7 +228,7 @@ def y(x): a, a ], 'inertial data i': np.repeat([0, 1], 5), - 'vert axis': 0 + 'vert axis': np.array([0] * 10) } return gait_aux From a5ef6fbaa6904cdc683b0861c76b25dab640c835 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 15:30:23 -0500 Subject: [PATCH 05/33] Make minimum scale value be 1 --- src/skimu/gait/get_gait_events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skimu/gait/get_gait_events.py b/src/skimu/gait/get_gait_events.py index ce6ee08e..26468724 100644 --- a/src/skimu/gait/get_gait_events.py +++ b/src/skimu/gait/get_gait_events.py @@ -67,8 +67,8 @@ def get_gait_events(vert_accel, dt, timestamps, va_sign, o_scale, filter_order, ic_opt_freq = 0.69 * step_freq + 0.34 fc_opt_freq = 3.6 * step_freq - 4.5 - scale1 = round(0.4 / (2 * ic_opt_freq * dt)) - 1 - scale2 = round(0.4 / (2 * fc_opt_freq * dt)) - 1 + scale1 = max(round(0.4 / (2 * ic_opt_freq * dt)) - 1, 1) + scale2 = max(round(0.4 / (2 * fc_opt_freq * dt)) - 1, 1) else: scale1 = scale2 = o_scale From 4c71cf84ff3d703ff113823fbede2a3ef4a3fac0 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 16:17:57 -0500 Subject: [PATCH 06/33] extend instead of append --- src/skimu/gait/get_strides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skimu/gait/get_strides.py b/src/skimu/gait/get_strides.py index 13f83205..d41693ed 100644 --- a/src/skimu/gait/get_strides.py +++ b/src/skimu/gait/get_strides.py @@ -93,6 +93,6 @@ def get_strides(gait, vert_accel, gait_index, ic, fc, timestamps, max_stride_tim gait['delta h'].append(nan) # make sure parameters here math the number of steps in gait - gait['delta h'].append(nan) + gait['delta h'].extend([nan] * min(1, bout_n_steps)) return bout_n_steps From dbc9f00b31b0afd67d7a8c21afb37520cbf847f1 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 16:18:18 -0500 Subject: [PATCH 07/33] Check if all 20 harmonics can be computed, and if not issue a warning --- src/skimu/gait/gait_metrics/gait_metrics.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 52cd8558..52fe5917 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -29,6 +29,8 @@ gait speed: stride_length / stride time """ +from warnings import warn + from numpy import zeros, nanmean, mean, nanstd, std, sum, sqrt, nan, nonzero, argmin, abs, round, \ float_, int_, fft, arange from numpy.linalg import norm @@ -423,6 +425,13 @@ def _predict(self, dt, leg_length, gait, gait_aux): stridef = 1 / gait['PARAM:stride time'][idx] # current stride frequency # get the indices for the first 20 harmonics ix_stridef = argmin(abs(self._freq / dt - stridef)) * self._harmonics + if (ix_stridef < F.size).sum() != ix_stridef.size: + warn( + f"Not enough frequency range to compute all 20 harmonics, using " + f"first {(ix_stridef < F.size).sum()}. Stride frequency {stridef:.2f}Hz", + UserWarning + ) + ix_stridef[ix_stridef < F.size] # make sure not taking more than possible # index 1 is harmonic 2 -> even harmonics / odd harmonics gait[self.k_][idx] = sum(F[ix_stridef[1::2]]) / sum(F[ix_stridef[::2]]) From def19f36eaf48b5c59ba42cfa5052c2f465b977e Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 16:20:18 -0500 Subject: [PATCH 08/33] harmonic ratio uses first available harmonics --- src/skimu/gait/gait_metrics/gait_metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 52fe5917..784f1fad 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -431,7 +431,7 @@ def _predict(self, dt, leg_length, gait, gait_aux): f"first {(ix_stridef < F.size).sum()}. Stride frequency {stridef:.2f}Hz", UserWarning ) - ix_stridef[ix_stridef < F.size] # make sure not taking more than possible + ix_stridef = ix_stridef[ix_stridef < F.size] # make sure not taking more than possible # index 1 is harmonic 2 -> even harmonics / odd harmonics gait[self.k_][idx] = sum(F[ix_stridef[1::2]]) / sum(F[ix_stridef[::2]]) From 1824bca37de90250e0b0f369235e54634490a6b6 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 16:33:06 -0500 Subject: [PATCH 09/33] nan if less than 10 harmonics, otherwise just warning --- src/skimu/gait/gait_metrics/gait_metrics.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 784f1fad..4013bd9b 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -425,10 +425,18 @@ def _predict(self, dt, leg_length, gait, gait_aux): stridef = 1 / gait['PARAM:stride time'][idx] # current stride frequency # get the indices for the first 20 harmonics ix_stridef = argmin(abs(self._freq / dt - stridef)) * self._harmonics - if (ix_stridef < F.size).sum() != ix_stridef.size: + if (ix_stridef < F.size).sum() < 10: warn( - f"Not enough frequency range to compute all 20 harmonics, using " - f"first {(ix_stridef < F.size).sum()}. Stride frequency {stridef:.2f}Hz", + f"High stride frequency [{stridef:.2f}] results too few harmonics in " + f"frequency range. Setting to nan", + UserWarning + ) + gait[self.k_][idx] = nan + continue + elif (ix_stridef < F.size).sum() < 20: + warn( + f"High stride frequency [{stridef:.2f}] results in use of less than 20 " + f"harmonics [{(ix_stridef < F.size).sum()}].", UserWarning ) ix_stridef = ix_stridef[ix_stridef < F.size] # make sure not taking more than possible From 62876e34c682ef3b1f42df233f0051023a28aa93 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Mon, 9 Nov 2020 16:36:52 -0500 Subject: [PATCH 10/33] adding full bout nan check to lag computations --- src/skimu/gait/gait_metrics/gait_metrics.py | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 4013bd9b..9ae22112 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -32,7 +32,7 @@ from warnings import warn from numpy import zeros, nanmean, mean, nanstd, std, sum, sqrt, nan, nonzero, argmin, abs, round, \ - float_, int_, fft, arange + float_, int_, fft, arange, isnan from numpy.linalg import norm from scipy.signal import butter, sosfiltfilt, find_peaks @@ -607,9 +607,11 @@ def _predict(self, dt, leg_length, gait, gait_aux): # setup acceleration filter sos = butter(4, 2 * 10 * dt, btype='low', output='sos') for i, acc in enumerate(gait_aux['accel']): - lag = int( - round(nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt) - ) + lag_ = nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt + if isnan(lag_): # if only nan values in the bout + gsi[i] = nan + continue + lag = int(round(lag_)) # GSI uses biased autocovariance ac = _autocovariancefunction(sosfiltfilt(sos, acc, axis=0), int(4.5 / dt), biased=True) @@ -662,9 +664,11 @@ def _predict(self, dt, leg_length, gait, gait_aux): for i, acc in enumerate(gait_aux['accel']): va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] - lag = int( - round(nanmean(gait['PARAM:step time'][gait_aux['inertial data i'] == i]) / dt) - ) + lag_ = nanmean(gait['PARAM:step time'][gait_aux['inertial data i'] == i]) / dt + if isnan(lag_): # if only nan values in the bout + stepreg[i] = nan + continue + lag = int(round(lag_)) acf = _autocovariancefunction(acc[:, va], int(4.5 / dt)) pks, _ = find_peaks(acf) idx = argmin(abs(pks - lag)) @@ -713,13 +717,12 @@ def _predict(self, dt, leg_length, gait, gait_aux): stridereg = zeros(len(gait_aux['accel']), dtype=float_) for i, acc in enumerate(gait_aux['accel']): - # acf = _autocovariancefunction(acc[:, gait_aux['vert axis']], int(4.5 / dt)) - # compute the average number of samples per stride, this *should* be the - # lag over the bout for sequential strides va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] - lag = int( - round(nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt) - ) + lag_ = nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt + if isnan(lag_): # if only nan values in the bout + stridereg[i] = nan + continue + lag = int(round(lag_)) acf = _autocovariancefunction(acc[:, va], int(4.5 / dt)) pks, _ = find_peaks(acf) idx = argmin(abs(pks - lag)) From 17147aebf966f3fe38a0f8466713cff43c0548af Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 07:48:34 -0500 Subject: [PATCH 11/33] Set to nan if no events in gait bout --- src/skimu/gait/gait_metrics/gait_metrics.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index 9ae22112..edca6d14 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -663,8 +663,12 @@ def _predict(self, dt, leg_length, gait, gait_aux): stepreg = zeros(len(gait_aux['accel']), dtype=float_) for i, acc in enumerate(gait_aux['accel']): - va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] - lag_ = nanmean(gait['PARAM:step time'][gait_aux['inertial data i'] == i]) / dt + mask = gait_aux['inertial data i'] == i + if mask.sum() == 0: + stepreg[i] = nan + continue + va = gait_aux['vert axis'][mask][0] + lag_ = nanmean(gait['PARAM:step time'][mask]) / dt if isnan(lag_): # if only nan values in the bout stepreg[i] = nan continue @@ -717,8 +721,12 @@ def _predict(self, dt, leg_length, gait, gait_aux): stridereg = zeros(len(gait_aux['accel']), dtype=float_) for i, acc in enumerate(gait_aux['accel']): - va = gait_aux['vert axis'][[gait_aux['inertial data i'] == i]][0] - lag_ = nanmean(gait['PARAM:stride time'][gait_aux['inertial data i'] == i]) / dt + mask = gait_aux['inertial data i'] == i + if mask.sum() == 0: + stridereg[i] = nan + continue + va = gait_aux['vert axis'][mask][0] + lag_ = nanmean(gait['PARAM:stride time'][mask]) / dt if isnan(lag_): # if only nan values in the bout stridereg[i] = nan continue From 88124cf8c7d1c37f4406e2d13defa354aabab553 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 08:02:57 -0500 Subject: [PATCH 12/33] Remove unnecessary keys from the gait results dictionary --- src/skimu/gait/gait.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index 9ee60c6f..f181857a 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -362,5 +362,16 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N for param in self._params: param().predict(dt, leg_length, gait, gait_aux) + # remove unnecessary stuff from gait dict + gait.pop('IC', None) + gait.pop('FC', None) + gait.pop('FC opp foot', None) + + # remove in-valid gait cycles + for k in [i for i in gait if i != 'b valid cycle']: + gait[k] = gait[k][gait['b valid cycle']] + + gait.pop('b valid cycle') + kwargs.update({self._acc: accel, self._time: time, self._gyro: gyro, 'height': height}) return kwargs, gait From 865a801f1cb13cbea7a4e451b212a10769beb244 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 08:18:56 -0500 Subject: [PATCH 13/33] Add enable logging to base process class --- src/skimu/base.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/skimu/base.py b/src/skimu/base.py index f2d0b22a..f56964f0 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -5,6 +5,8 @@ Pfizer DMTI 2020 """ from datetime import date as dt_date +import logging + from pandas import DataFrame @@ -46,6 +48,9 @@ def __init__(self, return_result=True, **kwargs): self._kw = kwargs + self.logger = None + self.log_filename = None + def predict(self, *args, **kwargs): result = self._predict(*args, **kwargs) @@ -54,6 +59,38 @@ def predict(self, *args, **kwargs): else: return result[0] + def enable_logging(self, logger=None, filename='pipeline_log.log', name=None): + """ + Enable logging during the execution of the pipeline. This will initialize loggers + for steps already added, and any new steps that are added to the pipeline. + + Parameters + ---------- + logger : logging.Logger, optional + Custom logger for logging. If None (default), a logger will be created. See Notes for + a more detailed description of the logger that will be created. + filename : str, optional + Filename/path for the logger. Default is 'pipeline_log.log'. + name : str, optional + Name for the logger. Default is None, which will use __name__ (skimu.pipeline) + + Notes + ----- + If a logger is being created, it is created with the following attributes/parameters: + + - Name is set to __name__ (`name=None`) or `name` + - Level is set to `INFO` + - Handler is a `logging.FileHandler` with `filename` for the file + """ + if logger is None: + self.log_filename = filename # save the file name + self.logger = logging.getLogger(name if name is not None else __name__) + self.logger.setLevel('INFO') # set the level for the logger + self.logger.addHandler(logging.FileHandler(filename=filename)) + else: + if isinstance(logger, logging.Logger): + self.logger = logger + def save_results(self, results, file_name): """ Save the results of the processing pipeline to a csv file From 8976bb83be7f68168403f1be1caa782a296b93f9 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 08:19:23 -0500 Subject: [PATCH 14/33] Add logging to pipeline class --- src/skimu/pipeline.py | 48 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/skimu/pipeline.py b/src/skimu/pipeline.py index 1bc40b05..5a20d846 100644 --- a/src/skimu/pipeline.py +++ b/src/skimu/pipeline.py @@ -7,6 +7,7 @@ import json from operator import attrgetter from warnings import warn +import logging from skimu.base import _BaseProcess as Process @@ -40,6 +41,44 @@ def __init__(self): self._save = [] self._current = -1 # iteration tracking + self.logger = None + self.log_filename = None + + def enable_logging(self, logger=None, filename='pipeline_log.log', name=None): + """ + Enable logging during the execution of the pipeline. This will initialize loggers + for steps already added, and any new steps that are added to the pipeline. + + Parameters + ---------- + logger : logging.Logger, optional + Custom logger for logging. If None (default), a logger will be created. See Notes for + a more detailed description of the logger that will be created. + filename : str, optional + Filename/path for the logger. Default is 'pipeline_log.log'. + name : str, optional + Name for the logger. Default is None, which will use __name__ (skimu.pipeline) + + Notes + ----- + If a logger is being created, it is created with the following attributes/parameters: + + - Name is set to __name__ (`name=None`) or `name` + - Level is set to `INFO` + - Handler is a `logging.FileHandler` with `filename` for the file + """ + if logger is None: + self.log_filename = filename # save the file name + self.logger = logging.getLogger(name if name is not None else __name__) + self.logger.setLevel('INFO') # set the level for the logger + self.logger.addHandler(logging.FileHandler(filename=filename)) + else: + if isinstance(logger, logging.Logger): + self.logger = logger + + for step in self._steps: + step.enable_logging(filename=filename) + def save(self, file): """ Save the pipeline to a file for consistent pipeline generation. @@ -101,7 +140,8 @@ def load(self, file): save_name=save_name ) - def add(self, process, save_results=False, save_name="{date}_{name}_results.cv"): + def add(self, process, save_results=False, save_name="{date}_{name}_results.cv", + logging_name=None): """ Add a processing step to the pipeline @@ -115,6 +155,9 @@ def add(self, process, save_results=False, save_name="{date}_{name}_results.cv") Optionally formattable path for the save file. Ignored if `save_results` is False. For options for the formatting, see any of the processes (e.g. :class:`Gait`, :class:`Sit2Stand`). Default is "{date}_{name}_results.csv + logging_name : str, optional + Name for the logger for the process, if `enable_logging` has been called. Default is + None, which will use the __name__ attribute for the process. """ if not isinstance(process, Process): raise NotAProcessError("process is not a subclass of _BaseProcess, " @@ -125,6 +168,9 @@ def add(self, process, save_results=False, save_name="{date}_{name}_results.cv") self._steps[-1].pipe_save = save_results self._steps[-1].pipe_fname = save_name + if self.logger is not None: + self._steps[-1].enable_logging(name=logging_name, filename=self.log_filename) + def __iter__(self): return self From 05419bf252bb910ab8b51c99b138166ed3cd8d26 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 08:34:01 -0500 Subject: [PATCH 15/33] Remove _predict method architecture, merge into predict --- src/skimu/base.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/skimu/base.py b/src/skimu/base.py index f56964f0..8c659f05 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -32,7 +32,7 @@ def __repr__(self): return ret - def __init__(self, return_result=True, **kwargs): + def __init__(self, **kwargs): """ Intended to be subclassed @@ -44,7 +44,7 @@ def __init__(self, return_result=True, **kwargs): Key-word arguments which are passed to the sub-class """ self._name = self.__class__.__name__ - self._return_result = return_result + self._in_pipeline = False # initialize to false. Will be set by the pipeline self._kw = kwargs @@ -52,12 +52,11 @@ def __init__(self, return_result=True, **kwargs): self.log_filename = None def predict(self, *args, **kwargs): - result = self._predict(*args, **kwargs) - - if self._return_result: - return result[1] - else: - return result[0] + """ + Intended to be overwritten in the subclass. Should still be called with super though + """ + if self.logger is not None: + self.logger.info(f"Entering {self._name} processing with call {self!r}") def enable_logging(self, logger=None, filename='pipeline_log.log', name=None): """ From 934235b38c224ec0730867a2697bcc9f41ab6471 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 08:38:16 -0500 Subject: [PATCH 16/33] Fix asymmetry metric name to use self.k_ instead of self.name --- src/skimu/gait/gait_metrics/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skimu/gait/gait_metrics/base.py b/src/skimu/gait/gait_metrics/base.py index 1fbda5e9..5be14dc8 100644 --- a/src/skimu/gait/gait_metrics/base.py +++ b/src/skimu/gait/gait_metrics/base.py @@ -122,7 +122,7 @@ def predict(self, dt, leg_length, gait, gait_aux): self._predict(dt, leg_length, gait, gait_aux) def _predict_asymmetry(self, dt, leg_length, gait, gait_aux): - asy_name = f'{self.name} asymmetry' + asy_name = f'{self.k_} asymmetry' gait[asy_name] = full(gait['IC'].size, nan, dtype=float_) mask = self._get_mask(gait, 1) From 59a36ee85f19682bf735c92d799d73edb0795275 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 09:12:22 -0500 Subject: [PATCH 17/33] Add gait logger to gait metrics --- src/skimu/gait/gait.py | 48 +++-------- src/skimu/gait/gait_metrics/gait_metrics.py | 88 +++++++++++++-------- 2 files changed, 65 insertions(+), 71 deletions(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index f181857a..865965d5 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -4,7 +4,6 @@ Lukas Adamowicz Pfizer DMTI 2020 """ -from warnings import warn from collections.abc import Iterable from numpy import mean, diff, abs, argmax, sign, round, array @@ -197,7 +196,7 @@ def add_metrics(self, metrics): else: raise ValueError(f'Metric {metrics!r} is not a EventMetric or BoutMetric') - def predict(self, *args, **kwargs): + def predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=None, **kwargs): """ predict(time, accel, *, gyro=None, height=None, gait_pred=None) @@ -234,40 +233,12 @@ def predict(self, *args, **kwargs): LowFrequencyError If the sampling frequency is less than 20Hz """ - return super().predict(*args, **kwargs) - - def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=None, - **kwargs): - """ - predict(time=None, accel=None, *, gyro=None, height=None, gait_pred=None) - - Get the gait events and metrics from a time series signal - - Parameters - ---------- - time : numpy.ndarray - (N, ) array of unix timestamps, in seconds - accel : numpy.ndarray - (N, 3) array of accelerations measured by centrally mounted lumbar device, in - units of 'g' - gyro : numpy.ndarray, optional - (N, 3) array of angular velocities measured by the same centrally mounted lumbar - device, in units of 'deg/s'. Only optionally used if provided. Main functionality - is to allow distinguishing step sides. - height : float, optional - Either height (False) or leg length (True) of the subject who wore the inertial - measurement device, in meters, depending on `leg_length`. If not provided, - spatial metrics will not be computed - gait_pred : numpy.ndarray, optional - (N, ) array of boolean predictions of gait. If not provided, gait classification - will be performed on the acceleration data + super().predict( + time=time, accel=accel, gyro=gyro, height=height, gait_pred=gait_pred, **kwargs + ) - Returns - ------- - gait_results : dict - """ if height is None: - warn('height not provided, not computing spatial metrics', UserWarning) + self.logger.warning('height not provided, not computing spatial metrics') leg_length = None else: # height factor is set to 1 if providing leg length @@ -360,7 +331,7 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N # loop over metrics and compute for param in self._params: - param().predict(dt, leg_length, gait, gait_aux) + param(self.logger).predict(dt, leg_length, gait, gait_aux) # remove unnecessary stuff from gait dict gait.pop('IC', None) @@ -373,5 +344,8 @@ def _predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=N gait.pop('b valid cycle') - kwargs.update({self._acc: accel, self._time: time, self._gyro: gyro, 'height': height}) - return kwargs, gait + if self._in_pipeline: + kwargs.update({self._acc: accel, self._time: time, self._gyro: gyro, 'height': height}) + return kwargs, gait + else: + return gait diff --git a/src/skimu/gait/gait_metrics/gait_metrics.py b/src/skimu/gait/gait_metrics/gait_metrics.py index edca6d14..987ef01f 100644 --- a/src/skimu/gait/gait_metrics/gait_metrics.py +++ b/src/skimu/gait/gait_metrics/gait_metrics.py @@ -29,8 +29,6 @@ gait speed: stride_length / stride time """ -from warnings import warn - from numpy import zeros, nanmean, mean, nanstd, std, sum, sqrt, nan, nonzero, argmin, abs, round, \ float_, int_, fft, arange, isnan from numpy.linalg import norm @@ -99,7 +97,8 @@ class StrideTime(EventMetric): heel-strike for the same foot. A basic asymmetry measure is also computed as the difference between sequential stride times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('stride time') @basic_asymmetry @@ -114,7 +113,8 @@ class StanceTime(EventMetric): (initial contact) to toe-off (final contact) for a foot. A basic asymmetry measure is also computed as the difference between sequential stance times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('stance time') @basic_asymmetry @@ -128,7 +128,8 @@ class SwingTime(EventMetric): heel-strike (initial contact) of the same foot. A basic asymmetry measure is also computed as the difference between sequential swing times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('swing time') @basic_asymmetry @@ -143,7 +144,8 @@ class StepTime(EventMetric): asymmetry measure is also computed as the difference between sequential step times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('step time') @basic_asymmetry @@ -159,7 +161,8 @@ class InitialDoubleSupport(EventMetric): foot. A basic asymmetry measure is also computed as the difference between sequential initial double support times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('initial double support') @basic_asymmetry @@ -174,7 +177,8 @@ class TerminalDoubleSupport(EventMetric): current foot. A basic asymmetry measure is also computed as the difference between sequential terminal double support times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('terminal double support') @basic_asymmetry @@ -189,7 +193,8 @@ class DoubleSupport(EventMetric): that the current and opposite foot are in contact with the ground. A basic asymmetry measure is also computed as the difference between sequential double support times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('double support', depends=[InitialDoubleSupport, TerminalDoubleSupport]) @basic_asymmetry @@ -205,7 +210,8 @@ class SingleSupport(EventMetric): asymmetry measure is also computed as the difference between sequential single support times of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('single support') @basic_asymmetry @@ -236,7 +242,8 @@ class StepLength(EventMetric): trunk accelerations during human walking,” Gait & Posture, vol. 18, no. 2, pp. 1–10, Oct. 2003, doi: 10.1016/S0966-6362(02)00190-X. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('step length') @basic_asymmetry @@ -270,7 +277,8 @@ class StrideLength(EventMetric): trunk accelerations during human walking,” Gait & Posture, vol. 18, no. 2, pp. 1–10, Oct. 2003, doi: 10.1016/S0966-6362(02)00190-X. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('stride length', depends=[StepLength]) @basic_asymmetry @@ -287,7 +295,8 @@ class GaitSpeed(EventMetric): stride duration, in m/s. A basic asymmetry measure is also computed as the difference between sequential gait speeds of opposite feet. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('gait speed', depends=[StrideLength, StrideTime]) @basic_asymmetry @@ -302,7 +311,8 @@ class Cadence(EventMetric): """ The number of steps taken in 1 minute. Computed per step as 60.0s divided by the step time. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('cadence', depends=[StepTime]) def _predict(self, dt, leg_length, gait, gait_aux): @@ -325,7 +335,8 @@ class IntraStrideCovarianceV(EventMetric): Methods Using a Single Accelerometer Located on the Trunk,” Sensors, vol. 20, no. 1, Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('intra-stride covariance - V') def _predict(self, dt, leg_length, gait, gait_aux): @@ -359,7 +370,8 @@ class IntraStepCovarianceV(EventMetric): Methods Using a Single Accelerometer Located on the Trunk,” Sensors, vol. 20, no. 1, Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('intra-step covariance - V') def _predict(self, dt, leg_length, gait, gait_aux): @@ -404,7 +416,8 @@ class HarmonicRatioV(EventMetric): Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('harmonic ratio - V', depends=[StrideTime]) self._freq = fft.rfftfreq(1024) # precompute the frequencies (still need to be scaled) # TODO add check for stride frequency, if too low, bump this up higher? @@ -426,19 +439,19 @@ def _predict(self, dt, leg_length, gait, gait_aux): # get the indices for the first 20 harmonics ix_stridef = argmin(abs(self._freq / dt - stridef)) * self._harmonics if (ix_stridef < F.size).sum() < 10: - warn( - f"High stride frequency [{stridef:.2f}] results too few harmonics in " - f"frequency range. Setting to nan", - UserWarning - ) + if self.logger is not None: + self.logger.warning( + f"High stride frequency [{stridef:.2f}] results too few harmonics in " + f"frequency range. Setting to nan" + ) gait[self.k_][idx] = nan continue elif (ix_stridef < F.size).sum() < 20: - warn( - f"High stride frequency [{stridef:.2f}] results in use of less than 20 " - f"harmonics [{(ix_stridef < F.size).sum()}].", - UserWarning - ) + if self.logger is not None: + self.logger.warning( + f"High stride frequency [{stridef:.2f}] results in use of less than 20 " + f"harmonics [{(ix_stridef < F.size).sum()}]." + ) ix_stridef = ix_stridef[ix_stridef < F.size] # make sure not taking more than possible # index 1 is harmonic 2 -> even harmonics / odd harmonics @@ -462,7 +475,8 @@ class StrideSPARC(EventMetric): analysis of movement smoothness,” J NeuroEngineering Rehabil, vol. 12, no. 1, p. 112, Dec. 2015, doi: 10.1186/s12984-015-0090-9. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('stride SPARC') def _predict(self, dt, leg_length, gait, gait_aux): @@ -526,7 +540,8 @@ class PhaseCoordinationIndex(BoutMetric): Further Insights into Motor-Cognitive Links,” Parkinsons Dis, vol. 2015, 2015, doi: 10.1155/2015/547065. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('phase coordination index', depends=[StrideTime, StepTime]) def _predict(self, dt, leg_length, gait, gait_aux): @@ -598,7 +613,8 @@ class GaitSymmetryIndex(BoutMetric): parameters following stroke: a practical assessment,” Journal of Rehabilitation Research and Development, vol. 32, no. 1, pp. 25–31, Feb. 1995. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('gait symmetry index', depends=[StrideTime]) def _predict(self, dt, leg_length, gait, gait_aux): @@ -656,7 +672,8 @@ class StepRegularityV(BoutMetric): Methods Using a Single Accelerometer Located on the Trunk,” Sensors, vol. 20, no. 1, Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('step regularity - V', depends=[StepTime]) def _predict(self, dt, leg_length, gait, gait_aux): @@ -714,7 +731,8 @@ class StrideRegularityV(BoutMetric): Methods Using a Single Accelerometer Located on the Trunk,” Sensors, vol. 20, no. 1, Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__('stride regularity - V', depends=[StrideTime]) def _predict(self, dt, leg_length, gait, gait_aux): @@ -759,7 +777,8 @@ class AutocovarianceSymmetryV(BoutMetric): Methods Using a Single Accelerometer Located on the Trunk,” Sensors, vol. 20, no. 1, Art. no. 1, Jan. 2020, doi: 10.3390/s20010037. """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__( 'autocovariance symmetry - V', depends=[StepRegularityV, StrideRegularityV] ) @@ -803,7 +822,8 @@ class RegularityIndexV(BoutMetric): """ - def __init__(self): + def __init__(self, logger=None): + self.logger = logger super().__init__( 'regularity index - V', depends=[StepRegularityV, StrideRegularityV] ) From 2164d30036d9724fd8f3d62065e1f6e06b5bab7e Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 09:17:25 -0500 Subject: [PATCH 18/33] Update _predict->predict and logging --- src/skimu/read/axivity.py | 23 +++++++---------------- src/skimu/read/geneactiv.py | 13 +++++++------ 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/skimu/read/axivity.py b/src/skimu/read/axivity.py index 57df506e..a9a5c1d6 100644 --- a/src/skimu/read/axivity.py +++ b/src/skimu/read/axivity.py @@ -50,7 +50,6 @@ class ReadCWA(_BaseProcess): def __init__(self, base=None, period=None): super().__init__( - False, # kwargs base=base, period=None @@ -73,7 +72,7 @@ def __init__(self, base=None, period=None): else: raise ValueError("Base must be in [0, 23] and period must be in [1, 23]") - def predict(self, *args, **kwargs): + def predict(self, file=None, **kwargs): """ predict(file) @@ -107,25 +106,14 @@ def predict(self, *args, **kwargs): - `time`: timestamps [s] - `day_ends`: window indices """ - return super().predict(*args, **kwargs) - - def _predict(self, file=None, **kwargs): - """ - Read the data from the axivity file - - Parameters - ---------- - file : {str, Path} - Path to the file to read. Must either be a string, or be able to be converted by - `str(file)` - """ + super().predict(file=file, **kwargs) if file is None: raise ValueError("file must not be None") if not isinstance(file, str): file = str(file) if file[-3:] != "cwa": - warn("File extension is not '.cwa'", UserWarning) + self.logger.warning("File extension is not '.cwa'", UserWarning) # read the file meta, imudata, ts, idx, light = read_cwa(file, self.base, self.period) @@ -159,4 +147,7 @@ def _predict(self, file=None, **kwargs): results[self._days] = vstack((day_starts, day_stops)).T kwargs.update(results) - return kwargs, None + if self._in_pipeline: + return kwargs, None + else: + return kwargs diff --git a/src/skimu/read/geneactiv.py b/src/skimu/read/geneactiv.py index 9514270f..158b698f 100644 --- a/src/skimu/read/geneactiv.py +++ b/src/skimu/read/geneactiv.py @@ -45,7 +45,6 @@ class ReadBin(_BaseProcess): def __init__(self, base=None, period=None): super().__init__( - False, # kwargs base=base, period=period @@ -68,7 +67,7 @@ def __init__(self, base=None, period=None): else: raise ValueError("Base must be in [0, 23] and period must be in [1, 23]") - def predict(self, *args, **kwargs): + def _predict(self, file=None, **kwargs): """ predict(file) @@ -100,15 +99,14 @@ def predict(self, *args, **kwargs): - `temperature`: temperature [deg C] - `day_ends`: window indices """ - return super().predict(*args, **kwargs) + super().predict(file=file, **kwargs) - def _predict(self, file=None, **kwargs): if file is None: raise ValueError("file must not be None") if not isinstance(file, str): file = str(file) if file[-3:] != "bin": - warn("File extension is not '.bin'", UserWarning) + self.logger.warning("File extension is not '.bin'", UserWarning) # read the file nmax, hdr, acc, time, light, temp, idx, dtime = bin_convert(file, self.base, self.period) @@ -125,4 +123,7 @@ def _predict(self, file=None, **kwargs): results[self._days] = vstack((day_starts, day_stops)).T kwargs.update(results) - return kwargs, None + if self._in_pipeline: + return kwargs, None + else: + return kwargs From 52eb28a7e0c38478901dfdb3db133a99d5b99d45 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 09:17:52 -0500 Subject: [PATCH 19/33] Remove old return_results in super().__init__ call --- src/skimu/gait/gait.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index 865965d5..5bd7b501 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -131,7 +131,6 @@ def __init__(self, use_cwt_scale_relation=True, min_bout_time=8.0, max_bout_separation_time=0.5, max_stride_time=2.25, loading_factor=0.2, height_factor=0.53, prov_leg_length=False, filter_order=4, filter_cutoff=20.0): super().__init__( - True, # key-word arguments for storage use_cwt_scale_relation=use_cwt_scale_relation, min_bout_time=min_bout_time, From 7b557da88ffd42e618ff1261f17cf1864cc7c775 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 09:20:06 -0500 Subject: [PATCH 20/33] Fix returns based on inside or outside pipeline --- src/skimu/gait/gait.py | 2 +- src/skimu/sit2stand/sit2stand.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index 5bd7b501..defaa4e7 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -343,8 +343,8 @@ def predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=No gait.pop('b valid cycle') + kwargs.update({self._acc: accel, self._time: time, self._gyro: gyro, 'height': height}) if self._in_pipeline: - kwargs.update({self._acc: accel, self._time: time, self._gyro: gyro, 'height': height}) return kwargs, gait else: return gait diff --git a/src/skimu/sit2stand/sit2stand.py b/src/skimu/sit2stand/sit2stand.py index 746b20c4..12ee7a22 100644 --- a/src/skimu/sit2stand/sit2stand.py +++ b/src/skimu/sit2stand/sit2stand.py @@ -108,7 +108,6 @@ def __init__( reconstruction_window=0.25 ): super().__init__( - True, # kwarg saving stillness_constraint=stillness_constraint, gravity=gravity, @@ -162,7 +161,7 @@ def __init__( still_window=still_window ) - def predict(self, *args, **kwargs): + def predict(self, time=None, accel=None, **kwargs): """ predict(time, accel) @@ -175,9 +174,8 @@ def predict(self, *args, **kwargs): accel : ndarray (N, 3) array of acceleration, with units of 'g'. """ - return super().predict(*args, **kwargs) + super().predict(time=time, accel=accel, **kwargs) - def _predict(self, time=None, accel=None, **kwargs): # FILTERING # ====================================================== # compute the sampling period @@ -254,4 +252,7 @@ def _predict(self, time=None, accel=None, **kwargs): sts.pop('Partial') kwargs.update({self._time: time, self._acc: accel}) - return kwargs, sts + if self._in_pipeline: + return kwargs, sts + else: + return sts From 5abe627c872ec87471404e88abc64e64c5beb54b Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:02:12 -0500 Subject: [PATCH 21/33] Change logging setup to be available, but start disabled --- src/skimu/base.py | 51 +++++++++++++++----------------- src/skimu/pipeline.py | 68 ++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 64 deletions(-) diff --git a/src/skimu/base.py b/src/skimu/base.py index 8c659f05..1f472f7c 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -48,8 +48,10 @@ def __init__(self, **kwargs): self._kw = kwargs - self.logger = None - self.log_filename = None + self.logger = logging.getLogger(__name__) + self.logger.setLevel('INFO') # set the level for the logger + self.logger.addHandler(logging.StreamHandler()) + self.logger.disabled = True def predict(self, *args, **kwargs): """ @@ -58,37 +60,30 @@ def predict(self, *args, **kwargs): if self.logger is not None: self.logger.info(f"Entering {self._name} processing with call {self!r}") - def enable_logging(self, logger=None, filename='pipeline_log.log', name=None): + def disable_logging(self): """ - Enable logging during the execution of the pipeline. This will initialize loggers - for steps already added, and any new steps that are added to the pipeline. + Disable all logging for the process. + """ + self.logger.disabled = True - Parameters - ---------- - logger : logging.Logger, optional - Custom logger for logging. If None (default), a logger will be created. See Notes for - a more detailed description of the logger that will be created. - filename : str, optional - Filename/path for the logger. Default is 'pipeline_log.log'. - name : str, optional - Name for the logger. Default is None, which will use __name__ (skimu.pipeline) + def enable_logging(self): + """ + Enable all logging for the process. + """ + self.logger.disabled = False - Notes - ----- - If a logger is being created, it is created with the following attributes/parameters: + def add_logging_file_handler(self, filename=None): + """ + Add a file handler for logging, which will output the logs to the specified file. - - Name is set to __name__ (`name=None`) or `name` - - Level is set to `INFO` - - Handler is a `logging.FileHandler` with `filename` for the file + Parameters + ---------- + filename : {None, str}, optional + Filename for the logger. Default is None, which will use "{name}_logs.log" """ - if logger is None: - self.log_filename = filename # save the file name - self.logger = logging.getLogger(name if name is not None else __name__) - self.logger.setLevel('INFO') # set the level for the logger - self.logger.addHandler(logging.FileHandler(filename=filename)) - else: - if isinstance(logger, logging.Logger): - self.logger = logger + if filename is None: + filename = f"{self._name}_logs.log" + self.logger.addHandler(logging.FileHandler(filename=filename)) def save_results(self, results, file_name): """ diff --git a/src/skimu/pipeline.py b/src/skimu/pipeline.py index 5a20d846..88742d4b 100644 --- a/src/skimu/pipeline.py +++ b/src/skimu/pipeline.py @@ -41,43 +41,35 @@ def __init__(self): self._save = [] self._current = -1 # iteration tracking - self.logger = None - self.log_filename = None + self.logger = logging.getLogger(__name__) + self.logger.setLevel('INFO') # set the level for the logger + self.logger.addHandler(logging.StreamHandler()) + self.logger.disabled = True - def enable_logging(self, logger=None, filename='pipeline_log.log', name=None): + def disable_logging(self): """ - Enable logging during the execution of the pipeline. This will initialize loggers - for steps already added, and any new steps that are added to the pipeline. + Disable all logging for the pipeline + """ + self.logger.disabled = True + + def enable_logging(self): + """ + Enable all logging for the pipeline + """ + self.logger.disabled = False + + def add_logging_file_handler(self, filename=None): + """ + Add a file handler for logging, which will output the logs to the specified file. Parameters ---------- - logger : logging.Logger, optional - Custom logger for logging. If None (default), a logger will be created. See Notes for - a more detailed description of the logger that will be created. - filename : str, optional - Filename/path for the logger. Default is 'pipeline_log.log'. - name : str, optional - Name for the logger. Default is None, which will use __name__ (skimu.pipeline) - - Notes - ----- - If a logger is being created, it is created with the following attributes/parameters: - - - Name is set to __name__ (`name=None`) or `name` - - Level is set to `INFO` - - Handler is a `logging.FileHandler` with `filename` for the file + filename : {None, str}, optional + Filename for the logger. Default is None, which will use "pipeline_logs.log" """ - if logger is None: - self.log_filename = filename # save the file name - self.logger = logging.getLogger(name if name is not None else __name__) - self.logger.setLevel('INFO') # set the level for the logger - self.logger.addHandler(logging.FileHandler(filename=filename)) - else: - if isinstance(logger, logging.Logger): - self.logger = logger - - for step in self._steps: - step.enable_logging(filename=filename) + if filename is None: + filename = f"pipeline_logs.log" + self.logger.addHandler(logging.FileHandler(filename=filename)) def save(self, file): """ @@ -165,11 +157,12 @@ def add(self, process, save_results=False, save_name="{date}_{name}_results.cv", self._steps += [process] # attach the save bool and save_name to the process + self._steps[-1]._in_pipeline = True self._steps[-1].pipe_save = save_results self._steps[-1].pipe_fname = save_name - if self.logger is not None: - self._steps[-1].enable_logging(name=logging_name, filename=self.log_filename) + # point the step logging disabled to the pipeline disabled + self._steps[-1].logger.disabled = self.logger.disabled def __iter__(self): return self @@ -203,10 +196,13 @@ def run(self, **kwargs): results = {} for proc in self: - kwargs, step_result = proc._predict(**kwargs) + kwargs, step_result = proc.predict(**kwargs) if proc.pipe_save: - proc.save_results(step_result if proc._return_result else kwargs, proc.pipe_fname) - if proc._return_result: + proc.save_results( + step_result if step_result is not None else kwargs, + proc.pipe_fname + ) + if step_result is not None: results[proc._name] = step_result return results From 5e37525b99e9985b276c5e731e1df177393f9b37 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:05:12 -0500 Subject: [PATCH 22/33] remove logging_name from add step --- src/skimu/pipeline.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/skimu/pipeline.py b/src/skimu/pipeline.py index 88742d4b..6b0e8eb8 100644 --- a/src/skimu/pipeline.py +++ b/src/skimu/pipeline.py @@ -68,7 +68,7 @@ def add_logging_file_handler(self, filename=None): Filename for the logger. Default is None, which will use "pipeline_logs.log" """ if filename is None: - filename = f"pipeline_logs.log" + filename = "pipeline_logs.log" self.logger.addHandler(logging.FileHandler(filename=filename)) def save(self, file): @@ -132,8 +132,7 @@ def load(self, file): save_name=save_name ) - def add(self, process, save_results=False, save_name="{date}_{name}_results.cv", - logging_name=None): + def add(self, process, save_results=False, save_name="{date}_{name}_results.cv"): """ Add a processing step to the pipeline @@ -147,9 +146,6 @@ def add(self, process, save_results=False, save_name="{date}_{name}_results.cv", Optionally formattable path for the save file. Ignored if `save_results` is False. For options for the formatting, see any of the processes (e.g. :class:`Gait`, :class:`Sit2Stand`). Default is "{date}_{name}_results.csv - logging_name : str, optional - Name for the logger for the process, if `enable_logging` has been called. Default is - None, which will use the __name__ attribute for the process. """ if not isinstance(process, Process): raise NotAProcessError("process is not a subclass of _BaseProcess, " From 066f41398bfef9853ee508791c903a86e606ee44 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:09:08 -0500 Subject: [PATCH 23/33] remove none check for logger --- src/skimu/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/skimu/base.py b/src/skimu/base.py index 1f472f7c..36ca9e08 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -57,8 +57,7 @@ def predict(self, *args, **kwargs): """ Intended to be overwritten in the subclass. Should still be called with super though """ - if self.logger is not None: - self.logger.info(f"Entering {self._name} processing with call {self!r}") + self.logger.info(f"Entering {self._name} processing with call {self!r}") def disable_logging(self): """ From d63d913b973ac3d13294bf65b65afa7891d83edb Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:11:44 -0500 Subject: [PATCH 24/33] Remove userwarning from logger.warning --- src/skimu/read/axivity.py | 2 +- src/skimu/read/geneactiv.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skimu/read/axivity.py b/src/skimu/read/axivity.py index a9a5c1d6..ef488485 100644 --- a/src/skimu/read/axivity.py +++ b/src/skimu/read/axivity.py @@ -113,7 +113,7 @@ def predict(self, file=None, **kwargs): if not isinstance(file, str): file = str(file) if file[-3:] != "cwa": - self.logger.warning("File extension is not '.cwa'", UserWarning) + self.logger.warning("File extension is not '.cwa'") # read the file meta, imudata, ts, idx, light = read_cwa(file, self.base, self.period) diff --git a/src/skimu/read/geneactiv.py b/src/skimu/read/geneactiv.py index 158b698f..7632b8f4 100644 --- a/src/skimu/read/geneactiv.py +++ b/src/skimu/read/geneactiv.py @@ -106,7 +106,7 @@ def _predict(self, file=None, **kwargs): if not isinstance(file, str): file = str(file) if file[-3:] != "bin": - self.logger.warning("File extension is not '.bin'", UserWarning) + self.logger.warning("File extension is not '.bin'") # read the file nmax, hdr, acc, time, light, temp, idx, dtime = bin_convert(file, self.base, self.period) From e058f9a18d7112b671108c05384733a12458c5d8 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:23:51 -0500 Subject: [PATCH 25/33] fix predict name --- src/skimu/read/geneactiv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skimu/read/geneactiv.py b/src/skimu/read/geneactiv.py index 7632b8f4..1fc6f200 100644 --- a/src/skimu/read/geneactiv.py +++ b/src/skimu/read/geneactiv.py @@ -67,7 +67,7 @@ def __init__(self, base=None, period=None): else: raise ValueError("Base must be in [0, 23] and period must be in [1, 23]") - def _predict(self, file=None, **kwargs): + def predict(self, file=None, **kwargs): """ predict(file) From f8f384c75ad50d16e8d783e5786ca2c6a20d809b Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 10:36:29 -0500 Subject: [PATCH 26/33] Test logging setup --- src/skimu/base.py | 10 +++++++--- src/skimu/pipeline.py | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/skimu/base.py b/src/skimu/base.py index 36ca9e08..d0837902 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -49,8 +49,10 @@ def __init__(self, **kwargs): self._kw = kwargs self.logger = logging.getLogger(__name__) - self.logger.setLevel('INFO') # set the level for the logger - self.logger.addHandler(logging.StreamHandler()) + self.logger.setLevel(logging.INFO) # set the level for the logger + self._sh = logging.StreamHandler() + self._sh.setLevel(logging.INFO) + self.logger.addHandler(self._sh) self.logger.disabled = True def predict(self, *args, **kwargs): @@ -82,7 +84,9 @@ def add_logging_file_handler(self, filename=None): """ if filename is None: filename = f"{self._name}_logs.log" - self.logger.addHandler(logging.FileHandler(filename=filename)) + fh = logging.FileHandler(filename=filename) + fh.setLevel(logging.INFO) + self.logger.addHandler(fh) def save_results(self, results, file_name): """ diff --git a/src/skimu/pipeline.py b/src/skimu/pipeline.py index 6b0e8eb8..2dfef9fd 100644 --- a/src/skimu/pipeline.py +++ b/src/skimu/pipeline.py @@ -42,8 +42,10 @@ def __init__(self): self._current = -1 # iteration tracking self.logger = logging.getLogger(__name__) - self.logger.setLevel('INFO') # set the level for the logger - self.logger.addHandler(logging.StreamHandler()) + self.logger.setLevel(logging.INFO) # set the level for the logger + self._sh = logging.StreamHandler() + self._sh.setLevel(logging.INFO) + self.logger.addHandler(self._sh) self.logger.disabled = True def disable_logging(self): @@ -69,7 +71,9 @@ def add_logging_file_handler(self, filename=None): """ if filename is None: filename = "pipeline_logs.log" - self.logger.addHandler(logging.FileHandler(filename=filename)) + _fh = logging.FileHandler(filename=filename) + _fh.setLevel(logging.INFO) + self.logger.addHandler(_fh) def save(self, file): """ From ffb9fe82534c439b268db7178fd2df5fbb6fdf7d Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 11:06:06 -0500 Subject: [PATCH 27/33] remove logging setup that should be handled by end users --- src/skimu/base.py | 32 -------------------------------- src/skimu/pipeline.py | 32 -------------------------------- 2 files changed, 64 deletions(-) diff --git a/src/skimu/base.py b/src/skimu/base.py index d0837902..8040ad20 100644 --- a/src/skimu/base.py +++ b/src/skimu/base.py @@ -49,11 +49,6 @@ def __init__(self, **kwargs): self._kw = kwargs self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) # set the level for the logger - self._sh = logging.StreamHandler() - self._sh.setLevel(logging.INFO) - self.logger.addHandler(self._sh) - self.logger.disabled = True def predict(self, *args, **kwargs): """ @@ -61,33 +56,6 @@ def predict(self, *args, **kwargs): """ self.logger.info(f"Entering {self._name} processing with call {self!r}") - def disable_logging(self): - """ - Disable all logging for the process. - """ - self.logger.disabled = True - - def enable_logging(self): - """ - Enable all logging for the process. - """ - self.logger.disabled = False - - def add_logging_file_handler(self, filename=None): - """ - Add a file handler for logging, which will output the logs to the specified file. - - Parameters - ---------- - filename : {None, str}, optional - Filename for the logger. Default is None, which will use "{name}_logs.log" - """ - if filename is None: - filename = f"{self._name}_logs.log" - fh = logging.FileHandler(filename=filename) - fh.setLevel(logging.INFO) - self.logger.addHandler(fh) - def save_results(self, results, file_name): """ Save the results of the processing pipeline to a csv file diff --git a/src/skimu/pipeline.py b/src/skimu/pipeline.py index 2dfef9fd..7b075329 100644 --- a/src/skimu/pipeline.py +++ b/src/skimu/pipeline.py @@ -42,38 +42,6 @@ def __init__(self): self._current = -1 # iteration tracking self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) # set the level for the logger - self._sh = logging.StreamHandler() - self._sh.setLevel(logging.INFO) - self.logger.addHandler(self._sh) - self.logger.disabled = True - - def disable_logging(self): - """ - Disable all logging for the pipeline - """ - self.logger.disabled = True - - def enable_logging(self): - """ - Enable all logging for the pipeline - """ - self.logger.disabled = False - - def add_logging_file_handler(self, filename=None): - """ - Add a file handler for logging, which will output the logs to the specified file. - - Parameters - ---------- - filename : {None, str}, optional - Filename for the logger. Default is None, which will use "pipeline_logs.log" - """ - if filename is None: - filename = "pipeline_logs.log" - _fh = logging.FileHandler(filename=filename) - _fh.setLevel(logging.INFO) - self.logger.addHandler(_fh) def save(self, file): """ From 4d0dd9bde1ce536f0b20c912a79de5044b301a73 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:13:45 -0500 Subject: [PATCH 28/33] Update gait tests for new logging and predict naming --- test/data/gait_data.h5 | Bin 2485971 -> 2485971 bytes test/gait/test_gait.py | 14 ++++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/data/gait_data.h5 b/test/data/gait_data.h5 index 83c88417a5052ec52e98176b467c5641ead33b28..477a35f216f1b6ed4cc71817eb54e8dee5039295 100644 GIT binary patch delta 2566 zcma)7ZA?>V6z=V%a!Yj-5iB3VWjz;aa7q1uF>wJTGb~_Mby>Fg2Sz(e>Y$OD6g7g= zOcCQpnsIRG0CA3KqOv+K3nE>}91%Baa2rnDy5TkxT++>CnUckwx4lkN`hzA+=3$q;%9s>2G$K`HcLSL~H(VS9lh7rz~rMMVxw z8$(b1>b|~{oX4pZJWh?Ewy3>ZHLe&wR%P;(#N}`!n6egfxGsDn6>`*O94Zgu3_>PBBxDhul?!ynfBw+6C@lAiAFm^v>UbiZ?L~AgHTl9n`pM7Xt%6S4BL1$4y?RLZAzD1bcBnfC<2qF$2 zluWvQWF18Dp;3&CKHV8UpeNCz7EyX48f_=;Zh~d<)g9J{cDGT`+S|D;fAST8bI%;N@A-$_Hl9#!{d~CvxGA znGKv1d@~mmQZR6W68%0e#8zDsv*|E$T!#aK`IJ z>w>ow5o zEah>dm(Eppp038m^K>-|KVw(Jae=M|Hm?VZEW8S336XD_dnz&-(gg3Br%_EPm)`WR zk>*x+NV(VRM$VrE~6TvO5mVdCr zsd%I;PcM-#(qOf)Z-y65`e8iL2yOV`3RuXC)_Uw;0U4>H)l#y)WbI08{g$^j*~Eq| zPG|jQmx^Qzu7ISpl?}CFLW9fZ6gS!GYFu^|b7*j(m%mZbK+|_k_Sdjomg9E#b6#}s2fGp=hJVk z3(fhkX1;5)g%7vOq;w6N^T8bdI3N}0s(r_Xj_r_=WVLN_Rf%?0*jQa0z zc=!8h@Wwx9gU1iB!Ry&4b+skZ-q_Ykd!wh7%)9o7R`y6}F11jGlLw?jI-fFLdu!O1 z)b%R;P%R6I!sC5v3yF3`w4^c-mo(BW-f@FSuVIcw+8JG#)=uR(+9_Z85teV;2Q1&f zmt=BWCey%WGIe~#OrxPvHe4YSKT4(zqhwlsm6@u)W?udb+NI5zv`hV&v`f#4Y?lVI zXqRdrsr#j4a&fOz$l>TGQYB7(Lt#3`q!PS0CXLCXeiml>8Vkc8C6o6kW#ErdhRI{h zl-S8k!2&42fpN($m{so{-*Mz#5t>(#YS(olR)0%G;nC#O%4_?liZJjZl*yCd(Z0sC z38@>ePDn+Vc7ur1H(12nn=GQ;NHrUPYPJK_TqaS?Sh=^};Fc6X?`_h0eq`F=pP07%XVRAcLfYI(rrmglX$wCh?edePP5hW?OM97? z#w>tN3+1(}W4)sFL)xidi97ilafg3r-1~Ql+h&45+hSq{7BeROVGxrlFv(PbnD8Ig Ca4RbS delta 5364 zcmZvg4^&iD9>?FC8JJ;4m=O?23diO?nWBu!igdRbvtuS@V|4s~0<(}LcS%<{ft|$G}vug3x`eq)i#vrqY0!3U#3TN3s^J{b8eWe{bG*kOnyC$LHSPz3<-p z{oQ-ty>E_v!Np@_H3ix^f%}=D{OuG&5V~Si8dM0uB(=OAdDloa??nCQi`Bdp<%Juh zP%2YfTl{_*|GhfMiI*qf+1NPNfrgd2+kxWCTCDV~bkwuf~$qZyMWJWTj?l7`gvN$p` znT2dPSv*++St8j8vLv!(vJ|qb$VQT-l3h)94Otr5C^9SAXtHbNER(JGBDYUtQ#Etz z_=?N6N^bqxW>r0xWSKk*h71SkO{v@q3@>{>q_7u-7fGv@)y6UB&@5 zW&dbaEXv*}cGqxG$4?}&>r7U}JoE9;6l7wWdon|_RWI;GsccMnpnkxB3wgwVH4Tj+ zkEb)rAf|XG8q-asXs7e`P_xDAums^Z8_g`=n92;iVuNHY5BLW(obz!+!&-_#WVpJU zl}fw$q)N%pcRHAbpRSagYRa}<3WiehrR@@_SBm9?9a6EHRQrniHL+|^|44#>d8I@tLH-!5dtpl5``R5e58i%VQ#C%nOmhD&?&zE>MyviKi=?}? z_itc)c%d{(-#T?;t{|+GTPBOk^fI~M`_cUwMm7;G{}Z(0-T5;3Kd=xR`UIG*QybU= z-V~+gUj|LlYJMqr8hk(>^1FqAnh+S?I9^bPlcN;;0n`su8+-;l4bF&F{SDyFacbTR z#+cRoQ(#w!Zb1kuwnO3TU?LQ*1-}FHq4X%|gwks88YnCWe*>35=o#=KMJV+zG(z!M zDBKFh8PyT51kZzWOsc;L9FGW8fL9{|rQiYx`U0E@A&o?(e_=e7RDcOkvI_hUI2THq zz#E}t3#fyTBJdP=F9hudtq`)_sP-@DpyW?r2RIpu_JX6JXd~DQF2)=m2VI!sec+9F zr+UHY;c5hz4iD)Ugwt?Li&qOeR0uP%8$JMUMF_Wo2?%8g_%)b=NH&3E5y46@4iPMK zBk?`hh)^y;=(@n!2;C9z4uo(Qn1qO}0sjlmN93BpyAZJ*pgRpATMPDq`3PMbI0>Pv z2N@#f19gbpbKpfVH$e^MVQ^BSn%}8%w2(eRb*u*ylhk|}$Qyi;H)Zek4ZUyt-03(p zyE^|{r_Q-FZ}-N&T%B`M0<-hfGARYa>cxc8y~Vdh`Y5F{Zu+KQ17A4QHm{V*g|A8} zV#+ti&ju>5Zy#J-_L@{Aa@z*UWpb&{3Y~qSH@g>E%Vo-@@jHL9rJFWlIph_D-u9VT z1+IlmdDlLm=Y^bZ2AiIqe|w>CEQ~zVwlsI9b8BOYyLrX!JpOI! zrDOWkzqSeUoZ&F=oVTf$r>eXs+8>$S@0D#eN-yIaQ@ak{Gv|tXQZ4mjS-ANR%W{v; z=y!9^dnBrLf4RPN(Y1jx)5Hn+uiE%6fuXKDO9Mk)cP72BbQc~|nDT-Tl;R`@Zuf(? z;H~Tjx15p%CcLW7V!33hazLYee$N_9Up<{#`syB3gVX1g-_ktiHv8TZqjTAvnKOzzVw~LK z8mbSMYp6b~)2WX^4a?4ls6XDfMyiP$sk28XpYL)QA2TOi`jzimE8Wc-UZ6K{_gX1v z-nMXw?}G2VV{>wT#jNym^1gM_Xff`yvw@S}969HBrl!7h{ipJ&QmI5tp4qx;!kd5l z+HrI0zwNHZ^A7dc-p_N>**3j;7{1SC8@rDendy_q7N=aTi^Z&k;yjXG@EcCVIva>*_DXc^KX(Py#*oN9P#8aCejbPRR z&GlF0bw0^I=w^~p>HKkgu-(yrGr3aVvxQE<$Qy~g;8i-qk|J*+YBTca^%R~fE%a@b z{DWqM`~1=n3#Z*b)R|koZOAkC@asdKxfxaAUqKF6NfY@l2Qzb1b@*41_G;zy4gCtz zw_7=VL%)~M4+B~J(mPUtd87P)`K|%aX=Q0!^0{B`)z^ILxb)agYf_W diff --git a/test/gait/test_gait.py b/test/gait/test_gait.py index bda7cd20..a3ed9a97 100644 --- a/test/gait/test_gait.py +++ b/test/gait/test_gait.py @@ -169,10 +169,7 @@ def setup_class(cls): cls.truth_data_file = resolve_data_path('gait_data.h5', 'gait') cls.truth_suffix = None cls.truth_data_keys = [ - 'IC', - 'FC', 'delta h', - 'b valid cycle', 'PARAM:stride time', 'PARAM:stance time', 'PARAM:swing time', @@ -217,15 +214,16 @@ def test_leg_length_factor(self): assert g.height_factor == 1.0 - def test_leg_length_warning(self, get_sample_data): + def test_leg_length_warning(self, get_sample_data, caplog): data = get_sample_data( self.sample_data_file, self.sample_data_keys ) data['height'] = None - with pytest.warns(UserWarning): - self.process._predict(**data) + self.process.predict(**data) + assert caplog.records[0].levelname == 'WARNING' + assert 'height not provided' in caplog.records[0].message def test_sample_rate_error(self, get_sample_data): data = get_sample_data( @@ -235,7 +233,7 @@ def test_sample_rate_error(self, get_sample_data): data['time'] = arange(0, 300, 0.5) with pytest.raises(LowFrequencyError): - self.process._predict(**data) + self.process.predict(**data) def test_gait_predictions_error(self, get_sample_data): data = get_sample_data( @@ -245,7 +243,7 @@ def test_gait_predictions_error(self, get_sample_data): data['gait_pred'] = arange(0, 1, 0.1) with pytest.raises(ValueError): - self.process._predict(**data) + self.process.predict(**data) def test_add_metrics(self): g = Gait() From 8a9d570efebc0a14084a09b3bd3ad8ade4daeb56 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:16:49 -0500 Subject: [PATCH 29/33] Make variable input errors raise warnings, not logging.warning --- src/skimu/gait/gait.py | 3 ++- src/skimu/read/axivity.py | 2 +- src/skimu/read/geneactiv.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/skimu/gait/gait.py b/src/skimu/gait/gait.py index defaa4e7..6914ebaf 100644 --- a/src/skimu/gait/gait.py +++ b/src/skimu/gait/gait.py @@ -5,6 +5,7 @@ Pfizer DMTI 2020 """ from collections.abc import Iterable +from warnings import warn from numpy import mean, diff, abs, argmax, sign, round, array @@ -237,7 +238,7 @@ def predict(self, time=None, accel=None, *, gyro=None, height=None, gait_pred=No ) if height is None: - self.logger.warning('height not provided, not computing spatial metrics') + warn('height not provided, not computing spatial metrics', UserWarning) leg_length = None else: # height factor is set to 1 if providing leg length diff --git a/src/skimu/read/axivity.py b/src/skimu/read/axivity.py index ef488485..1e4508bb 100644 --- a/src/skimu/read/axivity.py +++ b/src/skimu/read/axivity.py @@ -113,7 +113,7 @@ def predict(self, file=None, **kwargs): if not isinstance(file, str): file = str(file) if file[-3:] != "cwa": - self.logger.warning("File extension is not '.cwa'") + warn("File extension is not expected '.cwa'", UserWarning) # read the file meta, imudata, ts, idx, light = read_cwa(file, self.base, self.period) diff --git a/src/skimu/read/geneactiv.py b/src/skimu/read/geneactiv.py index 1fc6f200..af66164b 100644 --- a/src/skimu/read/geneactiv.py +++ b/src/skimu/read/geneactiv.py @@ -106,7 +106,7 @@ def predict(self, file=None, **kwargs): if not isinstance(file, str): file = str(file) if file[-3:] != "bin": - self.logger.warning("File extension is not '.bin'") + warn("File extension is not expected '.bin'", UserWarning) # read the file nmax, hdr, acc, time, light, temp, idx, dtime = bin_convert(file, self.base, self.period) From 82487ed1154107d8635531fef145916077bb2b9a Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:17:44 -0500 Subject: [PATCH 30/33] Fix test for going back to userwarning --- test/gait/test_gait.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/gait/test_gait.py b/test/gait/test_gait.py index a3ed9a97..2359ed28 100644 --- a/test/gait/test_gait.py +++ b/test/gait/test_gait.py @@ -221,9 +221,8 @@ def test_leg_length_warning(self, get_sample_data, caplog): ) data['height'] = None - self.process.predict(**data) - assert caplog.records[0].levelname == 'WARNING' - assert 'height not provided' in caplog.records[0].message + with pytest.warns(UserWarning): + self.process.predict(**data) def test_sample_rate_error(self, get_sample_data): data = get_sample_data( From 1794cfe2413104fc58c2f389f1b60f24823ff269 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:18:39 -0500 Subject: [PATCH 31/33] remove old _return_results arg --- test/skimu/test_base_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/skimu/test_base_process.py b/test/skimu/test_base_process.py index 0946c297..6d1cac50 100644 --- a/test/skimu/test_base_process.py +++ b/test/skimu/test_base_process.py @@ -5,7 +5,7 @@ class TestBaseProcess: def test_init_repr_str(self): - bp = _BaseProcess(True, kw1=None, kw2=0, kw3=0.5, kw4='str', kw5=False) + bp = _BaseProcess(kw1=None, kw2=0, kw3=0.5, kw4='str', kw5=False) r = "_BaseProcess(kw1=None, kw2=0, kw3=0.5, kw4='str', kw5=False)" From 8e9bd2505b5811526ac7694f815aecd2b1d22f2c Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:18:45 -0500 Subject: [PATCH 32/33] remove old _return_results arg --- test/skimu/test_base_process.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/skimu/test_base_process.py b/test/skimu/test_base_process.py index 6d1cac50..4defc913 100644 --- a/test/skimu/test_base_process.py +++ b/test/skimu/test_base_process.py @@ -10,6 +10,5 @@ def test_init_repr_str(self): r = "_BaseProcess(kw1=None, kw2=0, kw3=0.5, kw4='str', kw5=False)" assert bp._name == "_BaseProcess" - assert bp._return_result assert repr(bp) == r assert str(bp) == "_BaseProcess" From 3515256a6a80a42f7ed39f4a5b9707169eb65e25 Mon Sep 17 00:00:00 2001 From: Lukas Adamowicz Date: Tue, 10 Nov 2020 15:19:33 -0500 Subject: [PATCH 33/33] Remove keys that are no longer saved --- test/skimu/test_pipeline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/skimu/test_pipeline.py b/test/skimu/test_pipeline.py index 79295d16..92541e2b 100644 --- a/test/skimu/test_pipeline.py +++ b/test/skimu/test_pipeline.py @@ -13,10 +13,7 @@ class TestPipeline: gait_keys = [ - 'IC', - 'FC', 'delta h', - 'b valid cycle', 'PARAM:stride time', 'PARAM:stance time', 'PARAM:swing time',