biomechzoo 0.4.10__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of biomechzoo might be problematic. Click here for more details.

@@ -13,10 +13,10 @@ def filter_data(data, ch, filt=None):
13
13
  The name(s) of the channel(s) to filter.
14
14
  filt : dict, optional
15
15
  Dictionary specifying filter parameters. Keys may include:
16
- - 'type': 'butter' (default)
16
+ - 'ftype': 'butter' (default)
17
17
  - 'order': filter order (default: 4)
18
18
  - 'cutoff': cutoff frequency or tuple (Hz)
19
- - 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'low')
19
+ - 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'lowpass')
20
20
 
21
21
  Returns
22
22
  -------
@@ -25,32 +25,34 @@ def filter_data(data, ch, filt=None):
25
25
  """
26
26
 
27
27
  if filt is None:
28
- filt = {}
28
+ filt = {'ftype': 'butter',
29
+ 'order': 4,
30
+ 'cutoff': 10,
31
+ 'btype': 'lowpass',
32
+ 'filtfilt': True}
29
33
 
30
34
  if isinstance(ch, str):
31
35
  ch = [ch]
32
36
 
33
- analog_channels = data['zoosystem']['Analog']['Channels']
34
- if analog_channels:
35
- analog_freq = data['zoosystem']['Analog']['Freq']
36
- video_channels = data['zoosystem']['Video']['Channels']
37
- if video_channels:
38
- video_freq = data['zoosystem']['Video']['Freq']
39
-
37
+ # loop through all channels and filter
40
38
  for c in ch:
41
39
  if c not in data:
42
40
  raise KeyError('Channel {} not found in data'.format(c))
43
41
 
44
42
  if 'fs' not in filt:
43
+
44
+ video_channels = data['zoosystem']['Video']['Channels']
45
+ analog_channels = data['zoosystem']['Analog']['Channels']
46
+
45
47
  if c in analog_channels:
46
- filt['fs'] = analog_freq
47
- elif c in video_freq:
48
- filt['fs'] = video_freq
48
+ filt['fs'] = data['zoosystem']['Analog']['Freq']
49
+ elif c in video_channels:
50
+ filt['fs'] = data['zoosystem']['Video']['Freq']
49
51
  else:
50
- raise ValueError('frequency not provided and cannot be inferred from zoosystem for channel'.format(c))
52
+ raise ValueError('Channel not analog or video')
51
53
 
52
54
  signal_raw = data[c]['line']
53
- signal_filtered = filter_line(signal_raw, filt)
55
+ signal_filtered = filter_line(signal_raw=signal_raw, filt=filt)
54
56
  data[c]['line'] = signal_filtered
55
57
 
56
58
  return data
@@ -1,88 +1,85 @@
1
1
  import numpy as np
2
- from scipy.signal import butter, filtfilt
2
+ import scipy.signal as sgl
3
3
 
4
4
 
5
- def filter_line(signal_raw, filt):
6
- """ filter an array
5
+ def filter_line(signal_raw, filt=None, fs=None):
6
+ """Filter an array using a Butterworth filter."""
7
+ #todo: verify that filter is working correctly
8
+ #todo add more filters
9
+ #todo: consider using kineticstoolkit
7
10
 
8
- Arguments
9
- ----------
10
- signal_raw : n, or n x 3 array signal to be filtered
11
- filt : dict, optional
12
- Dictionary specifying filter parameters. Keys may include:
13
- - 'type': 'butter' (default)
14
- - 'order': filter order (default: 4)
15
- - 'cutoff': cutoff frequency or tuple (Hz)
16
- - 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'low')
17
- - 'fs' frequency
18
-
19
- Returns
20
- -------
21
- signal_filtered: filtered version of signal_raw"""
22
- # todo allow for missing frequency to be obtained from zoosystem metadata
23
11
  if filt is None:
24
- filt = {}
25
- if filt['type'] is 'butterworth':
26
- filt['type'] = 'butter'
27
- # Set default filter parameters
28
- ftype = filt.get('type', 'butter')
29
- order = filt.get('order', 4)
30
- cutoff = filt.get('cutoff', None)
31
- btype = filt.get('btype', 'low')
32
- fs = filt.get('fs', None)
33
-
34
- if ftype != 'butter':
35
- raise NotImplementedError(f"Filter type '{ftype}' not implemented.")
36
-
37
- if fs is None:
38
- raise ValueError("Sampling frequency 'fs' must be specified in filt.")
39
-
40
- if cutoff is None:
41
- raise ValueError("Cutoff frequency 'cutoff' must be specified in filt.")
12
+ filt = {'ftype': 'butter',
13
+ 'order': 4,
14
+ 'cutoff': 10,
15
+ 'btype': 'lowpass',
16
+ 'filtfilt': True}
17
+ if fs is None:
18
+ raise ValueError('fs is required if no filt is specified')
42
19
 
20
+ else:
21
+ if 'fs' not in filt:
22
+ raise ValueError('fs is a required key of filt')
23
+
24
+ # Normalize filter type strings
25
+ if filt['ftype'] == 'butterworth':
26
+ filt['ftype'] = 'butter'
27
+ if filt['btype'] is 'low':
28
+ filt['btype'] = 'lowpass'
29
+ if filt['btype'] is 'high':
30
+ filt['btype'] = 'highpass'
31
+
32
+ # Extract parameters
33
+ ftype = filt['ftype']
34
+ order = filt['order']
35
+ cutoff = filt['cutoff']
36
+ btype = filt['btype']
37
+ filtfilt = filt['filtfilt']
38
+ fs = filt['fs']
39
+
40
+ # prepare normalized cutoff(s)
43
41
  nyq = 0.5 * fs
44
- norm_cutoff = np.array(cutoff) / nyq
45
-
46
- b, a = butter(order, norm_cutoff, btype=btype, analog=False)
42
+ norm_cutoff = np.atleast_1d(np.array(cutoff) / nyq)
47
43
 
48
- if signal_raw.ndim == 1:
49
- signal_filtered = filtfilt(b, a, signal_raw)
44
+ if ftype is 'butter':
45
+ signal_filtered = kt_butter(ts=signal_raw, fc=norm_cutoff, fs=fs, order=order, btype=btype, filtfilt=filtfilt)
50
46
  else:
51
- # Apply filter to each column if multivariate
52
- signal_filtered = np.array([filtfilt(b, a, signal_raw[:, i]) for i in range(signal_raw.shape[1])]).T
47
+ raise NotImplementedError(f"Filter type '{ftype}' not implemented.")
53
48
 
54
49
  return signal_filtered
55
50
 
56
51
 
57
- if __name__ == '__main__':
58
- """ -------TESTING--------"""
59
- import os
60
- import matplotlib.pyplot as plt
61
- from src.biomechzoo.utils.zload import zload
62
- current_dir = os.path.dirname(os.path.abspath(__file__))
63
- project_root = os.path.dirname(current_dir)
64
- fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
65
- data = zload(fl)
66
- data = data['data']
67
- signal_raw = data['ForceFz1']['line']
68
- filt = {'type': 'butterworth',
69
- 'order': 3,
70
- 'cutoff': 20,
71
- 'btype': 'low',
72
- 'fs': data['zoosystem']['Analog']['Freq']
73
- }
74
- signal_filtered = filter_line(signal_raw, filt)
75
-
76
- # now plot
77
- plt.figure(figsize=(10, 4))
78
- plt.plot(signal_raw, label='Raw', alpha=0.6)
79
- plt.plot(signal_filtered, label='Filtered', linewidth=2)
80
- plt.xlabel('Frame')
81
- plt.ylabel('Amplitude')
82
- plt.title('Testing filter_line')
83
- plt.legend()
84
- plt.grid(True)
85
- plt.tight_layout()
86
- plt.show()
52
+ def kt_butter(ts, fc, fs, order=2, btype='lowpass', filtfilt=True):
53
+ """
54
+ Apply a Butterworth filter to data.
55
+
56
+ Parameters
57
+ ----------
58
+ ts, ndarray, 1d.
59
+ fc, Cut-off frequency in Hz. This is a float for single-frequency filters
60
+ (lowpass, highpass), or a tuple of two floats (e.g., (10., 13.)
61
+ for two-frequency filters (bandpass, bandstop)).
62
+ order, Optional. Order of the filter. Default is 2.
63
+ btype, Optional. Can be either "lowpass", "highpass", "bandpass" or
64
+ "bandstop". Default is "lowpass".
65
+ filtfilt, Optional. If True, the filter is applied two times in reverse direction
66
+ to eliminate time lag. If False, the filter is applied only in forward
67
+ direction. Default is True.
68
+
69
+ Returns
70
+ -------
71
+ ts_f, A copy of the input data which each data being filtered.
72
+
73
+ Notes:
74
+ - This code was adapted from kineticstoolkit Thanks @felxi
75
+ """
76
+
77
+ sos = sgl.butter(order, fc, btype, analog=False, output="sos", fs=fs)
87
78
 
79
+ # Filter
80
+ if filtfilt:
81
+ ts_f = sgl.sosfiltfilt(sos, ts, axis=0)
82
+ else:
83
+ ts_f = sgl.sosfilt(sos,ts, axis=0)
88
84
 
85
+ return ts_f
@@ -1,4 +1,5 @@
1
1
  import warnings
2
+ import copy
2
3
  from biomechzoo.biomech_ops.normalize_line import normalize_line
3
4
 
4
5
 
@@ -14,14 +15,14 @@ def normalize_data(data, nlength=101):
14
15
  """
