biomechzoo 0.5.9__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.
Files changed (58) hide show
  1. __init__.py +33 -0
  2. biomechzoo/__init__.py +0 -0
  3. biomechzoo/__main__.py +6 -0
  4. biomechzoo/biomech_ops/__init__.py +0 -0
  5. biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
  6. biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
  7. biomechzoo/biomech_ops/filter_data.py +58 -0
  8. biomechzoo/biomech_ops/filter_line.py +85 -0
  9. biomechzoo/biomech_ops/movement_onset.py +53 -0
  10. biomechzoo/biomech_ops/normalize_data.py +36 -0
  11. biomechzoo/biomech_ops/normalize_line.py +51 -0
  12. biomechzoo/biomech_ops/phase_angle_data.py +39 -0
  13. biomechzoo/biomech_ops/phase_angle_line.py +48 -0
  14. biomechzoo/biomechzoo.py +447 -0
  15. biomechzoo/conversion/__init__.py +0 -0
  16. biomechzoo/conversion/c3d2zoo_data.py +95 -0
  17. biomechzoo/conversion/mvnx2zoo_data.py +113 -0
  18. biomechzoo/conversion/opencap2zoo_data.py +23 -0
  19. biomechzoo/conversion/table2zoo_data.py +114 -0
  20. biomechzoo/imu/__init__.py +0 -0
  21. biomechzoo/imu/kinematics.py +0 -0
  22. biomechzoo/imu/tilt_algorithm.py +112 -0
  23. biomechzoo/linear_algebra_ops/__init__.py +0 -0
  24. biomechzoo/linear_algebra_ops/compute_magnitude_data.py +43 -0
  25. biomechzoo/mvn/__init__.py +0 -0
  26. biomechzoo/mvn/load_mvnx.py +514 -0
  27. biomechzoo/mvn/main_mvnx.py +75 -0
  28. biomechzoo/mvn/mvn.py +232 -0
  29. biomechzoo/mvn/mvnx_file_accessor.py +464 -0
  30. biomechzoo/processing/__init__.py +0 -0
  31. biomechzoo/processing/addchannel_data.py +71 -0
  32. biomechzoo/processing/addevent_data.py +116 -0
  33. biomechzoo/processing/explodechannel_data.py +69 -0
  34. biomechzoo/processing/partition_data.py +46 -0
  35. biomechzoo/processing/removechannel_data.py +46 -0
  36. biomechzoo/processing/removeevent_data.py +57 -0
  37. biomechzoo/processing/renamechannel_data.py +79 -0
  38. biomechzoo/processing/renameevent_data.py +62 -0
  39. biomechzoo/processing/split_trial_data.py +40 -0
  40. biomechzoo/statistics/eventval.py +118 -0
  41. biomechzoo/utils/__init__.py +0 -0
  42. biomechzoo/utils/batchdisp.py +21 -0
  43. biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
  44. biomechzoo/utils/engine.py +88 -0
  45. biomechzoo/utils/findfield.py +11 -0
  46. biomechzoo/utils/get_split_events.py +33 -0
  47. biomechzoo/utils/peak_sign.py +24 -0
  48. biomechzoo/utils/set_zoosystem.py +66 -0
  49. biomechzoo/utils/version.py +5 -0
  50. biomechzoo/utils/zload.py +57 -0
  51. biomechzoo/utils/zplot.py +61 -0
  52. biomechzoo/utils/zsave.py +54 -0
  53. biomechzoo-0.5.9.dist-info/METADATA +46 -0
  54. biomechzoo-0.5.9.dist-info/RECORD +58 -0
  55. biomechzoo-0.5.9.dist-info/WHEEL +5 -0
  56. biomechzoo-0.5.9.dist-info/entry_points.txt +2 -0
  57. biomechzoo-0.5.9.dist-info/licenses/LICENSE +21 -0
  58. biomechzoo-0.5.9.dist-info/top_level.txt +2 -0
