{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Read and write Cortex Motion Analysis Corporation ASCII files\n", "\n", "> Marcos Duarte \n", "> Laboratory of Biomechanics and Motor Control ([http://demotu.org/](http://demotu.org/)) \n", "> Federal University of ABC, Brazil" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Motion Analysis Corporation (MAC, http://www.motionanalysis.com/) builds motion capture systems and their software (e.g., Cortex) generates files in ASCII and binary formats for the different signals (kinematics, analog data, force plate data, etc.). Here are functions for reading most of the files saved in ASCII format. These files have headers with few lines with meta data and the signals are stored in columns and the rows for the different frames (instants of time).\n", "\n", "The \".trc\" (Track Row Column) file in ASCII contains X-Y-Z position data for the reflective markers from a motion capture trial. The position data for each marker is organized into 3 columns per marker (X, Y and Z position) with each row being a new frame. The position data is relative to the global coordinate system of the capture volume and the position values are in the units used for calibration.\n", "\n", "The \".anc\" (Analog ASCII Row Column) file contains ASCII analog data in row-column format. The data is derived from \".anb\" analog binary files. These binary \".anb\" files are generated simultaneously with video \".vc\" files if an optional analog input board is used in conjunction with video data capture.\n", "\n", "The \".cal\" file contains force plate calibration parameters. \n", "\n", "The \".forces\" file contains force plate data. The data is saved based on the \"forcepla.cal\" file of the trial and converts the raw force plate data into calibrated forces. The units used are Newtons and Newton-meters and each line in the file equates to one analog sample.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T23:18:56.045048Z", "start_time": "2018-02-25T23:18:55.668764Z" } }, "outputs": [], "source": [ "import sys\n", "sys.path.insert(1, r'./../functions') # add to pythonpath\n", "import io_cortexmac as io" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T23:18:56.951430Z", "start_time": "2018-02-25T23:18:56.948429Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Read and write Cortex Motion Analysis Corporation ASCII related files.\n", "\n", " read_trc(fname, fname2='_2', units='', df_multi=True): Read .trc file.\n", " read_anc(fname): Read .anc file.\n", " read_cal(fname): Read .cal file.\n", " read_forces(fname): Read .forces file.\n", " write_trc(fname, header, df): Write .trc file.\n", " write_v3dtxt(fname, trc, forces, freq=0): Write Visual3d text file\n", " from .trc and .forces files or dataframes.\n", " grf_moments(data, O): Calculate force plate moments around its origin\n", " given 3 forces, 2 COPs, 1 free moment, and its geometric position.\n", "\n" ] } ], "source": [ "print(io.__doc__)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T23:18:57.866188Z", "start_time": "2018-02-25T23:18:57.862186Z" } }, "outputs": [], "source": [ "import sys, os\n", "\n", "path2 = r'./../data/'\n", "fname = os.path.join(path2, 'arm26_elbow_flex.trc')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T23:18:58.657831Z", "start_time": "2018-02-25T23:18:58.628312Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Opening file \"./../data/arm26_elbow_flex.trc\"\n", "Saving file \"./../data/arm26_elbow_flex_2.trc\"\n" ] } ], "source": [ "h, df = io.read_trc(fname, fname2='_2', units='', df_multi=True)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T23:19:41.398255Z", "start_time": "2018-02-25T23:19:41.382744Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Markerr_acromionr_humerus_epicondyler_radius_styloid
CoordinateXYZXYZXYZ
XYZX1Y1Z1X2Y2Z2X3Y3Z3
Time
0.000000-13.05452439.505476169.505476-12.559380-297.414380199.985620-13.124683-533.569683251.420317
0.008333-12.96064839.599352169.599352-12.567324-297.422324199.977676-12.867025-533.600380251.382550
0.016667-12.85342539.706575169.706575-12.574394-297.429394199.970606-12.582610-533.629817251.345015
0.025000-12.73642939.823571169.823571-12.580312-297.435312199.964688-12.246998-533.658334251.306576
0.033333-12.61355639.946444169.946444-12.584846-297.439846199.960154-11.837471-533.686073251.266170
0.041667-12.48889940.071101170.071101-12.587819-297.442819199.957181-11.333753-533.712924251.222841
0.050000-12.36661040.193390170.193390-12.589113-297.444113199.955887-10.718575-533.738470251.175766
0.058333-12.25076340.309237170.309237-12.588679-297.443679199.956321-9.978059-533.761944251.124273
0.066667-12.14521740.414783170.414783-12.586533-297.441533199.958467-9.101892-533.782204251.067848
0.075000-12.05348740.506513170.506513-12.582760-297.437760199.962240-8.083304-533.797739251.006135
\n", "
" ], "text/plain": [ "Marker r_acromion r_humerus_epicondyle \\\n", "Coordinate X Y Z X Y \n", "XYZ X1 Y1 Z1 X2 Y2 \n", "Time \n", "0.000000 -13.054524 39.505476 169.505476 -12.559380 -297.414380 \n", "0.008333 -12.960648 39.599352 169.599352 -12.567324 -297.422324 \n", "0.016667 -12.853425 39.706575 169.706575 -12.574394 -297.429394 \n", "0.025000 -12.736429 39.823571 169.823571 -12.580312 -297.435312 \n", "0.033333 -12.613556 39.946444 169.946444 -12.584846 -297.439846 \n", "0.041667 -12.488899 40.071101 170.071101 -12.587819 -297.442819 \n", "0.050000 -12.366610 40.193390 170.193390 -12.589113 -297.444113 \n", "0.058333 -12.250763 40.309237 170.309237 -12.588679 -297.443679 \n", "0.066667 -12.145217 40.414783 170.414783 -12.586533 -297.441533 \n", "0.075000 -12.053487 40.506513 170.506513 -12.582760 -297.437760 \n", "\n", "Marker r_radius_styloid \n", "Coordinate Z X Y Z \n", "XYZ Z2 X3 Y3 Z3 \n", "Time \n", "0.000000 199.985620 -13.124683 -533.569683 251.420317 \n", "0.008333 199.977676 -12.867025 -533.600380 251.382550 \n", "0.016667 199.970606 -12.582610 -533.629817 251.345015 \n", "0.025000 199.964688 -12.246998 -533.658334 251.306576 \n", "0.033333 199.960154 -11.837471 -533.686073 251.266170 \n", "0.041667 199.957181 -11.333753 -533.712924 251.222841 \n", "0.050000 199.955887 -10.718575 -533.738470 251.175766 \n", "0.058333 199.956321 -9.978059 -533.761944 251.124273 \n", "0.066667 199.958467 -9.101892 -533.782204 251.067848 \n", "0.075000 199.962240 -8.083304 -533.797739 251.006135 " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.head(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2018-02-25T04:12:04.028888Z", "start_time": "2018-02-25T04:12:04.024886Z" } }, "outputs": [], "source": [ "# %load ./../functions/io_cortexmac.py\n", "\"\"\"Read and write Cortex Motion Analysis Corporation ASCII related files.\n", "\n", " read_trc(fname, fname2='_2', units='', df_multi=True): Read .trc file.\n", " read_anc(fname): Read .anc file.\n", " read_cal(fname): Read .cal file.\n", " read_forces(fname): Read .forces file.\n", " write_trc(fname, header, df): Write .trc file.\n", " write_v3dtxt(fname, trc, forces, freq=0): Write Visual3d text file\n", " from .trc and .forces files or dataframes.\n", " grf_moments(data, O): Calculate force plate moments around its origin\n", " given 3 forces, 2 COPs, 1 free moment, and its geometric position.\n", "\"\"\"\n", "\n", "__author__ = \"Marcos Duarte, https://github.com/demotu/BMC\"\n", "__version__ = \"1.0.1\"\n", "__license__ = \"MIT\"\n", "\n", "import os\n", "import csv\n", "import numpy as np\n", "import pandas as pd\n", "\n", "\n", "def read_trc(fname, fname2='_2', units='', df_multi=True):\n", " \"\"\"Read .trc file format from Cortex MAC.\n", "\n", " This function: 1. Delete markers (columns) of empty data; 2. Correct\n", " number of markers in the header according to the actual number of\n", " non-empty markers; 3. Save a '.trc' file with updated information and\n", " data; 4. Returns header information and data.\n", "\n", " The .trc (Track Row Column) file in ASCII contains X-Y-Z position\n", " data for the reflective markers from a motion capture trial. The\n", " position data for each marker is organized into 3 columns per marker\n", " (X, Y and Z position) with each row being a new frame. The position\n", " data is relative to the global coordinate system of the capture volume\n", " and the position values are in the units used for calibration.\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " Full file name of the .trc file to be opened.\n", "\n", " fname2 : string (default = '_2')\n", " Full file name of the .trc file to be saved with updated information\n", " and data if desired.\n", " If fname2 is '', no file is saved.\n", " If fname2 is '=', the original file name will be used.\n", " If fname2 is a string with length between 1 and 3, this string (other\n", " than '=') is appended to the original file name.\n", "\n", " units : string (default = '')\n", " Change the units of the data if desired.\n", " Accepted output units are 'm' or 'mm'.\n", "\n", " df_multi : bool (default = True)\n", " Whether to output data as pandas multiindex dataframe with \"Marker\"\n", " and \"Coordinate\" as labels and \"Time\" as index (True) or simple\n", " pandas dataframe with \"Frame#\" and \"Time\" as columns (False).\n", "\n", " Returns\n", " -------\n", " h : Python dictionary with .trc header information\n", " keys: header (the .trc full header), data_rate (Hz), camera_rate (Hz),\n", " nframes, nmarkers, markers (names), xyz (X1,Y1,Z1...), units.\n", "\n", " data : pandas dataframe\n", " Two possible output formats according to the `df_multi` option:\n", " Dataframe with shape (nframes, 2+3*nmarkers) with markerxyz as label\n", " and columns: Frame#, time and position data.\n", " Dataframe with shape (nframes, 3*nmarkers) with \"Marker\" and\n", " \"Coordinate\" as labels, \"Time\" as index, and data position as columns.\n", "\n", " \"\"\"\n", "\n", " with open(file=fname, mode='rt', encoding='utf-8', newline='') as f:\n", " print('Opening file \"{}\"'.format(fname))\n", " # get header information\n", " read = csv.reader(f, delimiter='\\t')\n", " header = [next(read) for x in range(5)]\n", " # actual number of markers\n", " nmarkers = int((len(header[3])-2)/3)\n", " # column labels\n", " markers = np.asarray(header[3])[np.arange(2, 2+3*nmarkers, 3)].tolist()\n", " markers3 = [m for m in markers for i in range(3)]\n", " markersxyz = [a+b for a, b in zip(markers3, ['x', 'y', 'z']*nmarkers)]\n", " # read data\n", " df = pd.read_csv(f, sep='\\t', names=['Frame#', 'Time'] + markersxyz,\n", " index_col=False, encoding='utf-8', engine='c')\n", " # drop markers with no data\n", " df.dropna(axis=1, how='all', inplace=True)\n", " # update header\n", " nmarkers = int((df.shape[1]-2)/3)\n", " if header[2][3] != str(nmarkers):\n", " print(' Number of markers changed from {} to {}.'\n", " .format(header[2][3], nmarkers))\n", " header[2][3] = str(nmarkers)\n", " header[3] = ['' if c[-1] in ['y', 'z'] else c[:-1] if c[-1] in ['x']\n", " else c for c in df.columns.values.tolist()] + ['']\n", " markers = np.asarray(header[3])[np.arange(2, 2+3*nmarkers, 3)].tolist()\n", " n3 = np.repeat(range(1, nmarkers+1), 3).tolist()\n", " xyz = [a+str(b) for a, b in zip(['X', 'Y', 'Z']*nmarkers, n3)]\n", " header[4] = ['', ''] + xyz\n", " if units == 'm':\n", " if header[2][4] == 'mm':\n", " df.iloc[:, 2:] = df.iloc[:, 2:]/1000\n", " header[2][4] = 'm'\n", " print(' Units changed from {} to {}'.format('\"mm\"', '\"m\"'))\n", " elif units == 'mm':\n", " if header[2][4] == 'm':\n", " df.iloc[:, 2:] = df.iloc[:, 2:]*1000\n", " header[2][4] = 'mm'\n", " print(' Units changed from {} to {}'.format('\"m\"', '\"mm\"'))\n", "\n", " # save file\n", " if len(fname2):\n", " if fname2 == '=':\n", " fname2 = fname\n", " elif len(fname2) <= 3:\n", " name, extension = os.path.splitext(fname)\n", " fname2 = name + fname2 + extension\n", "\n", " write_trc(fname2, header, df)\n", "\n", " # outputs\n", " h = {'header': header,\n", " 'data_rate': float(header[2][0]),\n", " 'camera_rate': float(header[2][1]),\n", " 'nframes': int(header[2][2]),\n", " 'nmarkers': int(header[2][3]),\n", " 'markers': markers,\n", " 'xyz': xyz,\n", " 'units': header[2][4],\n", " 'fname': fname,\n", " 'fname2': fname2}\n", " if df_multi:\n", " df.drop(labels='Frame#', axis=1, inplace=True)\n", " df.set_index('Time', inplace=True)\n", " df.index.name = 'Time'\n", " cols = [s[:-1] for s in df.columns.str.replace(r'.', '')]\n", " df.columns = [cols, list('XYZ')*int(df.shape[1]/3)]\n", " df.columns.set_names(names=['Marker', 'Coordinate'], level=[0, 1], inplace=True)\n", "\n", " return h, df\n", "\n", "\n", "def read_anc(fname):\n", " \"\"\"Read .anc file format from Cortex MAC.\n", "\n", " The .anc (Analog ASCII Row Column) file contain ASCII analog data\n", " in row-column format. The data is derived from *.anb analog binary\n", " files. These binary *.anb files are generated simultaneously with\n", " video *.vc files if an optional analog input board is used in\n", " conjunction with video data capture.\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " full file name of the .anc file to be opened\n", "\n", " Returns\n", " -------\n", " h : Python dictionary\n", " .anc header information\n", " keys: nbits, polarity, nchannels, data_rate, ch_names, ch_ranges\n", "\n", " data : pandas dataframe\n", " analog data with shape (nframes, nchannels)\n", "\n", " \"\"\"\n", "\n", " with open(file=fname, mode='rt', encoding='utf-8', newline='') as f:\n", " # get header information\n", " read = csv.reader(f, delimiter='\\t')\n", " header = [next(read) for x in range(11)]\n", " h = {'nbits': int(header[3][1]),\n", " 'polarity': header[1][3],\n", " 'nchannels': int(header[2][7]),\n", " 'data_rate': float(header[3][3]),\n", " 'ch_names': header[8],\n", " 'ch_ranges': header[10]}\n", " h['ch_names'] = h['ch_names'][1:-1]\n", " h['ch_ranges'] = np.asarray(h['ch_ranges'][1:-1], dtype=np.float)\n", " # analog data\n", " data = pd.read_csv(f, sep='\\t', names=h['ch_names'], engine='c',\n", " usecols=np.arange(1, 1+h['nchannels']))\n", " # convert ADC (bit) values to engineering units:\n", " data *= h['ch_ranges']/(2**h['nbits']/2 - 2)\n", "\n", " return h, data\n", "\n", "\n", "def read_cal(fname):\n", " \"\"\"Read .cal file format from Cortex MAC.\n", "\n", " The .cal (force plate calibration parameters) file in ASCII contains:\n", "\n", " {1}\n", " {2}\n", " {3}\n", " \n", " \n", " <3 x 3 orientation matrix>\n", " ...repeat for next force plate...\n", "\n", " {1}: for a Kistler force plate, there is a 'K' after the number\n", " {2}: the scale is the inverse of the gain\n", " {3}: N equal 8 for Kistler and equal 6 for all AMTI and Bertec\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " full file name of the .trc file to be opened\n", "\n", " Returns\n", " -------\n", " forcepla : Python dictionary\n", " parameter from the froce plate calibration file\n", " keys: 'fp', 'scale', 'size', 'cal_matrix', 'origin', 'center', 'orientation'\n", " \"\"\"\n", "\n", " fp, scale, size, cal_matrix, origin, center, orientation = [], [], [], [], [], [], []\n", " with open(file=fname, mode='rt', encoding='utf-8', newline='') as f:\n", " reader = csv.reader(f, delimiter=' ')\n", " for row in reader:\n", " # force plate number\n", " fp.append(int(row[0][0]))\n", " # number of rows for Kistler or AMTI/Bertec force plate\n", " n = 8 if row[0][-1] == 'K' else 6\n", " # scale (inverse of the gain)\n", " scale_size = np.array(next(reader)).astype(np.float)\n", " scale.append(scale_size[0])\n", " # force plate length (cm) and width (cm)\n", " size.append(scale_size[1:])\n", " # calibration matrix (the inverse sensitivity matrix)\n", " matrix = [next(reader) for x in range(n)]\n", " cal_matrix.append(np.array(matrix).astype(np.float))\n", " # true origin in relation to the geometric center (cm)\n", " origin.append(np.array(next(reader)).astype(np.float))\n", " # geometric center in relation to LCS origin (cm)\n", " center.append(np.array(next(reader)).astype(np.float))\n", " # 3 x 3 orientation matrix\n", " orienta = [next(reader) for x in range(3)]\n", " orientation.append(np.array(orienta).astype(np.float))\n", "\n", " forcepla = {'fp': fp, 'scale': scale, 'size': size, 'cal_matrix': cal_matrix,\n", " 'origin': origin, 'center': center, 'orientation': orientation}\n", "\n", " return forcepla\n", "\n", "\n", "def read_forces(fname):\n", " \"\"\"Read .forces file format from Cortex MAC.\n", "\n", " The .forces file in ASCII contains force plate data. The data is saved\n", " based on the forcepla.cal file of the trial and converts the raw force\n", " plate data into calibrated forces. The units used are Newtons and\n", " Newton-meters and each line in the file equates to one analog sample.\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " full file name of the .forces file to be opened\n", "\n", " Returns\n", " -------\n", " h : Python dictionary\n", " .forces header information\n", " keys: name, nforceplates, data_rate, nsamples, ch_names\n", "\n", " data : pandas dataframe\n", " force plate data with shape (nsamples, 7*nforceplates)\n", "\n", " \"\"\"\n", "\n", " with open(file=fname, mode='rt', encoding='utf-8', newline='') as f:\n", " # get header information\n", " read = csv.reader(f, delimiter='\\t')\n", " header = [next(read) for x in range(5)]\n", " h = {'name': header[0][0],\n", " 'nforceplates': int(header[1][0].split('=')[1]),\n", " 'data_rate': float(header[2][0].split('=')[1]),\n", " 'nsamples': int(header[3][0].split('=')[1]),\n", " 'ch_names': header[4][1:]}\n", " # force plate data\n", " data = pd.read_csv(f, sep='\\t', names=h['ch_names'], index_col=False,\n", " usecols=np.arange(1, 1+7*h['nforceplates']), engine='c')\n", "\n", " return h, data\n", "\n", "\n", "def write_trc(fname, header, df):\n", " \"\"\"Write .trc file format from Cortex MAC.\n", "\n", " See the read_trc.py function.\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " Full file name of the .trc file to be saved.\n", "\n", " header : list of lists\n", " header for the .trc file\n", "\n", " df : pandas dataframe\n", " dataframe with data for the .trc file (with frame and time columns)\n", "\n", " \"\"\"\n", "\n", " with open(file=fname, mode='wt', encoding='utf-8', newline='') as f:\n", " print('Saving file \"{}\"'.format(fname))\n", " for line in header:\n", " f.write('\\t'.join(line) + '\\n')\n", " f.write('\\n')\n", " df.to_csv(f, header=None, index=None, sep='\\t',\n", " line_terminator='\\t\\n') # float_format='%.8f'\n", "\n", "\n", "def write_v3dtxt(fname, trc, forces, freq=0):\n", " \"\"\"Write Visual3d text file from .trc and .forces files or dataframes.\n", "\n", " The .trc and .forces data are assumed to correspond to the same time\n", " interval. If the data have different number of samples (different\n", " frequencies), the data will be resampled to the highest frequency (or to\n", " the inputed frequency if it is higher than the former two) using the tnorm\n", " function.\n", "\n", " Parameters\n", " ----------\n", " fname : string\n", " Full file name of the Visual3d text file to be saved.\n", "\n", " trc : pandas dataframe or string\n", " If string, it is a full file name of the .trc file to read.\n", " If dataframe, data of the .trc file has shape (nsamples, 2 + 3*nmarkers)\n", " where the first two columns are from the Frame and Time values.\n", "\n", " forces : pandas dataframe or string\n", " If string, it is a full file name of the .forces file to read.\n", " If dataframe, data of the .forces file has shape (nsamples, 7*nforceplates)\n", "\n", " freq : float (optional, dafault=0)\n", " Sampling frequency in Hz to resample data if desired.\n", " Data will be resampled to the highest frequency between freq, trc, forces.\n", "\n", " \"\"\"\n", "\n", " if isinstance(trc, str):\n", " _, trc = read_trc(trc, fname2='', units='', df_multi=False)\n", " if isinstance(forces, str):\n", " _, forces = read_forces(forces)\n", "\n", " if trc.shape[0] != forces.shape[0] or freq:\n", " from tnorm import tnorm\n", " freq_trc = 1/np.nanmean(np.diff(trc.iloc[:, 1].values))\n", " freq_forces = freq_trc*(forces.shape[0]/trc.shape[0])\n", " freq = np.max([freq, freq_trc, freq_forces])\n", " nsample = np.max([trc.shape[0], forces.shape[0]]) * freq/(np.max([freq_trc, freq_forces]))\n", " trc2, _, _ = tnorm(trc.iloc[:, 2:].values, step=-nsample)\n", " trc2 = np.hstack((np.vstack((np.arange(1, nsample+1, 1),\n", " np.arange(0, nsample, 1)/freq)).T, trc2))\n", " trc = pd.DataFrame(trc2, index=None, columns=trc.columns)\n", " forces2, _, _ = tnorm(forces.values, step=-nsample)\n", " forces = pd.DataFrame(forces2, index=None, columns=forces.columns)\n", "\n", " ntrc = trc.shape[1]\n", " nforces = forces.shape[1]\n", " data = pd.concat([trc, forces], axis=1)\n", "\n", " with open(file=fname, mode='wt', encoding='utf-8', newline='') as f:\n", " rows = [[''] + ['default']*(ntrc + nforces - 1),\n", " [''] + data.columns.tolist()[1:],\n", " [''] + ['FRAME_NUMBERS'] + ['TARGET']*(ntrc - 2) + ['ANALOG']*nforces,\n", " [''] + ['ORIGINAL']*(ntrc + nforces -1),\n", " [data.columns[0]] + ['0'] + ['X', 'Y', 'Z']*int((ntrc - 2)/3) + ['0']*nforces]\n", " write = csv.writer(f, delimiter='\\t')\n", " write.writerows(rows)\n", " write.writerows(data.values)\n", "\n", "\n", "def grf_moments(data, O):\n", " \"\"\"Calculate force plate moments around its origin given\n", " 3 forces, 2 COPs, 1 free moment, and its geometric position.\n", "\n", " Parameters\n", " ----------\n", " data : Numpy array (n, 7)\n", " array with [Fx, Fy, Fz, COPx, COPy, COPz, Tz].\n", " O : Numpy array-like or list\n", " origin [x,y,z] of the force plate in the motion capture coordinate system [in meters].\n", "\n", " Returns\n", " -------\n", " grf : Numpy array (n, 8)\n", " array with [Fx, Fy, Fz, Mx, My, Mz]\n", " \"\"\"\n", "\n", " Fx, Fy, Fz, COPx, COPy, COPz, Tz = np.hsplit(data, 7)\n", "\n", " COPz = np.nanmean(COPz) # most cases is zero\n", "\n", " Mx = COPy*Fz + COPz*Fy\n", " My = -COPx*Fz - COPz*Fx\n", " Mz = Tz + COPx*Fy - COPy*Fx\n", "\n", " Mx = Mx - Fy*O[2] + Fz*O[1]\n", " My = My - Fz*O[0] + Fx*O[2]\n", " Mz = Mz - Fx*O[1] + Fy*O[0]\n", "\n", " grf = np.hstack((Fx, Fy, Fz, Mx, My, Mz))\n", "\n", " return grf\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" }, "nbTranslate": { "displayLangs": [ "*" ], "hotkey": "alt-t", "langInMainMenu": true, "sourceLang": "en", "targetLang": "fr", "useGoogleTranslate": true }, "toc": { "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "toc_cell": false, "toc_position": {}, "toc_section_display": "block", "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 1 }