15
16
 
16
17
  # normalize channel length
17
- data_new = data.copy()
18
+ data_new = copy.deepcopy(data)
18
19
  for ch_name, ch_data in data_new.items():
19
20
  if ch_name != 'zoosystem':
20
21
  ch_data_line = ch_data['line']
21
22
  ch_data_event = ch_data['event']
22
23
  ch_data_normalized = normalize_line(ch_data_line, nlength)
23
- data_new[ch_name]['line'] = ch_data_normalized
24
- data_new[ch_name]['event'] = ch_data_event
24
+ data_new[ch_name]['line'] = ch_data_normalized
25
+ data_new[ch_name]['event'] = ch_data_event
25
26
  warnings.warn('event data have not been normalized')
26
27
 
27
28
  # update zoosystem
biomechzoo/biomechzoo.py CHANGED
@@ -12,6 +12,7 @@ from biomechzoo.conversion.table2zoo_data import table2zoo_data
12
12
  from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
13
13
  from biomechzoo.processing.removechannel_data import removechannel_data
14
14
  from biomechzoo.processing.renamechannel_data import renamechannel_data
15
+ from biomechzoo.processing.removeevent_data import removeevent_data
15
16
  from biomechzoo.processing.explodechannel_data import explodechannel_data
16
17
  from biomechzoo.processing.addevent_data import addevent_data
17
18
  from biomechzoo.processing.partition_data import partition_data
@@ -19,6 +20,7 @@ from biomechzoo.processing.renameevent_data import renameevent_data
19
20
  from biomechzoo.biomech_ops.normalize_data import normalize_data
20
21
  from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
21
22
  from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
23
+ from biomechzoo.biomech_ops.filter_data import filter_data
22
24
 
23
25
  class BiomechZoo:
24
26
  def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
@@ -61,6 +63,8 @@ class BiomechZoo:
61
63
  in_folder_path = os.path.dirname(in_folder)
62
64
  self.in_folder = os.path.join(in_folder_path, out_folder)
63
65
 
66
+ batchdisp('all files saved to: {}'.format(self.in_folder ), level=1, verbose=self.verbose)
67
+
64
68
  def mvnx2zoo(self, out_folder=None, inplace=False):
65
69
  """ Converts all .mvnx files in the folder to .zoo format """
66
70
  start_time = time.time()
@@ -75,7 +79,7 @@ class BiomechZoo:
75
79
  f_zoo = f.replace('.mvnx', '.zoo')
76
80
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
77
81
  method_name = inspect.currentframe().f_code.co_name
78
- batchdisp('{} conversion complete for {} files'.format(method_name, len(fl)), level=1, verbose=verbose)
82
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
79
83
  # Update self.folder after processing
80
84
  self._update_folder(out_folder, inplace, in_folder)
81
85
 
@@ -99,17 +103,21 @@ class BiomechZoo:
99
103
  # Update self.folder after processing
100
104
  self._update_folder(out_folder, inplace, in_folder)
101
105
 
102
- def table2zoo(self, out_folder=None, inplace=None, skip_rows=0, extension='csv'):
106
+ def table2zoo(self, extension, out_folder=None, inplace=None, skip_rows=0, freq=None):
103
107
  """ Converts generic .csv file in the folder to .zoo format """
104
108
  start_time = time.time()
105
109
  verbose = self.verbose
106
110
  in_folder = self.in_folder
111
+
112
+ if extension.startswith('.'):
113
+ extension = extension[1:]
114
+
107
115
  if inplace is None:
108
116
  inplace = self.inplace
109
117
  fl = engine(in_folder, extension=extension, name_contains=self.name_contains, subfolders=self.subfolders)
110
118
  for f in fl:
111
119
  batchdisp('converting {} to zoo for {}'.format(extension, f), level=2, verbose=verbose)
112
- data = table2zoo_data(f, type=extension, skip_rows=skip_rows)
120
+ data = table2zoo_data(f, extension=extension, skip_rows=skip_rows, freq=freq)
113
121
  f_zoo = f.replace(extension, '.zoo')
114
122
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
115
123
  method_name = inspect.currentframe().f_code.co_name
@@ -118,8 +126,13 @@ class BiomechZoo:
118
126
  self._update_folder(out_folder, inplace, in_folder)
119
127
 
120
128
  def xls2zoo(self, out_folder=None, inplace=None):
121
- """ Converts generic .xls file in the folder to .zoo format """
122
- raise NotImplementedError
129
+ raise NotImplementedError('Use table2zoo instead')
130
+
131
+ def csv2zoo(self, out_folder=None, inplace=None):
132
+ raise NotImplementedError('Use table2zoo instead')
133
+
134
+ def parquet2zoo(self, out_folder=None, inplace=None):
135
+ raise NotImplementedError('Use table2zoo instead')
123
136
 