@@ -0,0 +1,118 @@
1
+ import os
2
+ import pandas as pd
3
+ from biomechzoo.utils.engine import engine
4
+ from biomechzoo.utils.zload import zload
5
+ from biomechzoo.utils.findfield import findfield # assuming this exists
6
+
7
+ def eventval(fld, dim1=None, dim2=None, ch=None, localevts=None, globalevts=None, anthroevts=None):
8
+ """
9
+ Extract event values from .zoo files and compile into a pandas DataFrame.
10
+
11
+ Parameters
12
+ ----------
13
+ fld : str
14
+ Path to the root data folder containing .zoo files.
15
+ dim1 : list of str, optional
16
+ List of conditions (subfolder names under fld).
17
+ dim2 : list of str, optional
18
+ List of participant identifiers.
19
+ ch : list of str
20
+ List of channels to extract events from.
21
+ localevts : list of str, optional
22
+ List of local events.
23
+ globalevts : list of str, optional
24
+ List of global events.
25
+ anthroevts : list of str, optional
26
+ List of events stored in the metadata.Usually anthropometric data
27
+
28
+ Returns
29
+ -------
30
+ pd.DataFrame
31
+ Columns: ['condition', 'subject', 'file', 'event_name', 'event_value']
32
+ """
33
+
34
+ zoo_files = engine(fld, extension='.zoo')
35
+ results = []
36
+ files_excluded = []
37
+ for fl in zoo_files:
38
+ data = zload(fl)
39
+ fname = os.path.basename(fl)
40
+ condition = next((c for c in (dim1 or []) if c in fl), '')
41
+ subject = next((s for s in (dim2 or []) if s in fl), '')
42
+
43
+ # Skip file if condition or subject is empty
44
+
45
+ if not condition or not subject:
46
+ files_excluded.append(fl)
47
+ continue
48
+ else:
49
+ print('processing {}'.format(fl))
50
+
51
+ # --- Local events ---
52
+ if localevts and ch:
53
+ print('extracting local events...')
54
+ for channel in ch:
55
+ if channel not in data:
56
+ print('channel {} not found'.format(channel))
57
+ continue
58
+ for evt in localevts:
59
+ try:
60
+ exd = int(data[channel]['event'][evt][0]) # xdata
61
+ eyd = data[channel]['event'][evt][1] # ydata
62
+ results.append({
63
+ 'condition': condition,
64
+ 'subject': subject,
65
+ 'file': fname,
66
+ 'event_name': f"{channel}_{evt}",
67
+ 'event_index': exd,
68
+ 'event_value': eyd
69
+ })
70
+ except (KeyError, IndexError, TypeError) as e:
71
+ print(f"Local event '{evt}' not found in channel '{channel}' for file '{fname}'")
72
+
73
+ # --- Global events ---
74
+ if globalevts and ch:
75
+ print('extracting global events...')
76
+ for evt in globalevts:
77
+ # use findfield to locate where the global event is stored
78
+ evt_val, evt_ch = findfield(data, evt)
79
+ if not evt_ch:
80
+ print(f"Global event '{evt}' not found in any channel for file '{fname}'")
81
+ continue
82
+
83
+ exd = int(evt_val[0])
84
+ for channel in ch:
85
+ if channel not in data:
86
+ print(f"Skipping {evt}: channel {channel} not in data")
87
+ continue
88
+ try:
89
+ eyd = data[channel]['line'][exd]
90
+ results.append({
91
+ 'condition': condition,
92
+ 'subject': subject,
93
+ 'file': fname,
94
+ 'event_name': f"{channel}_{evt}",
95
+ 'event_index': exd,
96
+ 'event_value': eyd
97
+ })
98
+ except (IndexError, KeyError, TypeError) as e:
99
+ print(f"Global event '{evt}' index out of range in channel '{channel}' for file '{fname}': {e}")
100
+
101
+ if anthroevts and ch:
102
+ raise NotImplementedError
103
+ print('extracting anthropometric events...')
104
+ for evt in globalevts:
105
+ # use findfield to locate where the global event is stored
106
+ evt_val, _ = findfield(data, evt)
107
+
108
+ if evt_val:
109
+ results.append({
110
+ 'condition': condition,
111
+ 'subject': subject,
112
+ 'file': fname,
113
+ 'event_name': evt,
114
+ 'event_index': evt_val[0],
115
+ 'event_value': evt_val[1]
116
+ })
117
+
118
+ return pd.DataFrame(results)
File without changes
@@ -0,0 +1,21 @@
1
+ def batchdisp(msg, level=1, verbose='none'):
2
+ """ utility to control verbosity level during batch processing"""
3
+ level = _normalize_verbose(level)
4
+ verbose = _normalize_verbose(verbose)
5
+ if verbose >= level:
6
+ print(msg)
7
+
8
+ def _normalize_verbose(verbose):
9
+ if isinstance(verbose, int):
10
+ if verbose not in (0, 1, 2):
11
+ raise ValueError("Integer verbose level must be 0 (none), 1 (minimal), or 2 (all)")
12
+ return verbose
13
+ elif isinstance(verbose, str):
14
+ verbose_map = {'none': 0, 'minimal': 1, 'all': 2}
15
+ if verbose.lower() not in verbose_map:
16
+ raise ValueError("String verbose level must be 'none', 'minimal', or 'all'")
17
+ return verbose_map[verbose.lower()]
18
+ else:
19
+ raise TypeError("Verbose must be an int (0–2) or str ('none', 'minimal', 'all')")
20
+
21
+
@@ -0,0 +1,25 @@
1
+ import numpy as np
2
+
3
+
4
+ def compute_sampling_rate_from_time(t, verbose=False):
5
+ """ computes sampling rate from time column
6
+ Arguments
7
+ t, 1D numpy array, recorded times
8
+
9
+ Returns
10
+ fsamp, int: sampling rate of capture
11
+ """
12
+ # Calculate differences between consecutive time points
13
+ dt = np.diff(t)
14
+
15
+ # Average time difference (seconds)
16
+ avg_dt = np.mean(dt)
17
+
18
+ # Sampling frequency (Hz)
19
+ # Sampling frequency (Hz)
20
+ fsamp = int(np.round(1 / avg_dt))
21
+
22
+ if verbose:
23
+ print('Inferred sampling rate: {} Hz'.format(fsamp))
24
+
25
+ return fsamp
@@ -0,0 +1,88 @@
1
+ import os
2
+ import numpy as np
3
+
4
+
5
+ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, verbose=False):
6
+ """
7
+ Recursively search for files with a given extension, optionally filtering by
8
+ specific subfolders and substrings in filenames.
9
+
10
+ This function walks through every directory and subdirectory starting at
11
+ 'root_folder'. If 'subfolders' is provided, it limits the search only to those
12
+ folders (or any of their subfolders) whose names appear in 'subfolders'. For
13
+ example, if subfolders=['Straight'], it will only consider files inside any
14
+ folder named 'Straight' at any depth within the root folder.
15
+
16
+ For each file found, it checks whether the file's extension matches the given
17
+ 'extension' (case-insensitive). If 'name_contains' is specified, it also
18
+ requires the filename to contain that substring (case-insensitive).
19
+
20
+ Arguments:
21
+ root_folder (str): The root directory path where the search begins.
22
+ extension (str): File extension to search for (e.g., '.zoo', '.c3d'). Default .zoo
23
+ subfolders (list or str, optional): List of folder names to restrict the search to.
24
+ Only files inside these folders (or their subfolders) are included.
25
+ If None, search all subfolders.
26
+ name_contains (str, or list; optional): Substring that must be present in the filename
27
+ (case-insensitive). If None, no substring filtering is applied.
28
+ verbose (bool, optional): If true, displays additional information to user
29
+ Returns:
30
+ list of str: List of full file paths matching the criteria.
31
+ """
32
+ # check format of subfolder (string or list)
33
+ if subfolders is not None:
34
+ if type(subfolders) is str:
35
+ subfolders = [subfolders]
36
+
37
+ # check format of name_contants (str or list)
38
+ if name_contains is not None:
39
+ if type(name_contains) is str:
40
+ name_contains = [name_contains]
41
+
42
+ matched_files = []
43
+
44
+ subfolders_set = set(subfolders) if subfolders else None
45
+ for dirpath, _, filenames in os.walk(root_folder):
46
+ if subfolders_set is not None:
47
+ rel_path = os.path.relpath(dirpath, root_folder)
48
+ if rel_path == '.':
49
+ continue
50
+ # Split the relative path into all folder parts
51
+ parts = rel_path.split(os.sep)
52
+ # Check if any folder in the path matches one in subfolders_set
53
+ if not any(part in subfolders_set for part in parts):
54
+ continue
55
+
56
+ for file in filenames:
57
+ if not file.lower().endswith(extension.lower()):
58
+ continue
59
+ full_path = os.path.join(dirpath, file)
60
+ if name_contains is not None:
61
+ match = False
62
+ for name_contain in name_contains:
63
+ if name_contain and name_contain.lower() in full_path.lower(): # <-- check full path
64
+ match = True
65
+ break
66
+ if not match:
67
+ continue
68
+ matched_files.append(full_path)
69
+
70
+ # sort list
71
+ matched_files = np.sort(matched_files)
72
+
73
+ if verbose:
74
+ print("Found {} {} files in subfolder(s) named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
75
+ for f in matched_files:
76
+ print('{}'.format(f))
77
+
78
+ return matched_files
79
+
80
+
81
+ if __name__ == '__main__':
82
+ """ testing: use engine to search for files in any subfolder called 'Straight' for files with the substring 'HC03'
83
+ with extension .c3d in the sample study folder (data)"""
84
+ # -------TESTING--------
85
+ current_dir = os.path.dirname(os.path.abspath(__file__))
86
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
87
+ sample_dir = os.path.join(project_root, 'data', 'sample_study', 'raw c3d files')
88
+ c3d_files = engine(sample_dir, '.c3d', subfolders=['Straight'], name_contains='HC03', verbose=True)
@@ -0,0 +1,11 @@
1
+ def findfield(data, target_event):
2
+ """ searches in zoo data for the event value and channel name associated with target_event"""
3
+ for channel, content in data.items():
4
+ if channel == 'zoosystem':
5
+ continue
6
+ events = content.get('event', {})
7
+ if target_event in events:
8
+ return events[target_event], channel
9
+ return None, None
10
+
11
+
@@ -0,0 +1,33 @@
1
+ from biomechzoo.utils.findfield import findfield
2
+
3
+
4
+ def get_split_events(data, first_event_name):
5
+ """ splits lengthy trials containing n cycles into n trials based on side"""
6
+
7
+ # find all events, events should follow style name1, name2, etc..
8
+ split_events = []
9
+ _, channel_name = findfield(data, first_event_name)
10
+ if channel_name is None:
11
+ return None
12
+
13
+ event_name_root = first_event_name[0:-1]
14
+ first_event_number = int(first_event_name[-1])
15
+ i = 1
16
+ if first_event_number > 1:
17
+ i = first_event_number
18
+
19
+ while True:
20
+ key = f"{event_name_root}{i}"
21
+ if key in data[channel_name]['event']:
22
+ split_events.append(key)
23
+ i += 1
24
+ else:
25
+ break
26
+
27
+ n_segments = len(split_events) - 1
28
+ if n_segments < 1:
29
+ print("Not enough {} events to split.".format(event_name_root))
30
+ return
31
+
32
+ return split_events
33
+
@@ -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
@@ -0,0 +1,66 @@
1
+ import numpy as np
2
+ from pathlib import Path
3
+ from biomechzoo.utils.version import get_biomechzoo_version
4
+
5
+
6
+ def set_zoosystem(fl=None):
7
+ """
8
+ Create the 'zoosystem' branch for data being imported to BiomechZoo.
9
+
10
+ Args:
11
+ fl: str
12
+ Path to the source file (e.g., C3D or CSV).
13
+
14
+ Returns:
15
+ zoosystem: dict
16
+ Dictionary containing default BiomechZoo system parameters.
17
+ """
18
+
19
+ # Default top-level fields
20
+ zch = ['Analog', 'Anthro', 'AVR', 'CompInfo', 'SourceFile',
21
+ 'Units', 'Version', 'Video']
22
+
23
+ # Initialize top-level dict
24
+ zoosystem = {key: {} for key in zch}
25
+
26
+ # Section-specific defaults
27
+ section = ['Video', 'Analog']
28
+ for sec in section:
29
+ zoosystem[sec]['Channels'] = []
30
+ zoosystem[sec]['Freq'] = []
31
+ zoosystem[sec]['Indx'] = []
32
+ zoosystem[sec]['ORIGINAL_START_FRAME'] = []
33
+ zoosystem[sec]['ORIGINAL_END_FRAME'] = []
34
+ zoosystem[sec]['CURRENT_START_FRAME'] = []
35
+ zoosystem[sec]['CURRENT_END_FRAME'] = []
36
+
37
+ # Processing and AVR defaults
38
+ zoosystem['Processing'] = ''
39
+ zoosystem['AVR'] = 0
40
+
41
+ # Force plates defaults
42
+ zoosystem['Analog']['FPlates'] = {
43
+ 'CORNERS': [],
44
+ 'NUMUSED': 0,
45
+ 'LOCALORIGIN': [],
46
+ 'LABELS': []
47
+ }
48
+
49
+ # Version and source file
50
+ zoosystem['Version'] = get_biomechzoo_version()
51
+ if fl is None:
52
+ zoosystem['SourceFile'] = 'None' # ensure string
53
+ else:
54
+ zoosystem['SourceFile'] = str(Path(fl)) # ensure string
55
+
56
+ # Units defaults
57
+ zoosystem['Units'] = {
58
+ 'Markers': 'mm',
59
+ 'Angles': 'deg',
60
+ 'Forces': 'N',
61
+ 'Moments': 'Nmm',
62
+ 'Power': 'W/kg',
63
+ 'Scalars': 'mm'
64
+ }
65
+
66
+ return zoosystem
@@ -0,0 +1,5 @@
1
+ from importlib.metadata import version as get_version
2
+
3
+
4
+ def get_biomechzoo_version() -> str:
5
+ return get_version("biomechzoo")
@@ -0,0 +1,57 @@
1
+ from scipy.io import loadmat
2
+ import os
3
+ import numpy as np
4
+
5
+
6
+ def zload(filepath):
7
+ if not filepath.endswith('.zoo'):
8
+ raise ValueError(f"{filepath} is not a .zoo file")
9
+
10
+ if not os.path.exists(filepath):
11
+ raise FileNotFoundError(f"File not found: {filepath}")
12
+
13
+ mat_data = loadmat(filepath, struct_as_record=False, squeeze_me=True)
14
+
15
+ # Remove default MATLAB metadata fields
16
+ mat_data = {k: v for k, v in mat_data.items() if not k.startswith('__')}
17
+
18
+ # Convert MATLAB structs to Python dicts (recursively)
19
+ def mat_struct_to_dict(obj):
20
+ if isinstance(obj, dict):
21
+ return {k: mat_struct_to_dict(v) for k, v in obj.items()}
22
+ elif hasattr(obj, '_fieldnames'):
23
+ return {field: mat_struct_to_dict(getattr(obj, field)) for field in obj._fieldnames}
24
+ elif isinstance(obj, list):
25
+ return [mat_struct_to_dict(item) for item in obj]
26
+ else:
27
+ return obj
28
+ data = {k: mat_struct_to_dict(v) for k, v in mat_data.items()}
29
+
30
+ if 'data' in data:
31
+ data = data['data']
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
+
43
+ return data
44
+
45
+
46
+ if __name__ == '__main__':
47
+ """ testing: load a single zoo file from the other subfolder in data"""
48
+ # -------TESTING--------
49
+ current_dir = os.path.dirname(os.path.abspath(__file__))
50
+ project_root = os.path.dirname(current_dir)
51
+ fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
52
+ data = zload(fl)
53
+
54
+ channels = [k for k in data.keys()]
55
+ print('{} channels found'.format(len(channels)))
56
+ for ch in channels:
57
+ print({ch})
@@ -0,0 +1,61 @@
1
+ import matplotlib.pyplot as plt
2
+
3
+
4
+ def zplot(data, ch, xlabel='frames', ylabel='angles (deg)'):
5
+ """
6
+ Plot a single channel of a zoo file, along with any existing events.
7
+
8
+ Parameters
9
+ ----------
10
+ data : dict
11
+ Loaded zoo file.
12
+ ch : str
13
+ Name of the channel to plot, e.g., 'RkneeAngles'.
14
+ xlabel : str
15
+ Label for x-axis. Default is 'frames'.
16
+ ylabel : str
17
+ Label for y-axis. Default is 'angles (deg)'.
18
+
19
+ Returns
20
+ -------
21
+ None
22
+ """
23
+
24
+ if ch not in data:
25
+ raise KeyError(f"Channel '{ch}' not found in data.")
26
+
27
+ y = data[ch]['line']
28
+ x = range(len(y))
29
+
30
+ plt.figure(figsize=(10, 4))
31
+ plt.plot(x, y, label='Signal', linewidth=2)
32
+ plt.title(ch)
33
+ plt.xlabel(xlabel)
34
+ plt.ylabel(ylabel)
35
+ plt.grid(True)
36
+
37
+ # Plot events if available
38
+ events = data[ch].get('event', {})
39
+ for name, coords in events.items():
40
+ evtx, evty = coords[0], coords[1]
41
+ plt.plot(evtx, evty, 'ro')
42
+ plt.text(evtx, evty, name, fontsize=8, color='red', ha='left', va='bottom')
43
+
44
+ plt.tight_layout()
45
+ plt.show()
46
+
47
+
48
+ if __name__ == '__main__':
49
+ # -------TESTING--------
50
+ import os
51
+ from biomechzoo.utils.zload import zload
52
+
53
+ # get path to sample zoo file
54
+ current_dir = os.path.dirname(os.path.abspath(__file__))
55
+ project_root = os.path.dirname(current_dir)
56
+ fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
57
+
58
+ # load zoo file
59
+ data = zload(fl)
60
+ ch = 'SACR'
61
+ zplot(data, ch)
@@ -0,0 +1,54 @@
1
+ from scipy.io import savemat
2
+ import inspect
3
+ import os
4
+
5
+ from biomechzoo.utils.batchdisp import batchdisp
6
+
7
+
8
+ def zsave(fl, data, inplace=True, out_folder=None, root_folder=None, verbose=False):
9
+ """
10
+ Save zoo data to .zoo file (MAT format)
11
+
12
+ Arguments:
13
+ fl (str): Full path to original .zoo file
14
+ data (dict): Zoo data to save
15
+ inplace (bool): Whether to overwrite original file
16
+ out_folder (str or None): If not inplace, output folder name (relative to root_folder or file location)
17
+ root_folder (str or None): Optional base directory for saving when inplace=False
18
+ """
19
+ # Get caller function name for logging
20
+ caller_name = inspect.stack()[1].function
21
+
22
+ # Initialize zoosystem and processing history
23
+ zoosystem = data.get('zoosystem', {})
24
+ processing = zoosystem.get('Processing', [])
25
+ if not isinstance(processing, list):
26
+ processing = [processing]
27
+ processing.append(caller_name)
28
+ zoosystem['Processing'] = processing
29
+ data['zoosystem'] = zoosystem
30
+
31
+ # Determine save path
32
+ if inplace:
33
+ fl_new = fl
34
+ out_dir = os.path.dirname(fl)
35
+ else:
36
+ if out_folder is None:
37
+ out_folder = 'processed'
38
+
39
+ if root_folder is None:
40
+ root_folder = os.path.dirname(fl)
41
+
42
+ root_path = os.path.dirname(root_folder)
43
+ in_folder = os.path.basename(root_folder)
44
+ out_dir = os.path.join(root_path, out_folder)
45
+ os.makedirs(out_dir, exist_ok=True)
46
+ fl_new = fl.replace(in_folder, out_folder)
47
+ save_folder = os.path.dirname(fl_new)
48
+ os.makedirs(save_folder, exist_ok=True)
49
+
50
+ # Save the .zoo file
51
+ savemat(fl_new, data)
52
+ batchdisp('all files saved to ' + out_dir, level=1, verbose=verbose)
53
+
54
+
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: biomechzoo
3
+ Version: 0.5.9
4
+ Summary: Python implementation of the biomechZoo toolbox
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
7
+ Requires-Python: <3.12,>=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: ezc3d>=1.5.19
11
+ Requires-Dist: matplotlib>=3.10.6
12
+ Requires-Dist: numpy==2.2.6
13
+ Requires-Dist: pandas>=2.3.2
14
+ Requires-Dist: scipy>=1.16.2
15
+ Requires-Dist: pyarrow>=19.0.0
16
+ Dynamic: license-file
17
+
18
+ # BiomechZoo for Python
19
+ This is a development version of the biomechzoo toolbox for python.
20
+
21
+ ## How to install
22
+ - biomechZoo for python is now an official package, you can simply add biomechZoo to your environment using
23
+ ``pip install biomechzoo``
24
+
25
+ ## Usage notes
26
+ - If you need to install a specific version, run ``pip install biomechzoo==x.x.x`` where x.x.x is the version number.
27
+ - If you need to update biomechzoo to the latest version in your env, run ``pip install biomechzoo --upgrade``
28
+
29
+ ## Dependencies notes
30
+ - We use Python 3.11 for compatibility with https://github.com/stanfordnmbl/opencap-processing
31
+ - We use Numpy 2.2.6 for compatibility with https://pypi.org/project/numba/
32
+
33
+ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechzoo.com for more information
34
+
35
+ ## Developer notes
36
+
37
+ ### Installing a dev environment
38
+ conda create -n biomechzoo-dev python=3.11
39
+ conda activate biomechzoo-dev
40
+ cd biomechzoo root folder
41
+ pip install -e ".[dev]"
42
+
43
+ ### import issues
44
+ if using PyCharm:
45
+ - Right-click on src/.
46
+ - Select Mark Directory as → Sources Root.