biomechzoo 0.4.9__py3-none-any.whl → 0.4.11__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
biomechzoo/biomechzoo.py CHANGED
@@ -8,7 +8,7 @@ from biomechzoo.utils.batchdisp import batchdisp
8
8
  from biomechzoo.utils.get_split_events import get_split_events
9
9
  from biomechzoo.utils.split_trial import split_trial
10
10
  from biomechzoo.conversion.c3d2zoo_data import c3d2zoo_data
11
- from biomechzoo.conversion.table2zoo import table2zoo_data
11
+ 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
@@ -19,6 +19,7 @@ from biomechzoo.processing.renameevent_data import renameevent_data
19
19
  from biomechzoo.biomech_ops.normalize_data import normalize_data
20
20
  from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
21
21
  from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
22
+ from biomechzoo.biomech_ops.filter_data import filter_data
22
23
 
23
24
  class BiomechZoo:
24
25
  def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
@@ -306,7 +307,7 @@ class BiomechZoo:
306
307
  # Update self.folder after processing
307
308
  self._update_folder(out_folder, inplace, in_folder)
308
309
 
309
- def addevent(self, ch, evt_type, evt_name, out_folder=None, inplace=None):
310
+ def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
310
311
  """ adds events of type evt_type with name evt_name to channel ch """
311
312
  start_time = time.time()
312
313
  verbose = self.verbose
@@ -316,9 +317,9 @@ class BiomechZoo:
316
317
  fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
317
318
  for f in fl:
318
319
  if verbose:
319
- batchdisp('adding event {} to channel {} for {}'.format(evt_type, ch, f), level=2, verbose=verbose)
320
+ batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
320
321
  data = zload(f)
321
- data = addevent_data(data, ch, evt_type, evt_name)
322
+ data = addevent_data(data, ch, event_type, event_name)
322
323
  zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
323
324
  method_name = inspect.currentframe().f_code.co_name
324
325
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
@@ -346,28 +347,22 @@ class BiomechZoo:
346
347
  self._update_folder(out_folder, inplace, in_folder)
347
348
 
348
349
  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
-
350
+ """ filter data"""
351
+ start_time = time.time()
352
+ verbose = self.verbose
353
+ in_folder = self.in_folder
354
+ if inplace is None:
355
+ inplace = self.inplace
356
+ fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
357
+ for f in fl:
358
+ if verbose:
359
+ batchdisp('filtering data for channel {} in {}'.format(ch, f), level=2, verbose=verbose)
360
+ data = zload(f)
361
+ data = filter_data(data, ch, filt)
362
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
363
+ method_name = inspect.currentframe().f_code.co_name
364
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
365
+ level=1, verbose=verbose)
366
+ # Update self.folder after processing
367
+ self._update_folder(out_folder, inplace, in_folder)
373
368
 
@@ -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
@@ -10,6 +10,7 @@ def mvnx2zoo_data(fl):
10
10
 
11
11
  # create zoo data dict
12
12
  data = {}
13
+ data['zoosystem'] = set_zoosystem()
13
14
 
14
15
  # extract joint angle data (All JOINTS may not exist in a given dataset)
15
16
  for key, val in JOINTS.items():
@@ -58,7 +59,6 @@ def is_valid_for_zoo(val):
58
59
 
59
60
  def _get_meta_info(fl, mvnx_file, data):
60
61
  # todo: add more, see mvnx_file object
61
- data['zoosystem'] = set_zoosystem(fl)
62
62
  data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
63
63
  data['zoosystem']['mvnx_version'] = mvnx_file.version
64
64
  data['zoosystem']['mvnx_configuration'] = mvnx_file.configuration
@@ -32,13 +32,15 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
32
32
  raise ValueError('type must be csv or parquet')
33
33
 
34
34
  # Use all columns
35
- data = df.iloc[:, 0:]
35
+ df_data = df.iloc[:, 0:]
36
36
 
37
37
  # assemble zoo data