124
137
  def phase_angle(self, ch, out_folder=None, inplace=None):
125
138
  """ computes phase angles"""
@@ -184,7 +197,8 @@ class BiomechZoo:
184
197
  batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
185
198
  verbose=verbose)
186
199
  data_new = split_trial(data, start, end)
187
- zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
200
+ if data_new is not None:
201
+ zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
188
202
  method_name = inspect.currentframe().f_code.co_name
189
203
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
190
204
 
@@ -266,6 +280,27 @@ class BiomechZoo:
266
280
  # Update self.folder after processing
267
281
  self._update_folder(out_folder, inplace, in_folder)
268
282
 
283
+
284
+ def removeevent(self, events, mode='remove', out_folder=None, inplace=None):
285
+ """ removes channels from zoo files """
286
+ start_time = time.time()
287
+ verbose = self.verbose
288
+ in_folder = self.in_folder
289
+ if inplace is None:
290
+ inplace = self.inplace
291
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
292
+ for f in fl:
293
+ batchdisp('removing events {} for {}'.format(events, f), level=2, verbose=verbose)
294
+ data = zload(f)
295
+ data = removeevent_data(data, events, mode)
296
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
297
+ method_name = inspect.currentframe().f_code.co_name
298
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
299
+
300
+ # Update self.folder after processing
301
+ self._update_folder(out_folder, inplace, in_folder)
302
+
303
+
269
304
  def explodechannel(self, out_folder=None, inplace=None):
270
305
  """ explodes all channels in a zoo file """
271
306
  start_time = time.time()
@@ -306,7 +341,7 @@ class BiomechZoo:
306
341
  # Update self.folder after processing
307
342
  self._update_folder(out_folder, inplace, in_folder)
308
343
 
309
- def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
344
+ def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None, constant=None):
310
345
  """ adds events of type evt_type with name evt_name to channel ch """
311
346
  start_time = time.time()
312
347
  verbose = self.verbose
@@ -318,7 +353,7 @@ class BiomechZoo:
318
353
  if verbose:
319
354
  batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
320
355
  data = zload(f)
321
- data = addevent_data(data, ch, event_type, event_name)
356
+ data = addevent_data(data, ch, event_type, event_name, constant)
322
357
  zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
323
358
  method_name = inspect.currentframe().f_code.co_name
324
359
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
@@ -346,28 +381,22 @@ class BiomechZoo:
346
381
  self._update_folder(out_folder, inplace, in_folder)
347
382
 
348
383
  def filter(self, ch, filt=None, out_folder=None, inplace=None):
349
- raise NotImplementedError
350
- # verbose = self.verbose
351
- # in_folder = self.in_folder
352
- # if inplace is None:
353
- # inplace = self.inplace
354
- #
355
- # # set filter type
356
- # if filt is None:
357
- # filt = {'type': 'butterworth',
358
- # 'order': 3,
359
- # 'pass': 'lowpass'}
360
- #
361
- # fl = engine(in_folder)
362
- # for f in fl:
363
- # batchdisp('filtering data in channels {} for {}'.format(ch, f), level=2, verbose=verbose)
364
- # data = zload(f)
365
- # data = filter_data(data, ch, filt)
366
- # zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder, verbose=verbose)
367
- # method_name = inspect.currentframe().f_code.co_name
368
- # batchdisp('{} computation complete for {} file(s)'.format(method_name, len(fl)), level=1, verbose=verbose)
369
- #
370
- # # Update self.folder after processing
371
- # self._update_folder(out_folder, inplace, in_folder)
372
-
384
+ """ filter data"""
385
+ start_time = time.time()
386
+ verbose = self.verbose
387
+ in_folder = self.in_folder
388
+ if inplace is None:
389
+ inplace = self.inplace
390
+ fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
391
+ for f in fl:
392
+ if verbose:
393
+ batchdisp('filtering data for channel {} in {}'.format(ch, f), level=2, verbose=verbose)
394
+ data = zload(f)
395
+ data = filter_data(data, ch, filt)
396
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
397
+ method_name = inspect.currentframe().f_code.co_name
398
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
399
+ level=1, verbose=verbose)
400
+ # Update self.folder after processing
401
+ self._update_folder(out_folder, inplace, in_folder)
373
402
 
@@ -8,10 +8,14 @@ def c3d2zoo_data(c3d_obj):
8
8
  - data (dict): Zoo dictionary with 'line' and 'event' fields per channel.
