Skip to content

Getting Started

Installation

Before you can use pyomeca, you will need to get it installed. Pyomeca itself is a pure Python package, but its dependencies are not. The easiest way to get everything installed is to use conda.

To install pyomeca with its recommended dependencies using the conda command line tool:

conda install -c conda-forge pyomeca
Now that you have installed pyomeca, you should be able to import it:

import pyomeca

Note

Want to test pyomeca from your browser and without installing anything? Try out our binder server: Binder

Quick overview

Here is a short introduction to xarray and pyomeca, geared mainly for new users. You should be able to follow along and complete this short example in about 10 minutes.

We will carry out common tasks in biomechanics, including reading files, manipulating and processing data, making figures and writing files.

Object creation

Let's begin by creating a biomechanical data structure with pyomeca. In this tutorial, we will analyze the skin marker data available in this c3d file.

Pyomeca provides the from_c3d function to read c3d files. As we want to analyse markers data, we will use the Markers class:

from pyomeca import Markers

data_path = "../tests/data/markers_analogs.c3d"
markers = Markers.from_c3d(data_path, prefix_delimiter=":")

Make sure to always have a check on the data after reading it.

When used in Jupyter notebooks, data can be explored interactively. A standard text representation is available otherwise.

markers
Show/Hide data repr Show/Hide attributes
xarray.DataArray
'markers'
  • axis: 4
  • channel: 51
  • time: 580
  • 44.16 44.17 44.16 44.17 44.17 44.19 44.2 ... 1.0 1.0 1.0 1.0 1.0 1.0
    array([[[  44.16278839,   44.16666412,   44.16487122, ...,
               99.22426605,   99.24201965,   99.25963593],
            [  32.57229614,   32.57104111,   32.56489563, ...,
               87.51286316,   87.52822876,   87.54118347],
            [ -93.72181702,  -93.72447968,  -93.72324371, ...,
              -41.1590271 ,  -41.14812851,  -41.12734985],
            ...,
            [ 562.26068115,  562.41027832,  562.56695557, ...,
              625.63555908,  625.98504639,  626.25811768],
            [ 568.24200439,  568.37792969,  568.49249268, ...,
              624.18139648,  624.51190186,  624.78894043],
            [ 568.44470215,  568.52038574,  568.59216309, ...,
              623.09222412,  623.44036865,  623.75152588]],
    
           [[-276.86193848, -276.86169434, -276.86407471, ...,
             -259.15292358, -259.16690063, -259.17092896],
            [-243.14048767, -243.14073181, -243.13331604, ...,
             -225.44718933, -225.45556641, -225.46226501],
            [ 124.78598022,  124.78731537,  124.78870392, ...,
              141.820755  ,  141.80741882,  141.80308533],
            ...,
            [ 638.35144043,  638.4241333 ,  638.50653076, ...,
              592.00372314,  592.15686035,  592.27819824],
            [ 626.79144287,  626.86114502,  626.90710449, ...,
              584.15661621,  584.27709961,  584.38830566],
            [ 651.37927246,  651.45532227,  651.49957275, ...,
              610.59655762,  610.72821045,  610.84472656]],
    
           [[ 675.69683838,  675.69873047,  675.6986084 , ...,
              903.97650146,  903.96801758,  903.980896  ],
            [ 676.57452393,  676.58099365,  676.57720947, ...,
              904.61694336,  904.61645508,  904.63104248],
            [ 674.27874756,  674.27947998,  674.28033447, ...,
              902.77349854,  902.77819824,  902.78143311],
            ...,
            [  81.34425354,   81.32899475,   81.2776413 , ...,
               53.45215607,   53.57727814,   53.72877121],
            [ 110.84020996,  110.81329346,  110.76582336, ...,
               83.92819214,   84.07093048,   84.20204163],
            [ 129.69673157,  129.6789856 ,  129.62939453, ...,
               99.39131165,   99.52735138,   99.68258667]],
    
           [[   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ],
            [   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ],
            [   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ],
            ...,
            [   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ],
            [   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ],
            [   1.        ,    1.        ,    1.        , ...,
                1.        ,    1.        ,    1.        ]]])
    • axis
      (axis)
      <U4
      'x' 'y' 'z' 'ones'
      array(['x', 'y', 'z', 'ones'], dtype='<U4')
    • channel
      (channel)
      <U14
      'gauche_ext' ... 'LATH'
      array(['gauche_ext', 'gauche_int', 'droite_int', 'droite_ext', 'avant_gauche',
             'avant_droit', 'arriere_droit', 'arriere_gauche', 'ASISr', 'ASISl',
             'PSISr', 'PSISl', 'STERr', 'STERl', 'STER', 'XIPH', 'T1', 'T10',
             'CLAV_SC', 'CLAVm', 'CLAV_ant', 'CLAV_post', 'CLAVl', 'CLAV_AC',
             'ACRO_tip', 'SCAP_AA', 'SCAPl', 'SCAPm', 'SCAP_CP', 'SCAP_RS',
             'SCAP_SA', 'SCAP_IA', 'DELT', 'ARMl', 'ARMm', 'ARMp_up', 'ARMp_do',
             'EPICl', 'EPICm', 'LARMm', 'LARMl', 'LARM_elb', 'LARM_ant', 'STYLr',
             'STYLr_up', 'STYLu', 'WRIST', 'INDEX', 'LASTC', 'MEDH', 'LATH'],
            dtype='<U14')
    • time
      (time)
      float64
      0.0 0.01 0.02 ... 5.77 5.78 5.79
      array([0.  , 0.01, 0.02, ..., 5.77, 5.78, 5.79])
  • first_frame :
    0
    last_frame :
    579
    rate :
    100.0
    units :
    mm

