1
+ % Code for converting MUEdit exports to OTB style exports for use in
2
+ % openHDEMG and analyze HDEMG decompositions.
3
+ %
4
+ % In openHDEMG open the new file as an OTB format and ensure
5
+ % you select the same extension factor that was chosen in MUEdit as this
6
+ % will not be hardcoded anywhere.
7
+
8
+ % Note that this is designed for a very specific export. you need to change
9
+ % the descriptions dection to match your export or the torque data to pull
10
+ % your correct aux channel info. Further, this only imports the first
11
+ % channel of aux data as 'aquired force' if you wish to use some other
12
+ % channel as the reference signal then that must be changed to select the
13
+ % proper column of data from signal.auxillary on line 83
14
+
15
+ % Last edit April 4 2024
16
+
17
+ % Copyright Ryan C. A. Foley 2024
18
+ % Github: rcafoley
19
+ % Twitter: foleyneuromech
20
+ % LinkedIn: ryancafoley
21
+
22
+ %% Clear all variables from the workspace, clear the Command Window, and print the current directory
23
+ clear all ; % Clears all variables from the workspace
24
+ clc ; % Clears the Command Window
25
+ disp([' Current working directory: ' , pwd ]); % Prints the current directory
26
+
27
+ % Variables to change to accomodate different experimental setups
28
+ % TypeofArray(s)
29
+ emgArray = ' GR04MM1305'
30
+ % muscle
31
+ muscle = ' Tibialis Anterior'
32
+ % MVC value & offset
33
+ mvc = 0.466 % in mV
34
+ offset = 0.13 % inmV
35
+
36
+ % List all .mat files in the current directory
37
+ files = dir(' *.mat' );
38
+
39
+ for i = 1 : length(files )
40
+ originalFileName = files(i ).name;
41
+ fprintf(' Processing %s\n ' , originalFileName );
42
+
43
+ % Load the .mat file
44
+ loadedData = load(originalFileName );
45
+ % Assume 'edition' and 'signal' are variables within 'loadedData'
46
+ % Adjust the following lines if they're nested within another structure
47
+ edition = loadedData .edition ; % Or the appropriate field if nested differently
48
+ signal = loadedData .signal ; % Adjust based on your data structure
49
+
50
+
51
+ %% Data Conversion here
52
+
53
+ % define the number of units from the decomp - this may need to the "clean"
54
+ % versions if a unit is deleted
55
+
56
+ numRawChannels = size(signal .data , 1 );
57
+ numMUs = size(edition .Dischargetimes , 2 );
58
+
59
+ % define the total channels to be created Array size (monopolar + 1
60
+ % force + number of MUs x2 decomp and source)
61
+
62
+ totalChannels = numRawChannels + 1 + numMUs + numMUs ;
63
+
64
+ % FileType
65
+ OTBFile = ' unknown' ;
66
+ % SamplingRate
67
+ SamplingFrequency = signal .fsamp ;
68
+
69
+ % Time Vector
70
+ % Initialize the cell array
71
+ Time = cell(1 ,1 ); % Creates a 1x1 cell array
72
+ % Insert the array 'edition.time' into the {1,1} location of the cell array 'Time'
73
+ Time{1 ,1 } = edition .time ' ;
74
+
75
+ % Convert the HDEMG Array Data Arrays
76
+ % first 64 are the raw data from the grid
77
+ Data = cell(1 ,1 ); % Creates a 1x1 cell array
78
+ Data{1 ,1 } = signal .data ' ;
79
+
80
+ % Extract the specific torque data from 'signal.auxillary' - ours is in
81
+ % row 1 but you may have more or want a different row for other aux or
82
+ % the target path
83
+ torqueColumnData = signal .auxiliary(10 , : ).' ; % Transpose it to match the orientation (rows, not columns)
84
+
85
+ % Check to ensure dimensions match for concatenation
86
+ if size(Data{1 ,1 }, 1 ) == length(torqueColumnData )
87
+ % Add the new column to the array in 'Data{1,1}'
88
+ Data{1 ,1 } = [Data{1 ,1 }, torqueColumnData ];
89
+ else
90
+ disp(' Error: Dimension mismatch. Cannot concatenate torque to the new column.' );
91
+ end
92
+
93
+ % Extract the decomposition aka discharge times
94
+ maxSamples = length(edition .time );
95
+
96
+ % Initialize the array to hold all converted data. Rows represent samples, columns represent scenarios.
97
+ allDecompositionConvertedData = zeros(maxSamples , numMUs );
98
+
99
+ % Loop through each scenario and fill in the allConvertedData array
100
+ for i = 1 : numMUs
101
+ % Extract the event data for the current scenario
102
+ spikeData = edition.Dischargetimes{1 , i };
103
+
104
+ % Create a temporary array of zeros for the current scenario
105
+ otbSpikeData = zeros(maxSamples , 1 );
106
+
107
+ % Mark the events in the tempData
108
+ otbSpikeData(spikeData ) = 1 ;
109
+
110
+ % Assign the converted data to the corresponding column in allConvertedData
111
+ allDecompositionConvertedData(: , i ) = otbSpikeData ;
112
+ end
113
+
114
+ % Check to ensure dimensions match for concatenation
115
+ if size(Data{1 ,1 }, 1 ) == length(allDecompositionConvertedData )
116
+ % Add the new column to the array in 'Data{1,1}'
117
+ Data{1 ,1 } = [Data{1 ,1 }, allDecompositionConvertedData ];
118
+ else
119
+ disp(' Error: Dimension mismatch. Cannot concatenate spike decomposition to the new column.' );
120
+ end
121
+
122
+ % Extract the source data for the decomposition aka individual optimised
123
+ % EMG channels for that MU **** NOTE: this may have to be changed to
124
+ % PulseTrainCLEAN as that may be the edited data - same for decomp
125
+ sourceEMGColumnData = edition.Pulsetrain{1 ,1 }.' ; % Transpose it to match the orientation (rows, not columns)
126
+
127
+ % Check to ensure dimensions match for concatenation
128
+ if size(Data{1 ,1 }, 1 ) == length(sourceEMGColumnData )
129
+ % Add the new column to the array in 'Data{1,1}'
130
+ Data{1 ,1 } = [Data{1 ,1 }, sourceEMGColumnData ];
131
+ else
132
+ disp(' Error: Dimension mismatch. Cannot concatenate sourceEMG to the new column.' );
133
+ end
134
+
135
+ % Reconstruct the needed Descriptions for each channel
136
+ % these cannot be extracted from the MUedit export easily so we will
137
+ % reconstruct them. If your setup is different you will need to adjust
138
+ % these to have the coorect info. this can be found
139
+
140
+ % Initialize the Descriptions cell array
141
+ Description = cell(totalChannels , 1 ); % Adjust the size based on the total number of descriptions
142
+
143
+ % Fill the array with descriptions using dynamic components
144
+ for i = 1 : numRawChannels % For the repetitive part with numbers 1 to 64
145
+ Description{i } = sprintf(' %s - MULTIPLE IN 1 (Channel 1->64) - %s (%d )[mV]' , muscle , emgArray , i );
146
+ end
147
+
148
+ % Add unique descriptions after the loop with dynamic MVC and Offset values
149
+ Description{numRawChannels + 1 } = sprintf(' acquired data MVC=%.3f Offset=%.2f [mV]' , mvc , offset );
150
+
151
+ % For Decomposition and Source for decomposition descriptions using dynamic components
152
+ for i = 1 : numMUs
153
+ Description{(numRawChannels + 1 ) + i } = sprintf(' Decomposition of %s - MULTIPLE IN 1 (Channel 1->64) - %s (%d )[a.u]' , muscle , emgArray , i );
154
+ Description{(numRawChannels + 1 + numMUs ) + i } = sprintf(' Source for decomposition of %s - MULTIPLE IN 1 (Channel 1->64) - %s (%d )[a.u]' , muscle , emgArray , i );
155
+ end
156
+
157
+ % Display the Descriptions cell array to verify
158
+ % disp(Description);
159
+
160
+ %% Output here
161
+ %% Output here
162
+ % Save the original data with a new name (_MUEditExport appended)
163
+ originalFileNewName = [originalFileName(1 : end - 4 ), ' _MUEditExport.mat' ];
164
+ save(originalFileNewName , ' -struct' , ' loadedData' );
165
+
166
+ % Save the variables into the new MAT file
167
+ newFileName = [originalFileName(1 : end - 4 ), ' _ConvertedToOTBExportFormat.mat' ];
168
+ save(newFileName , ' Time' , ' Data' , ' Description' , ' SamplingFrequency' , ' OTBFile' );
169
+ end
0 commit comments