38
- zoo_data = {}
39
- for ch in data.columns:
40
- zoo_data[ch] = {
41
- 'line': data[ch].values,
38
+ data = {}
39
+ data['zoosystem'] = set_zoosystem()
40
+
41
+ for ch in df_data.columns:
42
+ data[ch] = {
43
+ 'line': df_data[ch].values,
42
44
  'event': []
43
45
  }
44
46
 
@@ -59,14 +61,12 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
59
61
  freq = compute_sampling_rate_from_time(time_data)
60
62
 
61
63
  # 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'
64
+ data['zoosystem']['Video']['Freq'] = freq
65
+ data['zoosystem']['Analog']['Freq'] = 'None'
66
66
  if 'version' in metadata:
67
- zoo_data['zoosystem']['collection_system_version'] = metadata['version']
67
+ data['zoosystem']['collection_system_version'] = metadata['version']
68
68
 
69
- return zoo_data
69
+ return data
70
70
 
71
71
 
72
72
  def _parse_metadata(header_lines):
@@ -37,6 +37,11 @@ def addevent_data(data, ch, ename, etype):
37
37
  elif etype == 'rom':
38
38
  eyd = float(np.max(yd) - np.min(yd))
39
39
  exd = 0 # dummy index (like MATLAB version)
40
+ elif etype == 'max_stance':
41
+ # special event for gait and running
42
+ exd = max_stance(yd)
43
+ eyd = float(yd[exd])
44
+ eyd = float(yd[exd])
40
45
  else:
41
46
  raise ValueError(f'Unknown event type: {etype}')
42
47
 
@@ -44,3 +49,8 @@ def addevent_data(data, ch, ename, etype):
44
49
  data[channel]['event'][ename] = [exd, eyd, 0]
45
50
 
46
51
  return data
52
+
53
+ def max_stance(yd):
54
+ """ extracts max from first 40% of the gait cycle"""
55
+ raise NotImplementedError
56
+ return exd
@@ -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
@@ -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
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.9
3
+ Version: 0.4.11
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
@@ -37,6 +37,7 @@ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechz
37
37
  ### Installing a dev environment
38
38
  conda create -n biomechzoo-dev python=3.11
39
39
  conda activate biomechzoo-dev
40
+ cd biomechzoo root folder
40
41
  pip install -e ".[dev]"
41
42
 
42
43
  ### import issues
@@ -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=gWTFroK4FGEu4Z43n1Bxpqao5rgtoQTsgVvR4BwoPcI,19785
4
+ biomechzoo/biomechzoo.py,sha256=N2qqUJrSvWueB6TEOJjONdqGjy1pv1PmNXA177mvjGs,19772
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
8
+ biomechzoo/biomech_ops/filter_data.py,sha256=Q9knW23Ft_WeWVCBjaIQ5GkKU0NYV4SdGiDZV8Fm-hM,1805
9
+ biomechzoo/biomech_ops/filter_line.py,sha256=XKUdRsxU5AO1gSldnwp3qNzsUUL8qpOpAMyQbEMo5uI,2600
10
10
  biomechzoo/biomech_ops/normalize_data.py,sha256=gpoUh6EpxpCprBdrSjmZ4UsAWgonJgD41XUJWD8Yo9Y,1299
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=fZBFFYHuTC0gsH4tjB0S8yszVtc5Ki7KosKN68-zTkU,3558
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=H_r6RNt_MGwkOr4JH-M844DT_ivScQx_byc6DIZa5go,3210
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,10 +24,10 @@ 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=DyTfvjOWZIlsWfnT6XDwvsU1jOzSDf4HhLa1TaKIhwI,1325
28
- biomechzoo/processing/explodechannel_data.py,sha256=AC2BOEw1tXcgJ1WuYWSE-yToS-q9XGdgkOHS4D3iUFo,1490
27
+ biomechzoo/processing/addevent_data.py,sha256=L9xaoP0yBvaGbseotxpGCVqjr-eSV-gpnQaeRTz4VKE,1631
28
+ biomechzoo/processing/explodechannel_data.py,sha256=ceyXfcCmeNqj4p0zCksVdrnmsYR4t-JHLIyv3JlfNpU,2484
29
29
  biomechzoo/processing/partition_data.py,sha256=XF8dSqvHGpqsT-Q4i6qpoOajAT4LYjP-PJ6KbpoCfFc,2018
30
- biomechzoo/processing/removechannel_data.py,sha256=ndZcbWJRDvd7drlahGaq8MseT-rsxiu7pgdMtA1cTlo,1218
30
+ biomechzoo/processing/removechannel_data.py,sha256=uO7jjuHapRr2CGLsrvYQ1eJLvb1y_8KR6Ct4M6TPALA,1740
31
31
  biomechzoo/processing/renamechannel_data.py,sha256=5lEnWJknAwnJYXDlwO6cFe7C8ct5o42tTHW0Y4GeIL4,2210
32
32
  biomechzoo/processing/renameevent_data.py,sha256=9w7C_fQOsQ8XbdTr_hrg_iQe51oDczq2Rj7yJLyYG0M,2215
33
33
  biomechzoo/processing/split_trial_by_gait_cycle.py,sha256=maMUwumSlFLFc5LHdNdBO9JCoT06aaLbpdp43dgowDA,1684
@@ -40,12 +40,12 @@ biomechzoo/utils/get_split_events.py,sha256=xNhEuG6Yqsr1bjWIBHLbepfX-fcqcCYIXZzS
40
40
  biomechzoo/utils/set_zoosystem.py,sha256=oubEc3fy0x6y-AlqQWL3v7QYJA951jU9CRnlJ9ikwQo,1750
41
41
  biomechzoo/utils/split_trial.py,sha256=Fumz_ZukNBNtPauUhCge5EAHkg05dYDhA1_njQw0lHE,886
42
42
  biomechzoo/utils/version.py,sha256=JIXDUuOcaJiZv9ruMP6PtWvJBh4sP0D5kAvlqPiZK_I,130
43
- biomechzoo/utils/zload.py,sha256=FPT6_-gwaOOqOckjgPRfnKEVKMsmNVIcenmQZF2KOvo,1535
43
+ biomechzoo/utils/zload.py,sha256=_qmbQpiEwUNRcB86aS6dHiytOrz1ZXJVjYkk8h5fg8s,2039
44
44
  biomechzoo/utils/zplot.py,sha256=WVA8aCy1Pqy_bo_HXab9AmW-cBd8J8MPX2LAOd6dznU,1512
45
45
  biomechzoo/utils/zsave.py,sha256=wnRNuDxQc8bwCji4UrfoGjYrSZmka4eaDxQ5rMa78pE,1759
46
- biomechzoo-0.4.9.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
47
- biomechzoo-0.4.9.dist-info/METADATA,sha256=VVG2UyRLhjRchJAnMc80aqXLb-0oqCHZauXxntwDq-w,1553
48
- biomechzoo-0.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- biomechzoo-0.4.9.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
50
- biomechzoo-0.4.9.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
51
- biomechzoo-0.4.9.dist-info/RECORD,,
46
+ biomechzoo-0.4.11.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
47
+ biomechzoo-0.4.11.dist-info/METADATA,sha256=IAMgsVbJdayIURX7FwWfT5XNsBy4JfvOnqbTu-jlbYE,1580
48
+ biomechzoo-0.4.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
+ biomechzoo-0.4.11.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
50
+ biomechzoo-0.4.11.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
51
+ biomechzoo-0.4.11.dist-info/RECORD,,