pyomeca / ezc3d

Easy to use C3D reader/writer for C++, Python and Matlab
https://pyomeca.github.io/Documentation/ezc3d/index.html
MIT License
142 stars 44 forks source link

Signals EMG #261

Closed monsitacatalina closed 1 year ago

monsitacatalina commented 2 years ago

Hello! I have some EMG signals saved in a .c3d file obtained from VICON, I wanted to know how I can extract these signals to process them. Thank you

pariterre commented 2 years ago

Hi there! This really depends on which platform you work on.. Are you working in C++, matlab or Python? If you are interested in the Raw signals, you can have a look at the readme in the relevent section, it shows how to extract analogs for all the platforms.

If you are interested in filtered data and you are in Python, may I suggest to have a look a https://github.com/pyomeca/pyomeca which is a library that offers preselected filters for analysing biomechanics data, including EMG.

aymanhab commented 1 year ago

Hi @pariterre, there's some interest in getting EMG data from .c3d files in OpenSim as well, which means we need to extend/modify our interface to ezc3d in C++ to make available in Python, Matlab. Some specific questions:

  1. Does c3d format support querying for the list of available quantities? So we change interface once and get all quantities rather than once for EMG, then later for IMU ....etc.
  2. Are there analogous calibration parameters for various quantities (e.g. EMG range/max/filtering/....) that get applied to the raw data, or we get the raw data as is?

Thanks

pariterre commented 1 year ago

Hi @aymanhab There is no standardized way to store EMG in a C3D file, as far as the format is concerned, it stores analog data, no matter what these data are. Obviously, each software has their own "standardized" way of doing so (so they themselves know which type of data are stored where). This means that we must gather the analog data and parse them by name according to manufactor (probably mostly Vicon and Qualisys) either directly in ezc3d or in opensim interface.

The second question self-answers (Since there is no standard way to store EMG, there is no pre-analyzed data, this is solely up the each manufactor)

For completeness, your query data structure (which interfaces ezc3d and BTK) can be used to properly parse the data, but again, it will have to be done for each manufactor (assuming they have a stardard way to store the emg data)

aymanhab commented 1 year ago

Following up on this issue: Loading .c3d files with EMG from Vicon and Qualysis and I see the data extracted into the c3d structure. How do we tell if this is EMG or anything else? Also if the "labels" in the header are different for different manufacturers, does it make sense to have a separate C3DParser for each manufacturer, or should we use EZC3D as abstracting this out and have one C3DFileReader that handles the differences internally? Thank you @pariterre

pariterre commented 1 year ago

Hi @aymanhab

How do we tell if this is EMG or anything else?

You can't. There is no out-of-the-box EMG data structure. It only "analog" data mashed together. The only way to differentiate them is by the labels IF the manufacturor add something to the labels (for instance a prefix EMG:emgX). I did not have a look myself neither at a Vicon nor Qualisys c3d, so I don't know if they do. My worries is that they don't.

Also if the "labels" in the header are different for different manufacturers, does it make sense to have a separate C3DParser for each manufacturer, or should we use EZC3D as abstracting this out and have one C3DFileReader that handles the differences internally?

If you have examples of C3D from Vicon and from Qualisys which are representative of the way they mark EMG data (assuming they do), I could add an EMG module to ezc3d allowing you to simply request the values as already parsed in the module. That would therefore benefit both Opensim and ezc3d users :)

But again, if I recall well, Vicon at least does not mark emg data in a standardize way. If I am correct, then I would suggest that you guys write a parser for the analog data based on some Opensim naming standardization that your users should follow.

Hope this answers your questions!

aymanhab commented 1 year ago

Hello, I got notified that the issue was closed as completed, can you point to a pr or release notes describing how to use so we can decide whether we should upgrade on our side or how the interfaces may change? Thank you

pariterre commented 1 year ago

Hi @aymanhab As this issue was staled, I closed it, but I did not do anything particular related to this issue. As stated in my previous comment, there is no out-of-the-box solution for this. Hence, I would to create specific solution for each of the platforms (Vicon, Qualisys, others?) and versions (Nexus.1.x, Nexus 2.x, etc.) you plan to support on OpenSim, meaning I need a c3d example file for each of them. If OpenSim can provide a test suite, I could make this work!

nicos1993 commented 1 year ago

Hello @pariterre and @aymanhab !

I am in the midst of exporting C3D data from Cortex Motion Analysis. The built in utility for converting C3D to .trc and .mot files works great - but I would also like to get access to my EMG analogue data so I can then process it is myself... I have attached a few C3D files which should contain markers, forces and EMGs... please let me know if I can be of assistance or if there is a quick way to already access this data.

Best wishes,

Nicos Haralabidis
c3d_share.zip

pariterre commented 1 year ago

Dear @nicos1993 Could you specify if you are passing through the OpenSim flow, or you are calling ezc3d directly? If it the latter, are you using it from Matlab, Python or C++?