9
9
  """
10
10
  data = {}
11
-
11
+ data['zoosystem'] = set_zoosystem()
12
+ video_freq = None
13
+ analog_freq = None
14
+ # extract "video" data
12
15
  if 'points' in c3d_obj['data']:
13
16
  points = c3d_obj['data']['points'] # shape: (4, n_markers, n_frames)
14
- labels = c3d_obj['parameters']['POINT']['LABELS']['value']
17
+ labels = list(c3d_obj['parameters']['POINT']['LABELS']['value'])
18
+ video_freq = int(c3d_obj['parameters']['POINT']['RATE']['value'][0])
15
19
  for i, label in enumerate(labels):
16
20
  line_data = points[:3, i, :].T # shape: (frames, 3)
17
21
  data[label] = {
@@ -19,9 +23,32 @@ def c3d2zoo_data(c3d_obj):
19
23
  'event': {} # empty for now
20
24
  }
21
25
 
22
- params = c3d_obj['parameters']
23
- video_freq = c3d_obj['parameters']['POINT']['RATE']['value'][0]
24
- if 'EVENT' in params and 'TIMES' in params['EVENT']:
26
+ data['zoosystem']['Video']['Freq'] = video_freq
27
+ data['zoosystem']['Video']['Channels'] = labels
28
+
29
+ if 'analogs' in c3d_obj['data']:
30
+ analog_data = c3d_obj['data']['analogs'] # shape: (subframes, n_analog_channels, n_frames)
31
+ analog_labels = list(c3d_obj['parameters']['ANALOG']['LABELS']['value'])
32
+ analog_freq = int(c3d_obj['parameters']['ANALOG']['RATE']['value'][0])
33
+ # Flatten to 2D: (n_samples, n_channels)
34
+ # ezc3d stores analogs as subframes per frame, so we flatten across all
35
+ n_subframes, n_channels, n_frames = analog_data.shape
36
+ analog_data = analog_data.reshape(n_subframes * n_frames, n_channels)
37
+
38
+ for i, label in enumerate(analog_labels):
39
+ line_data = analog_data[:, i].reshape(-1, 1) # shape: (samples, 1)
40
+ data[label] = {
41
+ 'line': line_data,
42
+ 'event': {},
43
+ }
44
+
45
+ data['zoosystem']['Analog']['Freq'] = analog_freq
46
+ data['zoosystem']['Analog']['Channels'] = analog_labels
47
+
48
+ # extract event information
49
+ params = c3d_obj['parameters']
50
+ if 'EVENT' in params and 'TIMES' in params['EVENT']:
51
+ if 'points' in c3d_obj['data']:
25
52
  times_array = params['EVENT']['TIMES']['value']
26
53
  frames = times_array[1] # should be time depending on C3D file
27
54
 
@@ -61,8 +88,8 @@ def c3d2zoo_data(c3d_obj):
61
88
  else:
62
89
  data[labels[0]]['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
63
90
 
64
- # todo add relevant meta data to zoosystem
65
- data['zoosystem'] = set_zoosystem()
66
- data['zoosystem']['Analog']['Freq'] = int(params['ANALOG']['RATE']['value'][0])
91
+ # add more zoosystem
92
+ if analog_freq is not None and video_freq is not None:
93
+ data['zoosystem']['AVR'] = analog_freq/video_freq
67
94
 
68
95
  return data
@@ -9,8 +9,7 @@ def mvnx2zoo_data(fl):
9
9
  mvnx_file = load_mvnx(fl)
10
10
 
11
11
  # create zoo data dict
12
- data = {}
13
-
12
+ data = {'zoosystem': set_zoosystem()}
14
13
  # extract joint angle data (All JOINTS may not exist in a given dataset)
15
14
  for key, val in JOINTS.items():
16
15
  try:
@@ -58,7 +57,6 @@ def is_valid_for_zoo(val):
58
57
 
59
58
  def _get_meta_info(fl, mvnx_file, data):
60
59
  # todo: add more, see mvnx_file object
61
- data['zoosystem'] = set_zoosystem(fl)
62
60
  data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
63
61
  data['zoosystem']['mvnx_version'] = mvnx_file.version
64
62
  data['zoosystem']['mvnx_configuration'] = mvnx_file.configuration
@@ -6,42 +6,59 @@ from biomechzoo.utils.set_zoosystem import set_zoosystem
6
6
  from biomechzoo.utils.compute_sampling_rate_from_time import compute_sampling_rate_from_time
7
7
 
8
8
 
9
- def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
10
- # todo: check calculation for sampling rate
9
+ def table2zoo_data(fl, extension, skip_rows=0, freq=None):
11
10
 
12
- if type not in ['csv', 'parquet']:
13
- raise NotImplementedError('Only csv and parquet currently supported')
11
+ if extension == 'csv':
12
+ df, metadata, freq = _csv2zoo(fl, skip_rows=skip_rows, freq=freq)
14
13
 
15
- # Read header lines until 'endheader'
16
- metadata = {}
17
- if type == 'csv' and skip_rows > 0:
18
- header_lines = []
19
- with open(csv_path, 'r') as f:
20
- for line in f:
21
- header_lines.append(line.strip())
22
- if line.strip().lower() == 'endheader':
23
- break
24
- # Parse metadata
25
- metadata = _parse_metadata(header_lines)
26
-
27
- if type == 'csv':
28
- df = pd.read_csv(csv_path, skiprows=skip_rows)
29
- elif type =='parquet':
30
- df = pd.read_parquet(csv_path)
14
+ elif extension == 'parquet':
15
+ df, metadata, freq = _parquet2zoo(fl, skip_rows=skip_rows, freq=freq)
31
16
  else:
32
- raise ValueError('type must be csv or parquet')
33
-
34
- # Use all columns
35
- data = df.iloc[:, 0:]
17
+ raise ValueError('extension {} not implemented'.format(extension))
36
18
 
37
19
  # assemble zoo data
38
- zoo_data = {}
39
- for ch in data.columns:
40
- zoo_data[ch] = {
41
- 'line': data[ch].values,
20
+ data = {'zoosystem': set_zoosystem()}
21
+ for ch in df.columns:
22
+ data[ch] = {
23
+ 'line': df[ch].values,
42
24
  'event': []
43
25
  }
44
26
 
27
+
28
+ # now try to calculate freq from a time column
29
+ if freq is None:
30
+ time_col = [col for col in df.columns if 'time' in col.lower()]
31
+ if time_col is not None and len(time_col) > 0:
32
+ time_data = df[time_col].to_numpy()[:, 0]
33
+ freq = compute_sampling_rate_from_time(time_data)
34
+ else:
35
+ raise ValueError('Unable to compute sampling rate for time column, please specify a sampling frequency'
36
+ )
37
+ # add metadata
38
+ data['zoosystem']['Video']['Freq'] = freq
39
+ data['zoosystem']['Analog']['Freq'] = 'None'
40
+
41
+ if metadata is not None:
42
+ data['zoosystem']['Other'] = metadata
43
+
44
+ return data
45
+
46
+
47
+ def _parquet2zoo(fl, skip_rows=0, freq=None):
48
+ df = pd.read_parquet(fl)
49
+ metadata = None
50
+ return df, metadata, freq
51
+
52
+ def _csv2zoo(fl, skip_rows=0, freq=None):
53
+ header_lines = []
54
+ with open(fl, 'r') as f:
55
+ for line in f:
56
+ header_lines.append(line.strip())
57
+ if line.strip().lower() == 'endheader':
58
+ break
59
+ # Parse metadata
60
+ metadata = _parse_metadata(header_lines)
61
+
45
62
  # try to find frequency in metadata
46
63
  if freq is None:
47
64
  if 'freq' in metadata:
@@ -51,22 +68,12 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
51
68
  else:
52
69
  freq = None # or raise an error
53
70
 
54
- # now try to calculate freq from a time column
55
- if freq is None:
56
- time_col = [col for col in df.columns if 'time' in col.lower()]
57
- if time_col is not None:
58
- time_data = df[time_col].to_numpy()[:, 0]
59
- freq = compute_sampling_rate_from_time(time_data)
71
+ # read csv
72
+ df = pd.read_csv(fl, skiprows=skip_rows)
73
+
74
+ return df, metadata, freq
60
75
 
61
- # add metadata
62
- # todo update zoosystem to match biomechzoo requirements
63
- zoo_data['zoosystem'] = set_zoosystem(csv_path)
64
- zoo_data['zoosystem']['Video']['Freq'] = freq
65
- zoo_data['zoosystem']['Analog']['Freq'] = 'None'
66
- if 'version' in metadata:
67
- zoo_data['zoosystem']['collection_system_version'] = metadata['version']
68
76
 
69
- return zoo_data
70
77
 
71
78
 
72
79
  def _parse_metadata(header_lines):
@@ -96,6 +103,8 @@ def _parse_metadata(header_lines):
96
103
  return metadata
97
104
 
98
105
 
106
+
107
+
99
108
  if __name__ == '__main__':
100
109
  """ for unit testing"""
101
110
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -1,23 +1,27 @@
1
1
  import numpy as np
2
+ import copy
3
+ import warnings
4
+ from biomechzoo.utils.peak_sign import peak_sign
2
5
 
6
+ def addevent_data(data, channels, ename, etype, constant=None):
3
7
 
4
- def addevent_data(data, ch, ename, etype):
5
- if isinstance(ch, str):
6
- ch = [ch]
8
+ data_new = copy.deepcopy(data)
7
9
 
8
- if len(ch) == 1 and ch[0].lower() == 'all':
9
- ch = [key for key in data if key != 'zoosystem']
10
+ if isinstance(channels, str):
11
+ channels = [channels]
10
12
 
11
- for channel in ch:
13
+ if len(channels) == 1 and channels[0].lower() == 'all':
14
+ channels = [key for key in data if key != 'zoosystem']
15
+
16
+ for channel in channels:
12
17
  if ename == '':
13
18
  data[channel]['event'] = {}
14
19
  continue
15
20
 
16
21
  if channel not in data:
17
- print(f'Channel {channel} does not exist')
18
- continue
22
+ raise KeyError('Channel {} does not exist'.format(channel))
19
23
 
20
- yd = data[channel]['line'] # 1D array
24
+ yd = data_new[channel]['line'] # 1D array
21
25
  etype = etype.lower()
22
26
  if etype == 'absmax':
23
27
  exd = int(np.argmax(np.abs(yd)))
@@ -42,13 +46,51 @@ def addevent_data(data, ch, ename, etype):
42
46
  exd = max_stance(yd)
43
47
  eyd = float(yd[exd])
44
48
  eyd = float(yd[exd])
49
+ elif etype in ['fs_fp', 'fo_fp']:
50
+ # --- Handle constant ---
51
+ if constant is None:
52
+ print('Warning: Force plate threshold not set, defaulting to 0.')
53
+ constant = 0.0
54
+
55
+ # --- Check sampling rates ---
56
+ AVR = data['zoosystem']['AVR']
57
+ if AVR != 1:
58
+ warnings.warn('Video and Analog channels must be at the same sampling rate or events will be incorrect.')
59
+
60
+ # --- Handle units ---
61
+ units = data['zoosystem']['Units']['Forces']
62
+ if units == 'N/kg':
63
+ m = data['zoosystem']['Anthro']['Bodymass']
64
+ else:
65
+ m = 1.0
66
+
67
+ # --- Extract force signal ---
68
+ if '_' not in channel:
69
+ yd = data_new[channel]['line'][:, 2] # looking for GRF Z
70
+ else:
71
+ yd = data_new[channel]['line']
72
+
73
+ # --- Determine peak sign ---
74
+ peak = peak_sign(yd) # user-defined function
75
+
76
+ # --- Find threshold crossing ---
77
+ threshold_signal = peak * yd * m
78
+ if 'fs' in etype:
79
+ exd_array = np.where(threshold_signal > constant)[0]
80
+ exd = exd_array[0] - 1 # MATLAB indexing correction
81
+ eyd = yd[exd]
82
+ else: # 'FO' type
83
+ exd_array = np.where(threshold_signal > constant)[0]
84
+ exd = exd_array[-1] + 1
85
+ eyd = yd[exd]
86
+
45
87
  else:
46
88
  raise ValueError(f'Unknown event type: {etype}')
47
89
 
48
90
  # Add event to the channel's event dict
49
- data[channel]['event'][ename] = [exd, eyd, 0]
91
+ data_new[channel]['event'][ename] = [exd, eyd, 0]
50
92
 
51
- return data
93
+ return data_new
52
94
 
53
95
  def max_stance(yd):
54
96
  """ extracts max from first 40% of the gait cycle"""
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ import numpy as np
2
3
 
3
4
  def explodechannel_data(data, channels=None):
4
5
  """ Explodes 3D channels (n x 3 arrays) into separate X, Y, Z channels.