In this case, we have generated a 3D array with the axis, channel and time dimensions.

Note

There are several ways to create objects in pyomeca: by supplying data, from files or from other data-structure. A more complete guide on object creation is available in the object creation section of the documentation.

Indexing

Since we have labels associated with each dimension, we have several kinds of indexing available with varying levels of convenience and intuitiveness. Let's see three ways to get the same data (ninth marker and first time frame).

Positional indexing

Indexing directly works just like it does for numpy arrays.

markers[:, 9, 0]
<xarray.DataArray 'markers' (axis: 4)>
array([753.43908691,  75.9487381 , 187.7590332 ,   1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'ASISl'
    time     float64 0.0
Attributes:
    first_frame:  0
    last_frame:   579
    rate:         100.0
    units:        mm

Label-based indexing

Label-based indexing frees us from having to know how the data are organized. We do not have to rely on dimension order and can use them explicitly to index the data.

The isel method is used when the dimension lookup is by name and the index lookup is by integer

markers.isel(channel=9, time=0)
<xarray.DataArray 'markers' (axis: 4)>
array([753.43908691,  75.9487381 , 187.7590332 ,   1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'ASISl'
    time     float64 0.0
Attributes:
    first_frame:  0
    last_frame:   579
    rate:         100.0
    units:        mm

The sel method is used when the dimension and index lookups are both by name.

markers.sel(channel="ASISl", time=0)
<xarray.DataArray 'markers' (axis: 4)>
array([753.43908691,  75.9487381 , 187.7590332 ,   1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'ASISl'
    time     float64 0.0
Attributes:
    first_frame:  0
    last_frame:   579
    rate:         100.0
    units:        mm

Note

xarray offers extremely flexible indexing routines. For more details on indexing see the xarray documentation.

Metadata

Biomechanical datasets are usually more than just raw numbers and have various metadata attributes. Some metadata are already filled by pyomeca and available in the attrs Python dictionary.

markers.attrs
 >> {'first_frame': 0, 'last_frame': 579, 'rate': 100.0, 'units': 'mm'}

They can be accessed using the standard dictionary indexing

markers.attrs["rate"]
 >> 100.0

Or directly as a propriety

markers.rate
 >> 100.0

You can assign anything you wish.

markers.attrs["description"] = "Skin marker positions recorded in Montreal."
markers.attrs["participant_id"] = 12
markers.time.attrs["units"] = "seconds"
markers
<xarray.DataArray 'markers' (axis: 4, channel: 51, time: 580)>
array([[[  44.16278839,   44.16666412,   44.16487122, ...,
           99.22426605,   99.24201965,   99.25963593],
        [  32.57229614,   32.57104111,   32.56489563, ...,
           87.51286316,   87.52822876,   87.54118347],
        [ -93.72181702,  -93.72447968,  -93.72324371, ...,
          -41.1590271 ,  -41.14812851,  -41.12734985],
        ...,
        [ 562.26068115,  562.41027832,  562.56695557, ...,
          625.63555908,  625.98504639,  626.25811768],
        [ 568.24200439,  568.37792969,  568.49249268, ...,
          624.18139648,  624.51190186,  624.78894043],
        [ 568.44470215,  568.52038574,  568.59216309, ...,
          623.09222412,  623.44036865,  623.75152588]],

       [[-276.86193848, -276.86169434, -276.86407471, ...,
         -259.15292358, -259.16690063, -259.17092896],
        [-243.14048767, -243.14073181, -243.13331604, ...,
         -225.44718933, -225.45556641, -225.46226501],
        [ 124.78598022,  124.78731537,  124.78870392, ...,
          141.820755  ,  141.80741882,  141.80308533],
        ...,
        [ 638.35144043,  638.4241333 ,  638.50653076, ...,
          592.00372314,  592.15686035,  592.27819824],
        [ 626.79144287,  626.86114502,  626.90710449, ...,
          584.15661621,  584.27709961,  584.38830566],
        [ 651.37927246,  651.45532227,  651.49957275, ...,
          610.59655762,  610.72821045,  610.84472656]],

       [[ 675.69683838,  675.69873047,  675.6986084 , ...,
          903.97650146,  903.96801758,  903.980896  ],
        [ 676.57452393,  676.58099365,  676.57720947, ...,
          904.61694336,  904.61645508,  904.63104248],
        [ 674.27874756,  674.27947998,  674.28033447, ...,
          902.77349854,  902.77819824,  902.78143311],
        ...,
        [  81.34425354,   81.32899475,   81.2776413 , ...,
           53.45215607,   53.57727814,   53.72877121],
        [ 110.84020996,  110.81329346,  110.76582336, ...,
           83.92819214,   84.07093048,   84.20204163],
        [ 129.69673157,  129.6789856 ,  129.62939453, ...,
           99.39131165,   99.52735138,   99.68258667]],

       [[   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ],
        [   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ],
        [   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ],
        ...,
        [   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ],
        [   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ],
        [   1.        ,    1.        ,    1.        , ...,
            1.        ,    1.        ,    1.        ]]])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
  * channel  (channel) <U14 'gauche_ext' 'gauche_int' ... 'MEDH' 'LATH'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 ... 5.75 5.76 5.77 5.78 5.79
Attributes:
    first_frame:     0
    last_frame:      579
    rate:            100.0
    units:           mm
    description:     Skin marker positions recorded in Montreal.
    participant_id:  12

Computation

Arithmetic

As the underlying data-structure is a numpy array, xarray data arrays work the same way you would expect if you are used to numpy.

subset = markers[:, 0, :6]
subset + 10
<xarray.DataArray 'markers' (axis: 4, time: 6)>
array([[  54.16278839,   54.16666412,   54.16487122,   54.16558075,
          54.17311096,   54.18517685],
       [-266.86193848, -266.86169434, -266.86407471, -266.86123657,
        -266.85812378, -266.85818481],
       [ 685.69683838,  685.69873047,  685.6986084 ,  685.69775391,
         685.7041626 ,  685.69592285],
       [  11.        ,   11.        ,   11.        ,   11.        ,
          11.        ,   11.        ]])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 0.05
subset.T  # transpose
<xarray.DataArray 'markers' (time: 6, axis: 4)>
array([[  44.16278839, -276.86193848,  675.69683838,    1.        ],
       [  44.16666412, -276.86169434,  675.69873047,    1.        ],
       [  44.16487122, -276.86407471,  675.6986084 ,    1.        ],
       [  44.16558075, -276.86123657,  675.69775391,    1.        ],
       [  44.17311096, -276.85812378,  675.7041626 ,    1.        ],
       [  44.18517685, -276.85818481,  675.69592285,    1.        ]])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 0.05
Attributes:
    first_frame:     0
    last_frame:      579
    rate:            100.0
    units:           mm
    description:     Skin marker positions recorded in Montreal.
    participant_id:  12
subset.mean()
<xarray.DataArray 'markers' ()>
array(111.00187318)
Coordinates:
    channel  <U14 'gauche_ext'
subset.mean(axis=1)
<xarray.DataArray 'markers' (axis: 4)>
array([  44.16969872, -276.86087545,  675.69866943,    1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'

We can, however, take advantage of the labels instead of axis numbers

subset.mean(dim="time")
<xarray.DataArray 'markers' (axis: 4)>
array([  44.16969872, -276.86087545,  675.69866943,    1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'

Arithmetic operations broadcast based on dimension name. This means you don’t need to insert dummy dimensions for alignment:

only_first_axis = subset.sel(axis="x")
only_first_frame = subset.isel(time=0)
only_first_axis
<xarray.DataArray 'markers' (time: 6)>
array([44.16278839, 44.16666412, 44.16487122, 44.16558075, 44.17311096,
       44.18517685])
Coordinates:
    axis     <U4 'x'
    channel  <U14 'gauche_ext'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 0.05
Attributes:
    first_frame:     0
    last_frame:      579
    rate:            100.0
    units:           mm
    description:     Skin marker positions recorded in Montreal.
    participant_id:  12
only_first_frame
<xarray.DataArray 'markers' (axis: 4)>
array([  44.16278839, -276.86193848,  675.69683838,    1.        ])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'
    time     float64 0.0
Attributes:
    first_frame:     0
    last_frame:      579
    rate:            100.0
    units:           mm
    description:     Skin marker positions recorded in Montreal.
    participant_id:  12
only_first_frame + only_first_axis
<xarray.DataArray 'markers' (axis: 4, time: 6)>
array([[  88.32557678,   88.32945251,   88.32765961,   88.32836914,
          88.33589935,   88.34796524],
       [-232.69915009, -232.69527435, -232.69706726, -232.69635773,
        -232.68882751, -232.67676163],
       [ 719.85962677,  719.8635025 ,  719.86170959,  719.86241913,
         719.86994934,  719.88201523],
       [  45.16278839,   45.16666412,   45.16487122,   45.16558075,
          45.17311096,   45.18517685]])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 0.05

Let's compare with the sum made with numpy arrays:

only_first_axis.values + only_first_axis.values
 >> array([88.32557678, 88.33332825, 88.32974243, 88.3311615 , 88.34622192,
       88.3703537 ])

And we do not need to worry about the order of dimensions.

subset - subset.T
<xarray.DataArray 'markers' (axis: 4, time: 6)>
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])
Coordinates:
  * axis     (axis) <U4 'x' 'y' 'z' 'ones'
    channel  <U14 'gauche_ext'
  * time     (time) float64 0.0 0.01 0.02 0.03 0.04 0.05

Whereas this is not possible with numpy arrays:

subset.values - subset.values.T
>> ValueError: operands could not be broadcast together with shapes (4,6) (6,4) 

Note

xarray supports powerful shortcuts for computation. For more, see the xarray documentation.

Application: exploring missing values

When we visualize some of our markers, we can realize that there are some missing values.

markers.sel(axis="x", channel="SCAP_CP").plot.line(x="time")

svg

Let's investigate those missing values

markers_null_values = markers.sel(axis="x").isnull()
print(f"There are {markers_null_values.sum().values} missing values")
 >> There are 305 missing values

What are the 5 markers with the most missing values?

markers_null_values.sum(dim="time").to_series().nlargest(5).plot.barh()

svg

The cumulative number of missing values can tell us when marker occlusions occur.

markers_null_values.sum("channel").cumsum("time").plot()

svg

Now that we know more about the missing values, we can use xarray for filling missing values via 1D interpolation.

markers_without_null = markers.interpolate_na(dim="time", method="cubic")

Now, let's visualize our interpolated markers.

import matplotlib.pyplot as plt

markers.sel(axis="x", channel="SCAP_CP").plot.line(x="time")
(
    markers_without_null.where(markers.isnull())
    .sel(axis="x", channel="SCAP_CP")
    .plot.line(x="time", label="interpolated", color="r")
)
plt.legend()

svg

(
    markers_without_null.drop_sel(axis="ones")  # drop the axis with only ones
    .isel(channel=slice(10, 16))  # select some markers
    .plot.line(x="time", col="channel", hue="axis", col_wrap=3)  # plot the data
)

svg

Application: electromyographic pipeline

Pyomeca implements specialized functionalities commonly used in biomechanics.

As an example, let's process the electromyographic data contained in our c3d file.

from pyomeca import Analogs

muscles = [
    "Delt_ant",
    "Delt_med",
    "Delt_post",
    "Supra",
    "Infra",
    "Subscap",
]
emg = Analogs.from_c3d(data_path, suffix_delimiter=".", usecols=muscles)
emg.plot(x="time", col="channel", col_wrap=3)

svg

emg_processed = (
    emg.meca.band_pass(order=2, cutoff=[10, 425])
    .meca.center()
    .meca.abs()
    .meca.low_pass(order=4, cutoff=5, freq=emg.rate)
    .meca.normalize()
)

emg_processed.plot(x="time", col="channel", col_wrap=3)

svg

By updating the metadata (attrs dictionary), we can update the name and units on our plots.

emg_processed.name = "EMG"
emg_processed.attrs["units"] = "%"
emg_processed.time.attrs["units"] = "seconds"

emg_processed.plot(x="time", col="channel", col_wrap=3)

svg

fig, axes = plt.subplots(ncols=2, figsize=(10, 4))

emg_processed.mean("channel").plot(ax=axes[0])
axes[0].set_title("Mean EMG activation")

emg_processed.plot.hist(ax=axes[1], bins=50)
axes[1].set_title("EMG activation distribution")

svg

By converting the data array to a pandas dataframe, we can further extend the possibilities:

emg_dataframe = emg_processed.meca.to_wide_dataframe()
emg_dataframe.plot.box(showfliers=False)

svg

emg_dataframe.corr().style.background_gradient().set_precision(2)
channel Delt_ant Delt_med Delt_post Infra Subscap Supra
channel
Delt_ant 1.0 0.78 0.38 0.74 0.6 0.6
Delt_med 0.78 1.0 0.77 0.74 0.76 0.9
Delt_post 0.38 0.77 1.0 0.62 0.67 0.84
Infra 0.74 0.74 0.62 1.0 0.61 0.75
Subscap 0.6 0.76 0.67 0.61 1.0 0.78
Supra 0.6 0.9 0.84 0.75 0.78 1.0

Note

For more details, see the data processing section of the documentation.

Datasets

Datasets are a useful xarray feature to store multiple data arrays with common dimensions

import xarray as xr
import numpy as np
trials = xr.Dataset(
    {
        "trial 1": emg,
        "trial 2": emg * np.random.rand(),
        "trial 3": emg * np.random.rand(),
    }
)
trials
<xarray.Dataset>
Dimensions:  (channel: 6, time: 11600)
Coordinates:
  * channel  (channel) <U9 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'
  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8
Data variables:
    trial 1  (channel, time) float64 -2.609e-05 -2.544e-05 ... 3.04e-05
    trial 2  (channel, time) float64 -5.21e-06 -5.081e-06 ... 6.071e-06
    trial 3  (channel, time) float64 -1.355e-05 -1.321e-05 ... 1.578e-05

We can still access the individual data arrays

trials["trial 1"]
<xarray.DataArray 'trial 1' (channel: 6, time: 11600)>
array([[-2.60891229e-05, -2.54411752e-05, -2.45576939e-05, ...,
        -1.93931046e-05, -1.97550762e-05, -1.91258678e-05],
       [-4.02107289e-05, -6.80835801e-05, -8.64052563e-05, ...,
         4.98310801e-05,  4.22991216e-05,  3.81295613e-05],
       [-1.36110339e-05, -1.32793148e-05, -1.27393068e-05, ...,
        -2.54613460e-05, -2.30687110e-05, -1.89858001e-05],
       [ 2.97530321e-04,  1.55170274e-04,  3.52776406e-05, ...,
        -2.58133630e-04, -4.46292252e-04, -5.75785409e-04],
       [ 2.23239495e-05,  2.26500360e-05,  2.28447316e-05, ...,
         8.95366975e-06, -3.41701161e-06,  1.05170475e-05],
       [ 2.87022194e-05,  2.90283060e-05,  2.90723892e-05, ...,
         2.92962086e-05,  2.90890512e-05,  3.03986035e-05]])
Coordinates:
  * channel  (channel) <U9 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'
  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8
Attributes:
    first_frame:  0
    last_frame:   11580
    rate:         2000.0
    units:        V

While being able to do indexing and computation on the whole dataset

trials.sel(channel="Infra") + 100
<xarray.Dataset>
Dimensions:  (time: 11600)
Coordinates:
    channel  <U9 'Infra'
  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8
Data variables:
    trial 1  (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0
    trial 2  (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0
    trial 3  (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0

Note

If you are processing large amount of data, you can leverage Dask to support parallel computations. Dask divides large amount of data into manageable chunks and represents parallel computations with task graphs that are executed either on your laptop or on a large cluster of machines. Xarray has built-in support for dask on both DataArray and Dataset. To see examples and use cases, check out the xarray documentation.

File IO

NetCDF is the recommended file format to save and share xarray objects.

emg.to_netcdf("emg.nc")
xr.open_dataarray("emg.nc")
<xarray.DataArray 'analogs' (channel: 6, time: 11600)>
array([[-2.608912e-05, -2.544118e-05, -2.455769e-05, ..., -1.939310e-05,
        -1.975508e-05, -1.912587e-05],
       [-4.021073e-05, -6.808358e-05, -8.640526e-05, ...,  4.983108e-05,
         4.229912e-05,  3.812956e-05],
       [-1.361103e-05, -1.327931e-05, -1.273931e-05, ..., -2.546135e-05,
        -2.306871e-05, -1.898580e-05],
       [ 2.975303e-04,  1.551703e-04,  3.527764e-05, ..., -2.581336e-04,
        -4.462923e-04, -5.757854e-04],
       [ 2.232395e-05,  2.265004e-05,  2.284473e-05, ...,  8.953670e-06,
        -3.417012e-06,  1.051705e-05],
       [ 2.870222e-05,  2.902831e-05,  2.907239e-05, ...,  2.929621e-05,
         2.908905e-05,  3.039860e-05]])
Coordinates:
  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8
  * channel  (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'
Attributes:
    first_frame:  0
    last_frame:   11580
    rate:         2000.0
    units:        V
trials.to_netcdf("trials.nc")
xr.open_dataset("trials.nc")
<xarray.Dataset>
Dimensions:  (channel: 6, time: 11600)
Coordinates:
  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8
  * channel  (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'
Data variables:
    trial 1  (channel, time) float64 ...
    trial 2  (channel, time) float64 ...
    trial 3  (channel, time) float64 ...

Pyomeca implements function to read various file format commonly used in biomechanics such as c3d, csv, xlsx, sto, trc and mot.

Users can also write Matlab and csv files.

emg.meca.to_matlab("emg.mat")
emg.meca.to_csv("emg.csv")