The short answer for your question is it can be done by hand if you are not using the OpenSim flow.

The long answer is unfortunately, in a C3D, there is no canonical way to target the EMG data in the file. C3D was not created with that in mind. When they expanded it to inlcude EMG data, they did not try to separate the data by types, nor that they standardized the labels (as it is the case for POINTS for instance). Thus, you have to find the columns of EMG, based on the ANALOGS:LABEL (the naming will depend on the implementation the company made) and then you can simply slice these data from the analog data

nicos1993 commented 1 year ago

@pariterre thanks for the reply!

Currently I am passing it through the OpenSim flow (specifically C3Dexport.m), but if for what I want to do I need to run it through ezc3d that isn't a problem - in which case preferably Matlab or Python but via C++ is fine.

Based on your response it appears as though I should use the ezc3d workflow directly... I will try and spend some time extracting the data in that fashion to begin with I guess... but I'm glad to hear it is possible to extract the EMGs - albeit perhaps not in a nice neat fashion!

Thanks!

pariterre commented 1 year ago

The greatest problem you may face is to install ezc3d on matlab. Currently (and I don't see it in an upcoming future), there is no previously built binaries for Matlab. This means you have to compile it by yourself (there is one for Python though). While it is not complicated to compile per se, if you've never compiled C++ code before, it may be a bit of a ramp. I've made a video on youtube helping a bit for the Windows user (you can find it by typing pariterre in youtube, if needed)

aymanhab commented 1 year ago

Considering this is likely useful for other users, can we expand the OpenSim interface to provide other or analog data to mirror ezc3d and let the clients do the interpretation? Then users can get both python and matlab and a uniform interface through one workflow. Just thinking aloud!

pariterre commented 1 year ago

@aymanhab I think it is an excellent idea, and would probably not be too hard to implement (it would simply be a structure that mirrors force platforms, but does not perform any interpretation of the data, with a table header). Having this implemented in the GUI may be a bit harder though

nicos1993 commented 1 year ago

Hey @aymanhab, let me know if you wanna chat this through. I am happy to get involved with it!

aymanhab commented 1 year ago

@nicos1993 Definitely will stop by before or after the lab meeting. Thanks for volunteering šŸ„‡

pariterre commented 1 year ago

If at some point you need that we discuss by voices, I am available as well :)

aymanhab commented 1 year ago

Thanks for all your help @pariterre šŸ‘

@nicos1993 and I had a chat and we'd like to suggest beefing up the C3DFileAdapter interface to account for Analog data by adding a method along the lines of std::shared_ptr<TimeSeriesTable> getAnalogDataTable(DataAdapter::OutputTables& tables)

so in essence the method C3DFileAdapter::extendRead() here https://github.com/opensim-org/opensim-core/blob/d427d777e2f5591e20bebc9d15f20e3abe6ce47b/OpenSim/Common/C3DFileAdapter.cpp#L68 will also populate a TimeSeriesTable with Analog data and clients will have to handle it on their own (could be EMG or anything else depending on convention/user-knowledge.

Follow up questions:

  1. Does that sound doable?
  2. Could there be more analog data? what types? Maybe getAnalogScalarData and getAnalogVec3Data?
  3. Is it correct to assume there're labels associated with these Analog data, that will be embedded in the table(s)?
  4. I understand different manufacturers use different schemes, can they all be addressed using this proposed mechanism?

I would love to have an in-person/zoom discussion with you and @nicos1993 to plan the next steps once we have a viable proposal or if you have other mechanisms in mind that you want to discuss. Thanks again for all your help.

pariterre commented 1 year ago

Hi @aymanhab As I said, I am available for a zoom discussion (as in-person, from Quebec, is a bit hard haha!). You guys can write me at pariterre@hotmail.com to setup something when you are ready!