@@ -6,13 +7,23 @@ def explodechannel_data(data, channels=None):
6
7
  Arguments:
7
8
  data (dict): Zoo data loaded from a file
8
9
  channels (list of str or None): Channels to explode.
9
- If None, explode all channels with 'line' shaped (n x 3).
10
+ If None, explode all channels with 'line' shaped (n x 3).
10
11
 
11
12
  Returns:
12
13
  data_new (dict): Modified zoo dictionary with exploded channels.
13
14
  """
14
-
15
15
  data_new = copy.deepcopy(data)
16
+
17
+ # Ensure zoosystem channel lists are Python lists
18
+ for sys in ['Video', 'Analog']:
19
+ if sys in data_new.get('zoosystem', {}):
20
+ ch_list = data_new['zoosystem'][sys].get('Channels', [])
21
+ if isinstance(ch_list, np.ndarray):
22
+ ch_list = ch_list.tolist()
23
+ # strip whitespace
24
+ ch_list = [str(ch).strip() for ch in ch_list]
25
+ data_new['zoosystem'][sys]['Channels'] = ch_list
26
+
16
27
  # Find default channels if none provided
17
28
  if channels is None:
18
29
  channels = []
@@ -39,9 +50,20 @@ def explodechannel_data(data, channels=None):
39
50
  key = ch + axis
40
51
  data_new[key] = {
41
52
  'line': line,
42
- 'event': data[ch]['event']}
53
+ 'event': data_new[ch]['event']}
43
54
 
44
55
  # Remove original channel
45
56
  del data_new[ch]
46
57
 
58
+ # --- Update zoosystem lists ---
59
+ for sys in ['Video', 'Analog']:
60
+ if sys in data_new['zoosystem']:
61
+ ch_list = data_new['zoosystem'][sys]['Channels']
62
+ if ch in ch_list:
63
+ # Remove original channel
64
+ ch_list = [c for c in ch_list if c != ch]
65
+ # Add exploded channels
66
+ ch_list.extend([ch + '_x', ch + '_y', ch + '_z'])
67
+ data_new['zoosystem'][sys]['Channels'] = ch_list
68
+
47
69
  return data_new
@@ -1,6 +1,7 @@
1
1
  from biomechzoo.utils.findfield import findfield
2
2
  import warnings
3
3
  import copy
4
+ import numpy as np
4
5
 
5
6
 
6
7
  def partition_data(data, evt_start, evt_end):
@@ -13,16 +14,19 @@ def partition_data(data, evt_start, evt_end):
13
14
  if e1 is None or e2 is None or len(e1) == 0 or len(e2) == 0:
14
15
  raise ValueError(f"Event not found: evt_start='{evt_start}' returned {e1}, evt_end='{evt_end}' returned {e2}")
15
16
 
17
+ # convert to int and get first value
18
+ e1 = int(e1[0])
19
+ e2 = int(e2[0])
20
+
16
21
  data_new = copy.deepcopy(data)
17
- for ch_name, ch_data in data_new.items():
22
+ for ch_name, ch_data in sorted(data_new.items()):
18
23
  if ch_name != 'zoosystem':
19
- print(ch_name)
20
- line = ch_data['line']
24
+ r = ch_data['line']
21
25
  try:
22
- if line.ndim == 1:
23
- data_new[ch_name]['line'] = line[e1[0]:e2[0]]
26
+ if r.ndim == 1:
27
+ data_new[ch_name]['line'] = r[e1:e2]
24
28
  else:
25
- data_new[ch_name]['line'] = line[e1[0]:e2[0], :]
29
+ data_new[ch_name]['line'] = r[e1:e2, :]
26
30
  except (IndexError, ValueError) as e:
27
31
  # IndexError: if e1[0]:e2[0] goes beyond the available indices
28
32
  # ValueError: less likely, but may arise with shape mismatches
@@ -31,29 +35,12 @@ def partition_data(data, evt_start, evt_end):
31
35
  # partition events
32
36
  events = ch_data['event']
33
37
  for event_name, value in events.items():
34
- original_frame = value[0]
38
+ original_frame = int(value[0])
35
39
  if original_frame == 999:
36
40
  continue # do not change outlier markers
37
41
  else:
38
- new_frame = original_frame - e1[0] + 1
39
- print(new_frame)
40
- data_new[ch_name]['event'][event_name][0] = new_frame
42
+ arr = np.array(data_new[ch_name]['event'][event_name], dtype=np.int32)
43
+ arr[0] = original_frame - e1
44
+ data_new[ch_name]['event'][event_name] = arr
41
45
 
42
46
  return data_new
43
-
44
-
45
- def _partition_line(arr, evt_start, evt_end):
46
- arr_new = arr[evt_start:evt_end, :]
47
- return arr_new
48
-
49
-
50
- def _partition_event(event_dict, evt_start, evt_end, arr_len):
51
- raise NotImplementedError
52
- # event_dict_new = {}
53
- # for event, event_val in event_dict:
54
- # event_val_new =
55
- # event_dict_new[event] =
56
- #
57
- #
58
- #
59
- # return event_dict_new
@@ -13,7 +13,6 @@ def removechannel_data(data, channels, mode='remove'):
13
13
  if mode not in ['remove', 'keep']:
14
14
  raise ValueError("mode must be 'remove' or 'keep'.")
15
15
 
16
- zoosystem = data.get('zoosystem', {})
17
16
  all_channels = [ch for ch in data if ch != 'zoosystem']
18
17
 
19
18
  # Check for missing channels
@@ -28,9 +27,20 @@ def removechannel_data(data, channels, mode='remove'):
28
27
  else:
29
28
  raise ValueError("Mode must be 'remove' or 'keep'.")
30
29
 
31
- # Build new zoo dictionary
32
- data_new = {'zoosystem': zoosystem}
33
- for ch in keep_channels:
34
- data_new[ch] = data[ch]
30
+ # --- Compute channels to remove ---
31
+ remove_channels = [ch for ch in all_channels if ch not in keep_channels]
35
32
 
36
- return data_new
33
+ if remove_channels:
34
+ print('Removing channels: {}'.format(remove_channels))
35
+ else:
36
+ print('No channels to remove')
37
+
38
+ # Remove from main data dict ---
39
+ for ch in remove_channels:
40
+ data.pop(ch, None)
41
+ if ch in data['zoosystem']['Video']['Channels']:
42
+ data['zoosystem']['Video']['Channels'] = [c for c in data['zoosystem']['Video']['Channels'] if c != ch]
43
+ if ch in data['zoosystem']['Analog']['Channels']:
44
+ data['zoosystem']['Analog']['Channels'] = [c for c in data['zoosystem']['Analog']['Channels'] if c != ch]
45
+
46
+ return data
@@ -0,0 +1,57 @@
1
+ import copy
2
+ import warnings
3
+ from biomechzoo.utils.findfield import findfield
4
+
5
+ def removeevent_data(data, events, mode='remove'):
6
+ """
7
+ Remove or keep specified events in all channels of a zoo dictionary.
8
+
9
+ Parameters
10
+ ----------
11
+ data : dict
12
+ Zoo data loaded from a file
13
+ events : list of str
14
+ Events to remove or keep
15
+ mode : str
16
+ 'remove' or 'keep'
17
+
18
+ Returns
19
+ -------
20
+ dict
21
+ Modified zoo dictionary with events removed or kept
22
+ """
23
+ if mode not in ['remove', 'keep']:
24
+ raise ValueError("mode must be 'remove' or 'keep'.")
25
+
26
+ if isinstance(events, str):
27
+ events = [events]
28
+
29
+ # check if any events are not present
30
+ valid_events = []
31
+ for evt in events:
32
+ e, _ = findfield(data, evt)
33
+ if e is None:
34
+ warnings.warn('Could not find event {} in zoo file, skipping'.format(evt))
35
+ else:
36
+ valid_events.append(evt)
37
+ events = valid_events
38
+
39
+ data_new = copy.deepcopy(data)
40
+ channels = sorted([ch for ch in data_new if ch != 'zoosystem'])
41
+ for ch in channels:
42
+ event_dict = data_new[ch].get('event', {})
43
+ events_to_remove = []
44
+
45
+ for evt in list(event_dict.keys()):
46
+ if mode == 'remove' and evt in events:
47
+ events_to_remove.append(evt)
48
+ elif mode == 'keep' and evt not in events:
49
+ events_to_remove.append(evt)
50
+
51
+ for evt in events_to_remove:
52
+ event_dict.pop(evt, None)
53
+ # print('Removed event "{}" from channel "{}"'.format(evt, ch))
54
+
55
+ data_new[ch]['event'] = event_dict
56
+
57
+ return data_new
@@ -28,17 +28,11 @@ def renameevent_data(data, evt, nevt):
28
28
  # Get all data channels except 'zoosystem'
29
29
  channels = [ch for ch in data if ch != 'zoosystem']
30
30
  for old_name, new_name in zip(evt, nevt):
31
- eventsRenamed = False
32
31
  for ch in channels:
33
32
  events = data[ch].get('event', {})
34
33
  if old_name in events:
35
- print('Renaming event {} in channel {} to {}'.format(old_name, ch, new_name))
36
34
  data[ch]['event'][new_name] = events[old_name]
37
35
  del data[ch]['event'][old_name]
38
- eventsRenamed = True
39
-
40
- if not eventsRenamed:
41
- print('no event {} found in any channel'.format(old_name))
42
36
 
43
37
  return data
44
38
 
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import numpy as np
2
3
 
3
4
 
4
5
  def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, verbose=False):
@@ -66,10 +67,13 @@ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, v
66
67
  continue
67
68
  matched_files.append(full_path)
68
69
 
70
+ # sort list
71
+ matched_files = np.sort(matched_files)
72
+
69
73
  if verbose:
70
- print("Found {} {} files in subfolders named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
74
+ print("Found {} {} files in subfolder(s) named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
71
75
  for f in matched_files:
72
- print(" - {}".format(f))
76
+ print('{}'.format(f))
73
77
 
74
78
  return matched_files
75
79
 
@@ -79,6 +83,6 @@ if __name__ == '__main__':
79
83
  with extension .c3d in the sample study folder (data)"""
