From c36ddf1609ab62974fecb32a4ec825201a42ace3 Mon Sep 17 00:00:00 2001 From: Skudalen Date: Fri, 2 Jul 2021 10:13:27 +0200 Subject: [PATCH] chore: move mfcc func and add skeleton for saving mfcc data --- Handle_emg_data.py | 111 +++++++++++++++++---- Present_data.py | 37 ++++--- Signal_prep.py | 9 -- __pycache__/Handle_emg_data.cpython-38.pyc | Bin 23439 -> 24488 bytes __pycache__/Signal_prep.cpython-38.pyc | Bin 2194 -> 1789 bytes 5 files changed, 109 insertions(+), 48 deletions(-) diff --git a/Handle_emg_data.py b/Handle_emg_data.py index cdffe6c..39460f9 100644 --- a/Handle_emg_data.py +++ b/Handle_emg_data.py @@ -5,8 +5,18 @@ from pathlib import Path import numpy as np from pandas.core.frame import DataFrame from math import floor +import sys +sys.path.insert(0, '/Users/Markus/Prosjekter git/Slovakia 2021/python_speech_features/python_speech_features') +from python_speech_features.python_speech_features import * +import json #from Present_data import get_data +# Global variables for MFCC +MFCC_STEPSIZE = 0.5 # Seconds +MFCC_WINDOWSIZE = 2 # Seconds +NR_COEFFICIENTS = 13 # Number of coefficients +NR_MEL_BINS = 40 # Number of mel-filter-bins + class Data_container: def __init__(self, subject_nr:int, subject_name:str): @@ -488,6 +498,12 @@ class CSV_handler: class DL_data_handler: + JSON_PATH = "mfcc_data.json" + SAMPLE_RATE = None + TRACK_DURATION = None # measured in seconds + #SAMPLES_PER_TRACK = SAMPLE_RATE * TRACK_DURATION + + def __init__(self, csv_handler:CSV_handler) -> None: self.csv_handler = csv_handler # Should med 4 sessions * split nr of samples per person. Each sample is structured like [sample_df, samplerate] @@ -497,6 +513,7 @@ class DL_data_handler: 4: [], 5: [] } + def get_samples_dict(self): return self.samples_per_subject @@ -568,30 +585,71 @@ class DL_data_handler: main_df = pd.concat([main_df, adding_df], ignore_index=True) samplerate = get_samplerate(main_df) return main_df, samplerate + ''' + def save_mfcc(raw_data_dict, json_path, samples_per_subject): + + # dictionary to store mapping, labels, and MFCCs + data = { + "mapping": [], + "labels": [], + "mfcc": [] + } + + #hop_length = MFCC_STEPSIZE * sample_rate + #num_mfcc_vectors_per_segment = math.ceil(samples_per_subject / hop_length) + + # loop through all subjects to get samples + for key, value in raw_data_dict.items(): -# HELP FUNCTIONS: ------------------------------------------------------------------------: + # save genre label (i.e., sub-folder name) in the mapping + subject_label = 'Subject ' + key + data["mapping"].append(subject_label) + print("\nProcessing: {}".format(subject_label)) -# Help: gets the str from emg nr -def get_emg_str(emg_nr): - return 'emg' + str(emg_nr) + # process all audio files in genre sub-dir + for sample in value: -# Help: gets the min/max of a df -def get_min_max_timestamp(df:DataFrame): - #min = int(np.floor(df['timestamp'].min())) - min = df['timestamp'].min() - max = df['timestamp'].max() - return min, max + # load audio file + signal, sample_rate = sample[0], sample[1] -# Help: returns df_time_emg -def make_df_from_xandy(x, y, emg_nr): - dict = {'timestamp': x, get_emg_str(emg_nr): y} - df = DataFrame(dict) - #print(df) - return df + # extract mfcc + mfcc = mfcc_custom(signal, sample_rate, MFCC_WINDOWSIZE, MFCC_STEPSIZE, NR_COEFFICIENTS, NR_MEL_BINS) + mfcc = mfcc.T + print(len(mfcc)) -# Help: returns the samplerate of a df -def get_samplerate(df:DataFrame): + # store only mfcc feature with expected number of vectors + #if len(mfcc) == num_mfcc_vectors_per_segment: + data["mfcc"].append(mfcc.tolist()) + data["labels"].append(key) + print("sample:{}".format(value.index(sample))) + + # save MFCCs to json file + with open(json_path, "w") as fp: + json.dump(data, fp, indent=4) + ''' + # HELP FUNCTIONS: ------------------------------------------------------------------------: + + # Help: gets the str from emg nr + def get_emg_str(emg_nr): + return 'emg' + str(emg_nr) + + # Help: gets the min/max of a df + def get_min_max_timestamp(df:DataFrame): + #min = int(np.floor(df['timestamp'].min())) + min = df['timestamp'].min() + max = df['timestamp'].max() + return min, max + + # Help: returns df_time_emg + def make_df_from_xandy(x, y, emg_nr): + dict = {'timestamp': x, get_emg_str(emg_nr): y} + df = DataFrame(dict) + #print(df) + return df + + # Help: returns the samplerate of a df + def get_samplerate(df:DataFrame): min, max = get_min_max_timestamp(df) if max > 60: seconds = max - 60 - min @@ -599,4 +657,19 @@ def get_samplerate(df:DataFrame): seconds = max - min samples = len(df.index) samplerate = samples / seconds - return int(samplerate) \ No newline at end of file + return int(samplerate) + + # Takes in a df and outputs np arrays for x and y values + def get_xory_from_df(x_or_y, df:DataFrame): + swither = { + 'x': df.iloc[:,0].to_numpy(), + 'y': df.iloc[:,1].to_numpy() + } + return swither.get(x_or_y, 0) + + # Slightly modified mfcc with inputs like below. + # Returns N (x_values from original df) and mfcc_y_values + def mfcc_custom(df:DataFrame, samplesize, windowsize, stepsize, nr_coefficients, nr_mel_filters): + N = get_xory_from_df('x', df) + y = get_xory_from_df('y', df) + return N, base.mfcc(y, samplesize, windowsize, stepsize, nr_coefficients, nr_mel_filters) \ No newline at end of file diff --git a/Present_data.py b/Present_data.py index 8d1b72b..d6976f7 100644 --- a/Present_data.py +++ b/Present_data.py @@ -8,10 +8,10 @@ from matplotlib import cm import matplotlib.ticker as ticker # Global variables for MFCC -mfcc_stepsize = 0.5 # Seconds -mfcc_windowsize = 2 # Seconds -nr_coefficients = 13 # Number of coefficients -nr_mel_filters = 40 # Number of mel-filter-bins +MFCC_STEPSIZE = 0.5 # Seconds +MFCC_WINDOWSIZE = 2 # Seconds +NR_COEFFICIENTS = 13 # Number of coefficients +NR_MEL_BINS = 40 # Number of mel-filter-bins # PLOT FUNCTIONS --------------------------------------------------------------: @@ -126,13 +126,6 @@ def denoice_dataset(handler:Handler.CSV_handler, subject_nr, which_arm, round, e df_new = Handler.make_df_from_xandy(N, y_values, emg_nr) return df_new -# Slightly modified mfcc with inputs like below. -# Returns N (x_values from original df) and mfcc_y_values -def mfcc_custom(df:DataFrame, samplesize, windowsize, stepsize, nr_coefficients, nr_mel_filters): - N = get_xory_from_df('x', df) - y = get_xory_from_df('y', df) - return N, base.mfcc(y, samplesize, windowsize, stepsize, nr_coefficients, nr_mel_filters) - def test_for_NaN(dict, samples_per_person): for key, value in dict.items(): @@ -201,14 +194,14 @@ def mfcc_all_emg_plots(csv_handler:CSV_handler): df6, samplerate6 = csv_handler.get_data( 1, 'left', 1, 6) df7, samplerate7 = csv_handler.get_data( 1, 'left', 1, 7) df8, samplerate8 = csv_handler.get_data( 1, 'left', 1, 8) - N1, mfcc_feat1 = mfcc_custom(df1, samplerate1, mfcc_windowsize, mfcc_stepsize) - N2, mfcc_feat2 = mfcc_custom(df2, samplerate2, mfcc_windowsize, mfcc_stepsize) - N3, mfcc_feat3 = mfcc_custom(df3, samplerate3, mfcc_windowsize, mfcc_stepsize) - N4, mfcc_feat4 = mfcc_custom(df4, samplerate4, mfcc_windowsize, mfcc_stepsize) - N5, mfcc_feat5 = mfcc_custom(df5, samplerate5, mfcc_windowsize, mfcc_stepsize) - N6, mfcc_feat6 = mfcc_custom(df6, samplerate6, mfcc_windowsize, mfcc_stepsize) - N7, mfcc_feat7 = mfcc_custom(df7, samplerate7, mfcc_windowsize, mfcc_stepsize) - N8, mfcc_feat8 = mfcc_custom(df8, samplerate8, mfcc_windowsize, mfcc_stepsize) + N1, mfcc_feat1 = csv_handler.mfcc_custom(df1, samplerate1, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N2, mfcc_feat2 = csv_handler.mfcc_custom(df2, samplerate2, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N3, mfcc_feat3 = csv_handler.mfcc_custom(df3, samplerate3, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N4, mfcc_feat4 = csv_handler.mfcc_custom(df4, samplerate4, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N5, mfcc_feat5 = csv_handler.mfcc_custom(df5, samplerate5, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N6, mfcc_feat6 = csv_handler.mfcc_custom(df6, samplerate6, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N7, mfcc_feat7 = csv_handler.mfcc_custom(df7, samplerate7, MFCC_WINDOWSIZE, MFCC_STEPSIZE) + N8, mfcc_feat8 = csv_handler.mfcc_custom(df8, samplerate8, MFCC_WINDOWSIZE, MFCC_STEPSIZE) feat_list = [mfcc_feat1, mfcc_feat2, mfcc_feat3, mfcc_feat4, mfcc_feat5, mfcc_feat6, mfcc_feat7, mfcc_feat8] label_1 = 'Subject 1, session 1, left arm, emg nr. 1' label_2 = 'Subject 1, session 1, left arm, emg nr. 2' @@ -229,9 +222,13 @@ def main(): csv_handler = CSV_handler() csv_handler.load_data('soft') dl_data_handler = DL_data_handler(csv_handler) + mfcc_3_plots_1_1_2(csv_handler) + + ''' dl_data_handler.store_samples(10) dict = dl_data_handler.samples_per_subject - + dl_data_handler.save_mfcc() + ''' main() \ No newline at end of file diff --git a/Signal_prep.py b/Signal_prep.py index fae013b..b703076 100644 --- a/Signal_prep.py +++ b/Signal_prep.py @@ -4,17 +4,8 @@ from scipy.fft import fft, fftfreq import pywt import sys import Handle_emg_data as Handler -sys.path.insert(0, '/Users/Markus/Prosjekter git/Slovakia 2021/python_speech_features/python_speech_features') -from python_speech_features.python_speech_features import * -# Takes in a df and outputs np arrays for x and y values -def get_xory_from_df(x_or_y, df:DataFrame): - swither = { - 'x': df.iloc[:,0].to_numpy(), - 'y': df.iloc[:,1].to_numpy() - } - return swither.get(x_or_y, 0) # Normalizes a ndarray of a signal to the scale of int16(32767) def normalize_wave(y_values): diff --git a/__pycache__/Handle_emg_data.cpython-38.pyc b/__pycache__/Handle_emg_data.cpython-38.pyc index 65dd4c368145c83e902d1150d29179331e19f5ca..b6b174112239493175095e82bb6788b65331555d 100644 GIT binary patch delta 4044 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 delta 3138 zcmb_eO>7&-73M6--Q}++>L;?~sFYntrY);&l%#MZ$Bt_yj%CTTElU>Tc)ixFq_xN; zF}rdkI|WhHK#zejK~L_fK~f#k7FC1Xk{%Ku0fGQIW`Py~3Ir8`w3h-!Z++iTKeX(g zS`z%Y@6DU{Gw)~iHy^P#KVa=wGMQwAK11L9`NrliBay$#`qV!5&i$houDu-L1~>0T zxcPIfoFOW~lSCzn$`Y00X`<3Zwebwk-iwrTPKviZU_8g$@5RdPJkL9bo8RJg@-E_b z@aW}8vHSklCuD7^cjLD#&2*FOi-i1>W@Va>M6X9gha8HYXKA?>b&qsGq~l@e1ZfZ; z0AC|8HDSp6tWy?Z->_b3#82$YLuhxS+5=^M^0l~iU_U(-W1=5Iy#%3F=VenkHn-;e z+q9CGe~It!d3b+lRPANQ;tK;ZuRp~O%Hz88#8cZUin>rX4}vi$-`9^E+_OEu=mb{a zRvpW!E?R+ov*HX&%^1;}TQ)|GRhCL&dzxTVlOLJ~8%O9d)Lq|OsZ>J!wq03q{4i>J zYhk=DTrUvnH0rA3V#O7^qKjfvzxGK0e;9H!4w0RFN)X$xmr z{AdrW=8LDL-P6fN$=K)7#Fs;=!oMGdLB>!z@_z5BERuf z&);Gvl|Q>t4njg5c8ib13%e>c$~^R2rIN}Hw*!0n5B+zu4fria0dQB00iFh&0E_@$ z0GtGz0_^qMuPp?nk~odEGl0ihhg7Te-;<>ORz&Lk>l?p5_D1H~|LjN%0Y2Uhd3Us+ zrI}>?4>pdEz4iYvhIsyqvEsRcwxo>7<70XG?{mC_;HZ?4zA#FCBHF>&DqEFCRBUMY zj_bz4A$XHQpmUsbcdvcdhm4j;eoKe0o0V2#~$=#p)`}sL@t81p@ zg~{$NH70gq5|dCnDDO<}-?vA(iGcy#q5R$C;YK$Ep8)g#)M(rpKVlzvm=>x%TKYo8 zg^-$IstBh*mH^X$UAyW@4a+`SfKDIAOwH8g%v2ZaqcQtrV}@8xq|A7vtT|E6xb{Hf zQ68f?ALDVZ)AWx!IydM}FnNNw22b)7aZQp1H_bD|O*l!OC8~|*h)QurH$;2bcILba zn)=b>6-P`JwJ=3fd7BP(S-J4ie$@`DB~>?$kV~ouDO)r0iwk}I>X_kaOL#liz_-#U zT^UhnVd5%;1|F3Rb4Y-1SL+qWx41hW{FBKyOT%ndepveQq8d$bW7EK1<%{ZljBmOQ zlx>`EsN40r<8kp5$i=})K{KuaKiHYhru_P1|51$MkXfq2_jn$rw|7-sKlm5hC|(L! zPMK70B%r=5-@APH5;7M?D~=b&U0egobQoKr8&13mv1<^}h2z;(M;uTDblHV!JfZHVa=OJQU%Wl!XplilxBXzxGjXerX~s1;)1o{dal3ZaB&%S z_r#o`%P}RK?cx}ahh|@ltut{*-kSYILxrP)K8(ExOqItbrrLCoiADOSxwDJ~RTRj8 zr~ocvY3p4AwNR$F4%ef%4p9Y_usL_VMbMA&OZX)7jI3Wh-Jmn+CZc6vpre~pV~(D} z7>+{R08<_GZ9Jhkm6Dc#Rp_d7uF&cx<{T>A)N%}>%^49bEl8t9I5-GRo5f1SLuepk%+s1HvlbZ zk-wVj?K}frI;}+LI!iP0Q+kr$&5c@j=z&H9?5QA&@sO>CY)yF;#?ai4Tp}bJ=C|JY ztOZf4TB|gY)+TmrupFaT&!54@JLT$m7!ykX7l0n5Og`)}t}tpe?u!62kINw{;F7dn z?KYal&?%>{Kih{235~iPELGf_A_gk%tyJr4qAuUQUbxj9`zpssMl(|75&Tn{T25;x nr=dAkt?`wL^8&Kx7YJ~0=GHGA&9-G55qvLaXU$mFFpd8JGvSM4 diff --git a/__pycache__/Signal_prep.cpython-38.pyc b/__pycache__/Signal_prep.cpython-38.pyc index dc2cb9f3bf9f86ec25d4f028dbb4ed024d852592..53d2248c9f337ea142745b492409447933a76cec 100644 GIT binary patch delta 868 zcmZ8fJ8u&~5We;Kp7z;iL2Qx;k%T~UN)%B*7ZD<*phrmHg2VCdC6>;2*4#P7G*M9a z0byGZCE{oB6KE*5prOH{rJ&(4bGAt2wRUdCGxN>k+c%Bh-o|6kb2U8uPhX$^G&SuT z8JCZa#sj?BtMiSWhrG;>M4P5z$?X1<36#AXG_fmu}I!B^HMaw8O)?`q{Z@^TENAg!` ze|gNvwv>M$^6)r?Q5Zqm4|$}@83I66CVF6c7zDO_r!U^2A)ye2tDGpx!!*JKAI*%= zHRdvt+3YG_Xv**UYgUs>DmAyiltvQ#aIR1Z_B1XUp=4SW=iiP9K*HG&c z{hB7n#%ZgC3OFdnfAIuH(G4ufQ?uz8L*Jr++azwt59aF9%+Bk*KDVA?HBz0KF=hm!GYLut6%(4^0xZgJN*lB#qE6mA+uj-tMP-7>oP#4l_!% TSrrMh>1WtR&8a!IX?yC`p#2AHl8vW%IL8lw`Ehd9((cfT)Q9_q3z72CE12_PAhHW7z8Td(1ZPrY8MLrz6$lR%Ds6A-YVfhmU?-zh5d{u78v2?VWP{ zTV0e}YTGD%Wn*a8@8!jAx;;#td?we`u=Y!>6BoG3NeOfr?49j(e&sPVV#DmpH+ z0VhM|pTJ0Leu-9oj;>-`#GdzrOP2A=-VvLf*74^$DW38JUhyMdf&Uo?LcgyWZaAh6 z$k@JR)1I*YFfRr+GDR}p8BL~EY$($cabrIzbTZWq?2f~7Z)i4^{@v3{kpKGZfADZ` zxG_%iWTMq%4cB*YeX?KZX_Dz;l*num7nX=3&*NRz{k!4oWy}X;<@1ji3D6u1uVZ3> zyYedUh|j!L;OlxbcEBp~BkvEN{Q`i(^6|t5!?C$`(~42L56ICp*-i5uRq7@#aZu{6 z4bis8g&w8(@R3UP(p}Yst|aa~c9Ch4&e%k}P;)lj!oT zF1hiKf(lly(hlD=DuK12@~a>t--v!~QizLsp<}@MB}{RDtjP=;ew~hGuC#3vg=IRL zKu8gM6ePWd^OU|oJ6YfQ1jdH_#OQQf#)9Z3*PX)KL{uk51{Yk$LP>t`Sc^4S#JXsD z74m-|lcOBu?=krovSCL62`6yOM8zsT<3I5O0jL0O8YaXTftY5+w_2|u{VyS@S3$@j zemECYK_x0b6Cfj~$*@8`xS@%WV+w%Y!km`LbPp-{16-6V6AQU#bO-A;NVQIqVj#=9?1O^A6TaSN&Lvw^)aL z$n*!$oyl5q*GX*LfKbP);h+YPC8A)4>~vb@w4h+FMYL4H#RTu`H*vP->$fo2rrAXO z*er5c!xg2GSbGm^`hD6E4(=q`Fb6k-yKeF#j+L^2$4{E3dJ@ukepqK8Mzt`q^LH+_!4#JkV9&~v5jqCKR6kE)GzS_wd++U z9^S0{x?a73e{+d({0v_&a7^g8p^g0^9B&02>cRp^Lm2c`>Ijm4mv${qld;;HXT1$+ zjuSQni!^zWq2=Lt7xld^00@g~xTB=hr^IL@DX2wk5v{{Ct)58`VvU+hkV}}HN)Wt$ zt~zk()%i}D@Dw znfQ~JKWwP&FMIDCw7Q5Ld>z}Oh4rWSD{=nj&6+#xs$8g^bV912Sj_v(eK>g Px1%U%H`~pq5iR`#SM~3x