Quick answers to your questions:

  1. It is exactly how I see it, i.e. from the stand point of ezc3d, all the (untreated) analogs are fairly easily accessible in a manner highly similar to POINTS (the skin marker data), which we already sorted.
  2. There are basically as many analog data that there exist. If I had to give a definition of "Analog" in the C3D file standard, it is every piece of data which is not POINT. Unfortunately, in a c3d file, these data are all mashed up in a single large matrix, instead of being neatly separated by matrices of different data type. getAnalogVec3Data sounds right to me.
  3. This is not correct to assume that. While it is supposed to be the case (and for most of the implementation I've seen it is the case), there is nothing enforcing it. For instance, it may be the case that ANALOG:LABELS is empty even though analog data are present. That said, we can give dummy names to the data.
  4. It is not different schemes per se, since c3d is standardized. But it is only standardized in the format, not individual elements in the format. For instance, you could find a Nexus' file that prefixes EMG: the labels of the emg if they used the autoconnect emg, but nothing otherwise, or another software which would add a suffix, or another doing some other stuff (or nothing). Obviously when I say software, I also mean that different versions of the same software can also label the analog different. The main take away here is that LABELS are fundamentally unreliable while being at the same time the only information we have regarding the nature of the data in the matrix of analog data.

See you!

nicos1993 commented 1 year ago

How about we schedule a Zoom call for next week? What are your availabilities? @aymanhab @pariterre

Thanks!

pariterre commented 1 year ago

Sounds good I am from QuƩbec (East US and Canada Time zone), I have a pretty weird week next week... If you are available on Thursday morning (here), that would be perfect for me. Otherwise, I can try to find some time

nicos1993 commented 1 year ago

How about 11.30am QuƩbec time? Does 8.30am Pacific time work for you both? @aymanhab @pariterre

Nicos

aymanhab commented 1 year ago

Can we do any other day before Thursday? I'm out Thu-Fri

pariterre commented 1 year ago

I am doing a 24h hours of work from Tuesday to Wednesday... So I'll be quite exhausted haha! Can this be the week after then? I am available on Monday, Wednesday and Thursday the next week

nicos1993 commented 1 year ago

Monday 12th March - what time works for you both? @aymanhab @pariterre

Thanks,

Nicos

aymanhab commented 1 year ago

I hate to stand in the way of progress when high level plans are mostly in place, I'd suggest you find the earliest time that fits you both @pariterre and @nicos1993, let me know the date/time and I'll try to join even if I'm off. If I can't I will follow up on github if there're questions that need my input so we don't delay. Thoughts?

pariterre commented 1 year ago

Hi @nicos1993 !

I assume you meant Monday 13th? If so, I now have a meeting at 9 to 10, so from that point on. I am partially available on Tuesday and the full on Wednesday :)

nicos1993 commented 1 year ago

@pariterre Hey! Does this coming Thursday still work for you? (March 9th) 11.30am QuƩbec time? (8.30 PDT)

pariterre commented 1 year ago

Do you mean on 9th or 16th? I am available on both ;)

nicos1993 commented 1 year ago

The 9th! We can try and tackle the problem to begin with and then update @aymanhab

Thanks,

Nicos

pariterre commented 1 year ago

This is good, see you on the 9th!

nicos1993 commented 1 year ago

Great! I just sent you a Zoom link via your hotmail account!

nicos1993 commented 1 year ago

@pariterre Hey! I am very sorry but I won't be able to make our meeting today as something has cropped up teaching related - is there any chance we can do the same time tomorrow? I apologize for the short notice!

pariterre commented 1 year ago

Hi @nicos1993 I would be available during both morning and afternoon tomorrow. However, I am live on internet on the morning (I am streaming working session on Twitch), so I can be available for slices of 25 minutes (then I have 5 minutes of animation, then I am available again). Afternoon is all good though

nicos1993 commented 1 year ago

@pariterre Would 11am work for you? (8am for me) I think a 25 mins discussion should be fine...! I have a testing session at 9am tomorrow until 2pm unfortunately...

Thanks, Nicos

pariterre commented 1 year ago

Yep

pariterre commented 1 year ago

Sorry! I complete missed! I am connected to the zoom link you sent me, if you still have time!

nicos1993 commented 1 year ago

Hello @pariterre ! Sorry for getting back in touch now. The student I will be working with will likely need the .c3d/EMG feature in the next couple of months - so I should probably help in figuring this out!

Can you please let me know when might work for you to schedule a call?

Best wishes,

Nicos

pariterre commented 1 year ago

I am currently fairly available, so it is probably up to you :) (keeping in mind, I am on Eastern time zone!)

nicos1993 commented 1 year ago

Okay, great! Would this Friday (19th) at 11am Eastern Time work for you?

Thanks,

Nicos

pariterre commented 1 year ago

I can be available, if it could be 1PM (or even 12), that would be slightly better, but I still can manage to have a meeting at 11AM (I will have to leave for 5 minutes from 11:25 to 11:30 though)

nicos1993 commented 1 year ago

Sure! 12 Eastern Time works fine! :) I will send across a Zoom link!

@aymanhab in case you would like to join too - please see above! :)

Thanks,

Nicos

aymanhab commented 1 year ago

Not sure this issue is closed but anyway, I'm exploring a fix/solution per discussion with @nicos1993 @pariterre I started implementing a getAnalogDataTable() method to parallel the getMarkersTable and getForcesTab;e but got stuck trying to find the calls equivalent to c3d.parameters().group("POINT") is that documented anywhere? also for labels c3d.parameters().group("POINT").parameter("LABELS") can you point me in the right direction? Thank you

pariterre commented 1 year ago

Hi @aymanhab If you refer to the analog sid of POINT, it would be c3d.parameters().group("ANALOG"). So for the labels, it would be: c3d.parameters().group("ANALOG").parameter("LABELS")