80
84
  # -------TESTING--------
81
85
  current_dir = os.path.dirname(os.path.abspath(__file__))
82
- project_root = os.path.dirname(current_dir)
86
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
83
87
  sample_dir = os.path.join(project_root, 'data', 'sample_study', 'raw c3d files')
84
88
  c3d_files = engine(sample_dir, '.c3d', subfolders=['Straight'], name_contains='HC03', verbose=True)
@@ -0,0 +1,24 @@
1
+ import numpy as np
2
+
3
+ def peak_sign(r):
4
+ """
5
+ Determine whether the largest absolute peak in the signal is positive or negative.
6
+
7
+ Parameters
8
+ ----------
9
+ r : array-like
10
+ Signal vector.
11
+
12
+ Returns
13
+ -------
14
+ sign : int
15
+ 1 if the maximum peak is positive, -1 if negative.
16
+ """
17
+ r = np.asarray(r)
18
+ max_val = np.max(r)
19
+ min_val = np.min(r)
20
+
21
+ if abs(max_val) > abs(min_val):
22
+ return 1
23
+ else:
24
+ return -1
@@ -1,23 +1,35 @@
1
- def split_trial(data, start_event_indx, end_event_indx):
1
+ import copy
2
+ from biomechzoo.utils.findfield import findfield
3
+
4
+
5
+ def split_trial(data, start_event, end_event):
2
6
  # todo check index problem compared to matlab start at 0 or 1
3
- data_new = data.copy()
7
+ data_new = copy.deepcopy(data)
8
+
9
+ start_event_indx, _ = findfield(data_new, start_event)
10
+ end_event_indx, _ = findfield(data_new, end_event)
4
11
 
5
12
  for key, value in data_new.items():
6
13
  if key == 'zoosystem':
7
14
  continue
8
15
 
9
16
  # Slice the line data
10
- data_new[key]['line'] = value['line'][start_event_indx:end_event_indx]
17
+ trial_length = len(data_new[key]['line'])
18
+ if trial_length > end_event_indx[0]:
19
+ data_new[key]['line'] = value['line'][start_event_indx[0]:end_event_indx[0]]
20
+ else:
21
+ print('skipping split trial since event is outside range of data')
22
+ return None
11
23
 
12
24
  # Update events if present
13
- if 'event' in value:
14
- new_events = {}
15
- for evt_name, evt_val in value['event'].items():
16
- event_frame = evt_val[0]
17
- # Check if event falls within the new window
18
- if start_event_indx <= event_frame < end_event_indx:
19
- # Adjust index relative to new start
20
- new_events[evt_name] = [event_frame - start_event_indx, 0, 0]
21
- data_new[key]['event'] = new_events
25
+ # if 'event' in value:
26
+ # new_events = {}
27
+ # for evt_name, evt_val in value['event'].items():
28
+ # event_frame = evt_val[0]
29
+ # # Check if event falls within the new window
30
+ # if start_event_indx <= event_frame < end_event_indx:
31
+ # # Adjust index relative to new start
32
+ # new_events[evt_name] = [event_frame - start_event_indx, 0, 0]
33
+ # data_new[key]['event'] = new_events
22
34
 
23
35
  return data_new
biomechzoo/utils/zload.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from scipy.io import loadmat
2
2
  import os
3
+ import numpy as np
3
4
 
4
5
 
5
6
  def zload(filepath):
@@ -29,6 +30,16 @@ def zload(filepath):
29
30
  if 'data' in data:
30
31
  data = data['data']
31
32
 
33
+ # Convert Video and Analog channel arrays to Python lists
34
+ for sys in ['Video', 'Analog']:
35
+ if 'zoosystem' in data and sys in data['zoosystem']:
36
+ channels = data['zoosystem'][sys].get('Channels', [])
37
+ # Convert to list and strip spaces
38
+ if isinstance(channels, np.ndarray):
39
+ channels = channels.tolist()
40
+ channels = [str(ch).strip() for ch in channels]
41
+ data['zoosystem'][sys]['Channels'] = channels
42
+
32
43
  return data
33
44
 
34
45
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomechzoo
3
- Version: 0.4.10
3
+ Version: 0.5.0
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
@@ -1,21 +1,21 @@
1
1
  __init__.py,sha256=Uy3ykqw4l_lZKiUWSUFPRZpkZajYUfZLBaQLVhKzxdI,772
2
2
  biomechzoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  biomechzoo/__main__.py,sha256=hSMHN1Rxn2367fSGTLHoOQ4_pocZw0IWI7HFxl-74oY,88
4
- biomechzoo/biomechzoo.py,sha256=A60kTUaMVWaKl-4ZUZpW8pOKLiZLEpbPYvHZe6urd5w,19800
4
+ biomechzoo/biomechzoo.py,sha256=EBh92xNnkYtQan1Q2vS2lnleoMUVEwoDJxw0YPD4FYA,21353
5
5
  biomechzoo/biomech_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  biomechzoo/biomech_ops/continuous_relative_phase_data.py,sha256=RePbt6zyOI1iv74CWhxSrunIokTFYVfFmFnoW51781E,1300
7
7
  biomechzoo/biomech_ops/continuous_relative_phase_line.py,sha256=Fa1LFRuPlmGPLQLvln6HVnJy3zMSm9z5YeooHmTu0lc,1365
8
- biomechzoo/biomech_ops/filter_data.py,sha256=LZSlemUfbBTrqbo7miFubgTf9R4pr1kEgdmvljmNWdQ,1742
9
- biomechzoo/biomech_ops/filter_line.py,sha256=BuUFALIkmdFhmVocFaZ3aczy_efh2CY_qX9-5CoQZr0,2716
10
- biomechzoo/biomech_ops/normalize_data.py,sha256=gpoUh6EpxpCprBdrSjmZ4UsAWgonJgD41XUJWD8Yo9Y,1299
8
+ biomechzoo/biomech_ops/filter_data.py,sha256=Q9knW23Ft_WeWVCBjaIQ5GkKU0NYV4SdGiDZV8Fm-hM,1805
9
+ biomechzoo/biomech_ops/filter_line.py,sha256=XKUdRsxU5AO1gSldnwp3qNzsUUL8qpOpAMyQbEMo5uI,2600
10
+ biomechzoo/biomech_ops/normalize_data.py,sha256=ESdXeUkKdd0WpjP6FW2EYrOAd7NKWkX1JWUNn1SzDB4,1335
11
11
  biomechzoo/biomech_ops/normalize_line.py,sha256=KUE8gEkIolA-VDFCdUuaskk-waO8jjJ20ZMZaS8Qha8,854
12
12
  biomechzoo/biomech_ops/phase_angle_data.py,sha256=_ekUBW2v3iC4UawcDL38ZZLYJmQsAmyqA61Q6_AMtmQ,1435
13
13
  biomechzoo/biomech_ops/phase_angle_line.py,sha256=p6osB17_3QQSyKLNojuc6nYhv-k0K6EUUH75EXu8ifc,1391
14
14
  biomechzoo/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- biomechzoo/conversion/c3d2zoo_data.py,sha256=gnSCw99Hg5JLdrGezZNVZ6EHqiD6i-mpsLRtWmEa-Mg,2855
16
- biomechzoo/conversion/mvnx2zoo_data.py,sha256=O6WuKe_5fZJfd1gUe3oP9rND_i5P4WGEuu2MDvI3ypY,3560
15
+ biomechzoo/conversion/c3d2zoo_data.py,sha256=28JCj1Jpn7zsv2HjQdzH2F30jnGMwzmbBwYuRoWPJHo,4052
16
+ biomechzoo/conversion/mvnx2zoo_data.py,sha256=uMAZ4pNSSZ7NToW1WnawrXeVP8D-xE3dNDnooPvALE4,3545
17
17
  biomechzoo/conversion/opencap2zoo_data.py,sha256=n4bLsrJI0wTSzG5bgJcmxj1dp2plnUKFNRzcTIbmV1o,608
18
- biomechzoo/conversion/table2zoo_data.py,sha256=OLiRlYB8GqOwv4MAlmwhIkGLxgAvmrgUjTb-Irev4lU,3297
18
+ biomechzoo/conversion/table2zoo_data.py,sha256=rAP7OrKbVrb50NHJ6h8fgoMntV87-t9wT2xQ30AD96I,3330
19
19
  biomechzoo/mvn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  biomechzoo/mvn/load_mvnx.py,sha256=B4VGuidQ-5G_5E1t5vpU51Nb_Lu85_EOS_FmGZYjfX8,22499
21
21
  biomechzoo/mvn/main_mvnx.py,sha256=e1LasJQ9icyzjWnRCEcAWQq_-L13-mIzf7PzYA3QYDg,2394
@@ -24,28 +24,30 @@ biomechzoo/mvn/mvnx_file_accessor.py,sha256=Gk2vKq9v_gPbnOS_12zgeJehjFz7v3ClTnN2
24
24
  biomechzoo/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  biomechzoo/processing/add_channel_data.py,sha256=U1xLLBSnyJeeDWzgmHSxOz1hyguzuuDXxQCQ8IFoZkw,1955
26
26
  biomechzoo/processing/addchannel_data.py,sha256=rmnnlMRVkoMlQCR-nRg1jjh-hzMDt37Sx9fAmocrpqA,1953
27
- biomechzoo/processing/addevent_data.py,sha256=L9xaoP0yBvaGbseotxpGCVqjr-eSV-gpnQaeRTz4VKE,1631
28
- biomechzoo/processing/explodechannel_data.py,sha256=AC2BOEw1tXcgJ1WuYWSE-yToS-q9XGdgkOHS4D3iUFo,1490
29
- biomechzoo/processing/partition_data.py,sha256=XF8dSqvHGpqsT-Q4i6qpoOajAT4LYjP-PJ6KbpoCfFc,2018
30
- biomechzoo/processing/removechannel_data.py,sha256=ndZcbWJRDvd7drlahGaq8MseT-rsxiu7pgdMtA1cTlo,1218
27
+ biomechzoo/processing/addevent_data.py,sha256=T1l7u7cOuILO0mAk3Oac5FjOxH6VBKHHgXUgr8cnCTk,3282
28
+ biomechzoo/processing/explodechannel_data.py,sha256=ceyXfcCmeNqj4p0zCksVdrnmsYR4t-JHLIyv3JlfNpU,2484
29
+ biomechzoo/processing/partition_data.py,sha256=4wuKrnMmRMNobymYwZ0WuRvNGsvkhThZ2ZhoSkkhntg,1741
30
+ biomechzoo/processing/removechannel_data.py,sha256=uO7jjuHapRr2CGLsrvYQ1eJLvb1y_8KR6Ct4M6TPALA,1740
31
+ biomechzoo/processing/removeevent_data.py,sha256=DC1vBIwuo_mk5Oz0UGLK7Nx3BVqAeVDfT29XgUM_1Rc,1631
31
32
  biomechzoo/processing/renamechannel_data.py,sha256=5lEnWJknAwnJYXDlwO6cFe7C8ct5o42tTHW0Y4GeIL4,2210
32
- biomechzoo/processing/renameevent_data.py,sha256=9w7C_fQOsQ8XbdTr_hrg_iQe51oDczq2Rj7yJLyYG0M,2215
33
+ biomechzoo/processing/renameevent_data.py,sha256=PRGHEs-t9qJMzDY1DCNgQz_h-aAOIII4ryQeCYBXwGo,1952
33
34
  biomechzoo/processing/split_trial_by_gait_cycle.py,sha256=maMUwumSlFLFc5LHdNdBO9JCoT06aaLbpdp43dgowDA,1684
34
35
  biomechzoo/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
36
  biomechzoo/utils/batchdisp.py,sha256=Ke7yeZ4cYQvyf8bmgsiLaRElUeQm49TYIYzAcPWan9s,839
36
37
  biomechzoo/utils/compute_sampling_rate_from_time.py,sha256=iyHbN734vYe5bXEoAsHScJN7Qw--2quUgII7judSQDk,579
37
- biomechzoo/utils/engine.py,sha256=D91ZhevBnoWml_NztZP-1lzFoANy5TyIAnJg0FFZyUc,3859
38
+ biomechzoo/utils/engine.py,sha256=tatGMBbgIe8DIHx2_z_JQwA9TJYCeVYqyWiKiMoRHw4,3971
38
39
  biomechzoo/utils/findfield.py,sha256=i7ewGQOMIiTC06tYFK-JctehHLCippkY9FoXIygx14w,381
39
40
  biomechzoo/utils/get_split_events.py,sha256=xNhEuG6Yqsr1bjWIBHLbepfX-fcqcCYIXZzS3eaDDHQ,911
41
+ biomechzoo/utils/peak_sign.py,sha256=4XNqVIrAmpLw9C9RmWwIKkI1rluBTzeCXJE4-AHCzC4,465
40
42
  biomechzoo/utils/set_zoosystem.py,sha256=oubEc3fy0x6y-AlqQWL3v7QYJA951jU9CRnlJ9ikwQo,1750
41
- biomechzoo/utils/split_trial.py,sha256=Fumz_ZukNBNtPauUhCge5EAHkg05dYDhA1_njQw0lHE,886
43
+ biomechzoo/utils/split_trial.py,sha256=0SNpKKkRoizRe9MCuMrQ6Ev6O5V51rN0D-e8FGlLlo4,1302
42
44
  biomechzoo/utils/version.py,sha256=JIXDUuOcaJiZv9ruMP6PtWvJBh4sP0D5kAvlqPiZK_I,130
43
- biomechzoo/utils/zload.py,sha256=FPT6_-gwaOOqOckjgPRfnKEVKMsmNVIcenmQZF2KOvo,1535
45
+ biomechzoo/utils/zload.py,sha256=_qmbQpiEwUNRcB86aS6dHiytOrz1ZXJVjYkk8h5fg8s,2039
44
46
  biomechzoo/utils/zplot.py,sha256=WVA8aCy1Pqy_bo_HXab9AmW-cBd8J8MPX2LAOd6dznU,1512
45
47
  biomechzoo/utils/zsave.py,sha256=wnRNuDxQc8bwCji4UrfoGjYrSZmka4eaDxQ5rMa78pE,1759
46
- biomechzoo-0.4.10.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
47
- biomechzoo-0.4.10.dist-info/METADATA,sha256=P1enuQWmGLcQUQ5Q_6jy1e8o641FncZId2M5wUs1P9Y,1580
48
- biomechzoo-0.4.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- biomechzoo-0.4.10.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
50
- biomechzoo-0.4.10.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
51
- biomechzoo-0.4.10.dist-info/RECORD,,
48
+ biomechzoo-0.5.0.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
49
+ biomechzoo-0.5.0.dist-info/METADATA,sha256=GuXAmEs9sQ2uVq0SzUirvtKWzmWTGC-t6DMNKxWgkiI,1579
50
+ biomechzoo-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ biomechzoo-0.5.0.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
52
+ biomechzoo-0.5.0.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
53
+ biomechzoo-0.5.0.dist-info/RECORD,,