%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%22marimo%3E%3D0.23.9%22%2C%20%22numpy%3E%3D2.0%22%2C%20%22matplotlib%3E%3D3.9%22%2C%20%22pandas%3E%3D2.2%22%2C%20%22scipy%3E%3D1.14%22%5D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Read%20Delsys%20data%20exported%20by%20Cortex%0A%0A%20%20%20%20%3E%20Marcos%20Duarte%2C%20Renato%20Naville%20Watanabe%3Cbr%3E%0A%20%20%20%20%3E%20%5BLaboratory%20of%20Biomechanics%20and%20Motor%20Control%5D(https%3A%2F%2Fbmclab.pesquisa.ufabc.edu.br%2F)%3Cbr%3E%0A%20%20%20%20%3E%20Federal%20University%20of%20ABC%2C%20Brazil%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20How%20to%20use%20this%20guide%0A%0A%20%20%20%20An%20EMG%20sensor%20and%20its%20inertial%20channels%20have%20different%20sampling%20rates.%20How%20do%20we%20load%20both%20without%20confusing%20their%20time%20axes%20or%20units%3F%0A%0A%20%20%20%20We%20will%20read%20a%20Cortex%20asynchronous%20export%2C%20inspect%20each%20sample%20rate%2C%20and%20compare%20raw%20EMG%20with%20a%20low-rate%20amplitude%20envelope.%0A%0A%20%20%20%20Predict%20the%20result%20before%20running%20each%20example.%20Try%20the%20**Exercises**%20before%20opening%20their%20worked%20solutions.%20Finish%20with%20the%20**Checkpoint%20questions**%20and%20**Problems**%3B%20the%20latter%20provide%20final%20answers%20only.%0A%0A%20%20%20%20Open%20this%20file%20locally%20in%20marimo%2C%20on%20%5Bmarimo.app%5D(https%3A%2F%2Fmarimo.app%2F)%2C%20or%20in%20%5Bmolab%5D(https%3A%2F%2Fmolab.marimo.io%2Fnotebooks).%20The%20unavailable%20private%20recording%20is%20replaced%20by%20a%20clearly%20labelled%20synthetic%20format%20example.%20You%20can%20optionally%20upload%20a%20compatible%20Cortex%20export%3B%20uploaded%20recordings%20are%20never%20sent%20to%20a%20third-party%20service%20by%20this%20notebook.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Python%20setup%0A%0A%20%20%20%20The%20parser%20and%20envelope%20helpers%20are%20included%20here.%20NumPy%2C%20pandas%2C%20SciPy%2C%20and%20Matplotlib%20supply%20their%20dependencies.%20No%20device%20SDK%20or%20native%20binary%20is%20needed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20import%20tempfile%0A%0A%20%20%20%20return%20Path%2C%20mo%2C%20np%2C%20plt%2C%20tempfile%0A%0A%0A%40app.function%0Adef%20delsys_demo_text()%3A%0A%20%20%20%20%22%22%22Return%20one%20second%20of%20synthetic%20EMG%20(1%20kHz)%20and%20IMU%20channels%20(100%20Hz).%22%22%22%0A%20%20%20%20import%20math%0A%20%20%20%20lines%20%3D%20%5B'Cortex%20generated%20Asynchronous%20device%20data%20file%20(.add)'%2C%20'%5BDevices%5D'%2C%20''%2C%20'Count%3D1'%2C%20'0%2C%20Demonstration%20sensor'%2C%20''%2C%20'%5BChannels%5D'%2C%20'Count%3D10'%2C%20'0%2C0%2C0%2CEMG'%2C%20'0%2C0%2C0%2CAccX'%2C%20'0%2C0%2C0%2CAccY'%2C%20'0%2C0%2C0%2CAccZ'%2C%20'0%2C0%2C0%2CGyroX'%2C%20'0%2C0%2C0%2CGyroY'%2C%20'0%2C0%2C0%2CGyroZ'%2C%20'0%2C0%2C0%2CMagX'%2C%20'0%2C0%2C0%2CMagY'%2C%20'0%2C0%2C0%2CMagZ'%2C%20''%2C%20'Demonstration%20sensor'%2C%20'EMG'%2C%20'Sample%2CSubFrame%2CCortexTime%2CEMG'%5D%0A%20%20%20%20for%20sample%20in%20range(1000)%3A%0A%20%20%20%20%20%20%20%20t%20%3D%20sample%20%2F%201000%0A%20%20%20%20%20%20%20%20emg%20%3D%200.001%20*%20math.sin(2%20*%20math.pi%20*%2080%20*%20t)%20*%20(0.3%20%2B%200.7%20*%20math.sin(math.pi%20*%20t)%20**%202)%0A%20%20%20%20%20%20%20%20lines.append(f'%7Bsample%7D%2C0%2C%7B150%20*%20t%3A.8f%7D%2C%7Bemg%3A.10f%7D')%0A%20%20%20%20lines.extend(%5B''%2C%20'Sample%2CSubFrame%2CCortexTime%2CAccX%2CAccY%2CAccZ%2CGyroX%2CGyroY%2CGyroZ%2CMagX%2CMagY%2CMagZ'%5D)%0A%20%20%20%20for%20sample%20in%20range(100)%3A%0A%20%20%20%20%20%20%20%20t%20%3D%20sample%20%2F%20100%0A%20%20%20%20%20%20%20%20channels%20%3D%20%5Bmath.sin(2%20*%20math.pi%20*%20t)%2C%200%2C%201%2C%200%2C%200%2C%200%2C%201%2C%200%2C%200%5D%0A%20%20%20%20%20%20%20%20lines.append(f'%7Bsample%7D%2C0%2C%7B150%20*%20t%3A.8f%7D%2C'%20%2B%20'%2C'.join(map(str%2C%20channels)))%0A%20%20%20%20lines.extend(%5B''%2C%20'End'%5D)%0A%20%20%20%20return%20'%5Cn'.join(lines)%20%2B%20'%5Cn'%0A%0A%0A%40app.function%0Adef%20moving_rms(x%2C%20window)%3A%0A%20%20%20%20%22%22%22Moving%20RMS%20of%20'x'%20with%20window%20size%20'window'.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20from%20scipy.signal%20import%20convolve%0A%20%20%20%20window%20%3D%20int(np.round(window))%0A%20%20%20%20y%20%3D%20np.sqrt(convolve(x%20*%20x%2C%20np.ones(window)%20%2F%20window%2C%20'same'%2C%20method%3D'direct'))%0A%20%20%20%20return%20y%0A%0A%0A%40app.function%0Adef%20linear_envelope(x%2C%20freq%3D1000%2C%20fc_bp%3D%5B10%2C%20400%5D%2C%20fc_lp%3D8%2C%20method%3D'rect')%3A%0A%20%20%20%20%22%22%22Calculate%20the%20linear%20envelope%20of%20a%20signal.%0A%0A%20%20%20%20Parameters%0A%20%20%20%20----------%0A%20%20%20%20x%20%3A%201D%20array_like%0A%20%20%20%20%20%20%20%20Raw%20signal%0A%20%20%20%20freq%20%3A%20number%2C%20optional.%20Default%20%3D%201000%0A%20%20%20%20%20%20%20%20Sampling%20frequency.%0A%20%20%20%20fc_bp%20%3A%20list%20of%20floats%20%5Bfc_h%2C%20fc_l%5D%20or%20None%2C%20optional.%20Default%20%3D%20%5B10%2C%20400%5D%0A%20%20%20%20%20%20%20%20Cutoff%20frequencies%20for%20the%20band-pass%20filter%20(in%20Hz).%20Enter%20None%20to%0A%20%20%20%20%20%20%20%20not%20use%20this%20option.%0A%20%20%20%20fc_lp%20%3A%20number%2C%20optional.%20Default%20%3D%208%0A%20%20%20%20%20%20%20%20For%20rectification%3A%20per-pass%20Butterworth%20low-pass%20cutoff%20(Hz).%0A%20%20%20%20%20%20%20%20For%20RMS%3A%20inverse%20window-duration%20parameter%20(Hz)%2C%20not%20a%20half-power%20cutoff.%0A%20%20%20%20method%3A%20string%2C%20optional.%20Default%20%3D%20'rect'%0A%20%20%20%20%20%20%20%20Method%20to%20calculate%20the%20linear%20envelope.%20Enter%20'rect'%20for%20using%20a%0A%20%20%20%20%20%20%20%20full-wave%20rectification%20followed%20by%20low-pass%20Butterworth%20filter.%0A%20%20%20%20%20%20%20%20Enter%20'rms'%20for%20a%20moving-RMS%20filter.%20For%20the%20'rms'%20method%2C%20the%0A%20%20%20%20%20%20%20%20window%20size%20is%20the%20nearest%20integer%20of%20freq%2Ffc_lp.%20For%20RMS%2C%20fc_lp%20is%0A%20%20%20%20%20%20%20%20an%20inverse%20window-duration%20parameter%2C%20not%20a%20Butterworth%20cutoff%3B%20one%0A%20%20%20%20%20%20%20%20centered%20mean-square%20convolution%20is%20followed%20by%20a%20square%20root.%0A%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20y%20%3A%201D%20array_like%0A%20%20%20%20%20%20%20%20Linear%20envelope%20of%20the%20signal%0A%0A%20%20%20%20Notes%0A%20%20%20%20-----%0A%20%20%20%20A%202nd-order%20Butterworth%20filter%20with%20zero%20lag%20is%20used%20for%20the%20filtering%0A%20%20%20%20The%20moving-RMS%20filter%20is%20calculated%20by%20one%20convolution%20with%20mode%0A%20%20%20%20equal%20to%20'same'%2C%20so%20the%20extremities%20of%20the%20signal%20of%20size%20'window'%2F2%20are%0A%20%20%20%20not%20reliable.%0A%0A%20%20%20%20See%20this%20notebook%20%5B1%5D_.%0A%0A%20%20%20%20References%0A%20%20%20%20----------%0A%20%20%20%20..%20%5B1%5D%20https%3A%2F%2Fmolab.marimo.io%2Fgithub%2FBMClab%2FBMC%2Fblob%2Fmaster%2Fnotebooks%2Felectromyography.py%0A%0A%20%20%20%20Version%20history%0A%20%20%20%20---------------%0A%20%20%20%20'1.0.1'%3A%0A%20%20%20%20%20%20%20%20Included%20parameter%20'method'%2C%20to%20select%20method%20for%20the%20linear%20envelope.%0A%20%20%20%20%20%20%20%20Changed%20parameters%20fc_bp%20and%20fc_lp%20to%20be%20written%20in%20lowercase.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20from%20scipy.signal%20import%20butter%2C%20filtfilt%0A%20%20%20%20if%20fc_bp%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20fc_bp%20%3D%20np.array(fc_bp)%0A%20%20%20%20%20%20%20%20b%2C%20a%20%3D%20butter(N%3D2%2C%20Wn%3Dfc_bp%20%2F%20(freq%20%2F%202)%2C%20btype%3D'bandpass')%0A%20%20%20%20%20%20%20%20x%20%3D%20filtfilt(b%2C%20a%2C%20x)%0A%20%20%20%20if%20method%20%3D%3D%20'rect'%3A%0A%20%20%20%20%20%20%20%20x%20%3D%20abs(x)%0A%20%20%20%20%20%20%20%20b%2C%20a%20%3D%20butter(N%3D2%2C%20Wn%3Dfc_lp%20%2F%20(freq%20%2F%202)%2C%20btype%3D'low')%0A%20%20%20%20%20%20%20%20y%20%3D%20filtfilt(b%2C%20a%2C%20x)%0A%20%20%20%20elif%20method%20%3D%3D%20'rms'%3A%0A%20%20%20%20%20%20%20%20y%20%3D%20moving_rms(x%2C%20freq%20%2F%20fc_lp)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError(%22Valid%20method%20is%20'rect'%20or%20'rms'%22)%0A%20%20%20%20return%20y%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20def%20_plot_df_emg(df%2C%20ax%2C%20suptitle)%3A%0A%20%20%20%20%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20%20%20%20%20%22%22%22Plot%20EMG%20data%20of%20the%20read_delsys%20function%2C%20see%20its%20help.%22%22%22%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20%20%20%20%20except%20ImportError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print('matplotlib%20is%20not%20available.')%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ax%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nplots%20%3D%20df.shape%5B1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20nplots%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nrows%2C%20ncols%20%3D%20(nplots%2C%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20nplots%20%3D%3D%204%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nrows%2C%20ncols%20%3D%20(2%2C%202)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ncols%20%3D%203%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nrows%20%3D%20int(np.ceil(nplots%20%2F%20ncols))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_%2C%20ax%20%3D%20plt.subplots(nrows%2C%20ncols%2C%20figsize%3D(9%2C%206)%2C%20sharex%3D'all'%2C%20constrained_layout%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20no_ax%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20no_ax%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%20%3D%20df.plot(color%3D'b'%2C%20ax%3Dax%2C%20subplots%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20suptitle%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20plt.suptitle(suptitle)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20no_ax%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20plt.show()%0A%0A%20%20%20%20return%0A%0A%0A%40app.function%0Adef%20read_delsys(fname%2C%20fname2%3D''%2C%20sensors%3DNone%2C%20freq_trc%3D150%2C%20emg%3DTrue%2C%20imu%3DFalse%2C%20resample%3D%5B1200%2C%20150%5D%2C%20freqs%3D%5B20%2C%2020%2C%20450%5D%2C%20show_msg%3DTrue%2C%20show%3DFalse%2C%20ax%3DNone%2C%20suptitle%3D'')%3A%0A%20%20%20%20%22%22%22Read%20Delsys%20csv%20file%20from%20Cortex%20MAC%20(Asynchronous%20device%20data%20file).%0A%0A%20%20%20%20Parameters%0A%20%20%20%20----------%0A%20%20%20%20fname%20%3A%20string%0A%20%20%20%20%20%20%20%20Full%20file%20name%20of%20the%20Delsys%20csv%20file%20from%20Cortex%20file%20to%20be%20opened.%0A%20%20%20%20fname2%20%3A%20string%2C%20optional%20(default%20%3D%20'')%0A%20%20%20%20%20%20%20%20Full%20file%20name%20of%20the%20text%20file%20to%20be%20saved%20with%20data%20if%20desired.%0A%20%20%20%20%20%20%20%20If%20both%20parameters%20%60emg%60%20and%20%60imu%60%20are%20True%2C%20you%20must%20input%20a%20list%20with%0A%20%20%20%20%20%20%20%20the%20two%20full%20file%20names%20(EMG%20and%20IMU).%0A%20%20%20%20%20%20%20%20If%20fname2%20is%20''%2C%20no%20file%20is%20saved.%0A%20%20%20%20%20%20%20%20If%20fname2%20is%20'%3D'%2C%20the%20original%20file%20name%20will%20be%20used%20but%20its%20extension%0A%20%20%20%20%20%20%20%20will%20be%20.emg%20and%20.imu%20for%20the%20files%20with%20EMG%20data%20and%20with%20IMU%20data%20(if%0A%20%20%20%20%20%20%20%20parameters%20%60emg%60%20and%20%60imu%60%20are%20True).%0A%20%20%20%20sensors%20%3A%20list%20of%20strings%2C%20optional%0A%20%20%20%20%20%20%20%20Names%20of%20sensors%20to%20be%20used%20as%20column%20names%20for%20the%20EMG%20and%20IM%20data.%0A%20%20%20%20freq_trc%20%3A%20number%2C%20optional%20(default%20%3D%20150)%0A%20%20%20%20%20%20%20%20Sampling%20frequency%20of%20the%20markers%20data%0A%20%20%20%20emg%20%3A%20bool%2C%20optional%20(default%20%3D%20True)%0A%20%20%20%20%20%20%20%20Read%20and%20save%20EMG%20data%0A%20%20%20%20imu%20%3A%20bool%2C%20optional%20(default%20%3D%20False)%0A%20%20%20%20%20%20%20%20Read%20and%20save%20IMU%20data%0A%20%20%20%20resample%20%3A%20list%20with%20two%20numbers%2C%20optional%20(default%20%3D%20%5B1200%2C%20150%5D)%0A%20%20%20%20%20%20%20%20Whether%20to%20resample%20the%20data%20to%20have%20the%20given%20frequencies.%0A%20%20%20%20%20%20%20%20The%20list%20order%20is%20%5Bfreq_emg%2C%20freq_imu%5D.%20Enter%200%20(zero)%20to%20not%20resample.%0A%20%20%20%20%20%20%20%20It's%20used%20signal.resample_poly%20scipy%20function.%0A%20%20%20%20%20%20%20%20For%20the%20EMG%20signal%2C%20if%20the%20parameter%20frequency%20is%20lower%20than%201000%20Hz%2C%0A%20%20%20%20%20%20%20%20first%20it%20will%20be%20calculated%20the%20linear%20envelope%20with%20a%20low-pass%0A%20%20%20%20%20%20%20%20frequency%20given%20by%20parameter%20freqs%5B0%5D%20(but%20first%20the%20EMG%20data%20will%20be%0A%20%20%20%20%20%20%20%20band-pass%20filtered%20with%20frequencies%20given%20by%20parameters%20freqs%5B1%5D%2C%20freqs%5B2%5D.%0A%20%20%20%20freqs%20%3A%20list%20of%20three%20numbers%2C%20optional%20(default%20%3D%20%5B20%2C%2020%2C%20450%5D)%0A%20%20%20%20%20%20%20%20Frequencies%20to%20be%20used%20at%20the%20linear%20envelope%20calculation%20if%20desired.%0A%20%20%20%20%20%20%20%20See%20the%20parameter%20%60resample%60.%0A%20%20%20%20show_msg%20%3A%20bool%2C%20optional%20(default%20%3D%20True)%0A%20%20%20%20%20%20%20%20Whether%20to%20print%20messages%20about%20the%20execution%20of%20the%20intermediary%20steps%0A%20%20%20%20%20%20%20%20(True)%20or%20not%20(False).%0A%20%20%20%20show%20%3A%20bool%2C%20optional%20(default%20%3D%20False)%0A%20%20%20%20%20%20%20%20if%20True%20(1)%2C%20plot%20data%20in%20matplotlib%20figure.%0A%20%20%20%20ax%20%3A%20a%20matplotlib.axes.Axes%20instance%2C%20optional%20(default%20%3D%20None).%0A%20%20%20%20suptitle%20%3A%20string%2C%20optional%20(default%20%3D%20'')%0A%20%20%20%20%20%20%20%20If%20string%2C%20shows%20string%20as%20suptitle.%20If%20empty%2C%20doesn't%20show%20suptitle.%0A%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20data%20%3A%201%20or%202%20pandas%20dataframe%0A%20%20%20%20%20%20%20%20df_emg%20and%20df_imu%20if%20paramters%20%60emg%60%20and%20%60imu%60.%0A%20%20%20%20%20%20%20%20The%20units%20of%20df_emg%20will%20be%20mV%20(the%20raw%20signal%20is%20multiplied%20by%201000).%0A%20%20%20%20%20%20%20%20The%20units%20of%20the%20IMU%20data%20are%20according%20to%20Delsys%20specification.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20os%0A%20%20%20%20from%20scipy%20import%20signal%0A%20%20%20%20from%20fractions%20import%20Fraction%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D'rt'%2C%20encoding%3D'utf-8'%2C%20newline%3DNone)%20as%20f%3A%0A%20%20%20%20%20%20%20%20if%20show_msg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'Opening%20file%20%22%7Bfname%7D%22%20...%20'%2C%20end%3D'')%0A%20%20%20%20%20%20%20%20file%20%3D%20f.read().splitlines()%0A%20%20%20%20%20%20%20%20if%20file%5B0%5D%20!%3D%20'Cortex%20generated%20Asynchronous%20device%20data%20file%20(.add)'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError('Not%20a%20Cortex%20asynchronous%20Delsys%20export')%0A%20%20%20%20%20%20%20%20idx%20%3D%20file.index('%5BDevices%5D')%20%2B%202%0A%20%20%20%20%20%20%20%20count%20%3D%20int(file%5Bidx%5D.split('%3D')%5B1%5D)%0A%20%20%20%20%20%20%20%20devices%20%3D%20%5Bname.split('%2C%20')%5B-1%5D%20for%20name%20in%20file%5Bidx%20%2B%201%3Aidx%20%2B%201%20%2B%20count%5D%5D%0A%20%20%20%20%20%20%20%20if%20sensors%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sensors%20%3D%20devices%0A%20%20%20%20%20%20%20%20idx%20%3D%20idx%20%2B%203%20%2B%20count%0A%20%20%20%20%20%20%20%20count2%20%3D%20int(file%5Bidx%5D.split('%3D')%5B1%5D)%0A%20%20%20%20%20%20%20%20channels%20%3D%20%5Bname%20for%20name%20in%20file%5Bidx%20%2B%201%3Aidx%20%2B%201%20%2B%20count2%5D%5D%0A%20%20%20%20%20%20%20%20n_im%20%3D%20int((count2%20-%20count)%20%2F%20count)%0A%20%20%20%20%20%20%20%20idxs%20%3D%20np.zeros((count%2C%204)%2C%20dtype%3Dint)%0A%20%20%20%20%20%20%20%20for%20i%2C%20device%20in%20enumerate(devices)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20idxs%5Bi%2C%200%5D%20%3D%20file.index(device)%20%2B%203%0A%20%20%20%20%20%20%20%20%20%20%20%20idxs%5Bi%2C%201%5D%20%3D%20file%5Bidxs%5Bi%2C%200%5D%3A%5D.index('')%20%2B%20idxs%5Bi%2C%200%5D%20-%201%0A%20%20%20%20%20%20%20%20idxs%5B%3A%2C%202%5D%20%3D%20idxs%5B%3A%2C%201%5D%20%2B%203%0A%20%20%20%20%20%20%20%20idxs%5B%3A%2C%203%5D%20%3D%20np.r_%5Bidxs%5B1%3A%2C%200%5D%20-%206%2C%20np.array(len(file)%20-%203%2C%20dtype%3Dint%2C%20ndmin%3D1)%5D%0A%20%20%20%20%20%20%20%20if%20emg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nrows_emg%20%3D%20int(np.min(idxs%5B%3A%2C%201%5D%20-%20idxs%5B%3A%2C%200%5D)%20%2B%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20f.seek(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20t_emg%20%3D%20pd.read_csv(f%2C%20sep%3D'%2C'%2C%20header%3DNone%2C%20names%3DNone%2C%20index_col%3DNone%2C%20usecols%3D%5B2%5D%2C%20skiprows%3Didxs%5B0%2C%200%5D%2C%20nrows%3Dnrows_emg%2C%20dtype%3Dnp.float32%2C%20encoding%3D'utf-8'%2C%20engine%3D'c').squeeze('columns').to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20freq_emg%20%3D%20np.mean(freq_trc%20%2F%20np.diff(t_emg))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20resample%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fr%20%3D%20Fraction(resample%5B0%5D%20%2F%20freq_emg).limit_denominator(1000)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nrows_emg%20%3D%20int(np.ceil(nrows_emg%20*%20fr.numerator%20%2F%20fr.denominator))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20freq_emg2%20%3D%20resample%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20freq_emg2%20%3D%20freq_emg%0A%20%20%20%20%20%20%20%20%20%20%20%20ys%20%3D%20np.empty((nrows_emg%2C%20count)%2C%20dtype%3Dnp.float32)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20sensor%20in%20enumerate(sensors)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f.seek(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y%20%3D%20pd.read_csv(f%2C%20sep%3D'%2C'%2C%20header%3DNone%2C%20names%3D%5Bsensor%5D%2C%20index_col%3DNone%2C%20usecols%3D%5B3%5D%2C%20skiprows%3Didxs%5Bi%2C%200%5D%2C%20nrows%3Dlen(t_emg)%2C%20dtype%3Dnp.float32%2C%20encoding%3D'utf-8'%2C%20engine%3D'c').squeeze('columns').to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20resample%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20resample%5B0%5D%20%3C%201000%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y%20%3D%20linear_envelope(y%2C%20freq_emg%2C%20fc_bp%3D%5Bfreqs%5B1%5D%2C%20freqs%5B2%5D%5D%2C%20fc_lp%3Dfreqs%5B0%5D%2C%20method%3D'rms')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y%20%3D%20signal.resample_poly(y%2C%20fr.numerator%2C%20fr.denominator)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ys%5B%3A%2C%20i%5D%20%3D%20y%20*%201000%0A%20%20%20%20%20%20%20%20%20%20%20%20df_emg%20%3D%20pd.DataFrame(data%3Dys%2C%20columns%3Dsensors)%0A%20%20%20%20%20%20%20%20%20%20%20%20df_emg.index%20%3D%20float(t_emg%5B0%5D)%20%2F%20freq_trc%20%2B%20df_emg.index%20%2F%20freq_emg2%0A%20%20%20%20%20%20%20%20%20%20%20%20df_emg.index.name%20%3D%20'Time'%0A%20%20%20%20%20%20%20%20if%20imu%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nrows_imu%20%3D%20int(np.min(idxs%5B%3A%2C%203%5D%20-%20idxs%5B%3A%2C%202%5D)%20%2B%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%20%3D%20%5Bsensor%20%2B%20channel.split('%2C')%5B3%5D%20for%20sensor%20in%20sensors%20for%20channel%20in%20channels%5B1%3Aint(count2%20%2F%20count)%5D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20f.seek(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20t_imu%20%3D%20pd.read_csv(f%2C%20sep%3D'%2C'%2C%20header%3DNone%2C%20names%3DNone%2C%20index_col%3DNone%2C%20usecols%3D%5B2%5D%2C%20skiprows%3Didxs%5B0%2C%202%5D%2C%20nrows%3Dnrows_imu%2C%20dtype%3Dnp.float32%2C%20encoding%3D'utf-8'%2C%20engine%3D'c').squeeze('columns').to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20freq_imu%20%3D%20np.mean(freq_trc%20%2F%20np.diff(t_imu))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20resample%5B1%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fr%20%3D%20Fraction(resample%5B1%5D%20%2F%20freq_imu).limit_denominator(1000)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nrows_imu%20%3D%20int(np.ceil(nrows_imu%20*%20fr.numerator%20%2F%20fr.denominator))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20freq_imu%20%3D%20resample%5B1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ys%20%3D%20np.empty((nrows_imu%2C%20count2%20-%20count)%2C%20dtype%3Dnp.float32)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20sensor%20in%20enumerate(sensors)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f.seek(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cs%20%3D%20slice(int(n_im%20*%20i)%2C%20int(n_im%20*%20(i%20%2B%201)))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y%20%3D%20pd.read_csv(f%2C%20sep%3D'%2C'%2C%20header%3DNone%2C%20names%3Dcols%5Bcs%5D%2C%20index_col%3DNone%2C%20usecols%3Drange(3%2C%203%20%2B%20n_im)%2C%20skiprows%3Didxs%5Bi%2C%202%5D%2C%20nrows%3Dlen(t_imu)%2C%20dtype%3Dnp.float32%2C%20encoding%3D'utf-8'%2C%20engine%3D'c').squeeze('columns').to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20resample%5B1%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y2%20%3D%20np.empty((nrows_imu%2C%20y.shape%5B1%5D)%2C%20dtype%3Dnp.float32)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20range(y.shape%5B1%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y2%5B%3A%2C%20c%5D%20%3D%20signal.resample_poly(y%5B%3A%2C%20c%5D%2C%20fr.numerator%2C%20fr.denominator)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y2%20%3D%20y%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ys%5B%3A%2C%20cs%5D%20%3D%20y2%0A%20%20%20%20%20%20%20%20%20%20%20%20df_imu%20%3D%20pd.DataFrame(data%3Dys%2C%20columns%3Dcols)%0A%20%20%20%20%20%20%20%20%20%20%20%20df_imu.index%20%3D%20float(t_imu%5B0%5D)%20%2F%20freq_trc%20%2B%20df_imu.index%20%2F%20freq_imu%0A%20%20%20%20%20%20%20%20%20%20%20%20df_imu.index.name%20%3D%20'Time'%0A%20%20%20%20%20%20%20%20if%20show_msg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print('done.')%0A%20%20%20%20if%20len(fname2)%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(fname2%2C%20list)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fname2_emg%20%3D%20fname2%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20fname2_imu%20%3D%20fname2%5B1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20emg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fname2_emg%20%3D%20fname2%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20imu%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fname2_imu%20%3D%20fname2%0A%20%20%20%20%20%20%20%20if%20emg%20and%20fname2_emg%20%3D%3D%20'%3D'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20name%2C%20extension%20%3D%20os.path.splitext(fname)%0A%20%20%20%20%20%20%20%20%20%20%20%20fname2_emg%20%3D%20name%20%2B%20'.emg'%0A%20%20%20%20%20%20%20%20if%20imu%20and%20fname2_imu%20%3D%3D%20'%3D'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20name%2C%20extension%20%3D%20os.path.splitext(fname)%0A%20%20%20%20%20%20%20%20%20%20%20%20fname2_imu%20%3D%20name%20%2B%20'.imu'%0A%20%20%20%20%20%20%20%20if%20emg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_emg.to_csv(fname2_emg%2C%20sep%3D'%5Ct'%2C%20float_format%3D'%25.6f')%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20show_msg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'Saving%20file%20%22%7Bfname2_emg%7D%22%20...%20'%2C%20end%3D'')%0A%20%20%20%20%20%20%20%20if%20imu%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_imu.to_csv(fname2_imu%2C%20sep%3D'%5Ct'%2C%20float_format%3D'%25.6f')%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20show_msg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%5CnSaving%20file%20%22%7Bfname2_imu%7D%22%20...%20'%2C%20end%3D'')%0A%20%20%20%20%20%20%20%20if%20show_msg%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print('done.')%0A%20%20%20%20if%20show%20and%20emg%3A%0A%20%20%20%20%20%20%20%20_plot_df_emg(df_emg%2C%20ax%3DNone%2C%20suptitle%3Dsuptitle)%0A%20%20%20%20if%20emg%20and%20imu%3A%0A%20%20%20%20%20%20%20%20return%20(df_emg%2C%20df_imu)%0A%20%20%20%20elif%20emg%3A%0A%20%20%20%20%20%20%20%20return%20df_emg%0A%20%20%20%20elif%20imu%3A%0A%20%20%20%20%20%20%20%20return%20df_imu%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Choose%20the%20source%0A%0A%20%20%20%20With%20no%20upload%2C%20the%20example%20contains%20a%20**synthetic**%20EMG%20waveform%20at%201%2C000%20Hz%20and%20nine%20synthetic%20inertial%20channels%20at%20100%20Hz.%20It%20is%20a%20parser%20demonstration%2C%20not%20an%20experimental%20recording.%20The%20time%20column%20stores%20Cortex%20frame%20units%2C%20converted%20using%20the%20Cortex%20rate%20below.%20Confirm%20that%20rate%20for%20a%20real%20export%3B%20it%20cannot%20be%20inferred%20from%20a%20filename.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20delsys_upload%20%3D%20mo.ui.file(filetypes%3D%5B%22.csv%22%2C%20%22.add%22%5D%2C%20multiple%3DFalse%2C%20label%3D%22Optional%20Cortex%2FDelsys%20export%22)%0A%20%20%20%20cortex_rate%20%3D%20mo.ui.number(value%3D150%2C%20start%3D1%2C%20label%3D%22Cortex%20frame%20rate%20%5BHz%5D%22)%0A%20%20%20%20mo.vstack(%5Bdelsys_upload%2C%20cortex_rate%5D)%0A%20%20%20%20return%20cortex_rate%2C%20delsys_upload%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20delsys_upload%2C%20mo%2C%20tempfile)%3A%0A%20%20%20%20_folder%20%3D%20Path(tempfile.mkdtemp(prefix%3D%22bmc-delsys-%22))%0A%20%20%20%20input_path%20%3D%20_folder%20%2F%20%22input.add%22%0A%20%20%20%20if%20delsys_upload.value%3A%0A%20%20%20%20%20%20%20%20input_path.write_bytes(delsys_upload.value%5B0%5D.contents)%0A%20%20%20%20%20%20%20%20source_label%20%3D%20%22Uploaded%20recording%22%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20input_path.write_text(delsys_demo_text()%2C%20encoding%3D%22utf-8%22)%0A%20%20%20%20%20%20%20%20source_label%20%3D%20%22Synthetic%20format%20demonstration%22%0A%20%20%20%20mo.md(f%22**Source%3A%20%7Bsource_label%7D.**%22)%0A%20%20%20%20return%20(input_path%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**Exercise%201.**%20The%20EMG%20time%20column%20increases%20by%200.15%20Cortex%20frames%20per%20sample%20and%20the%20Cortex%20rate%20is%20150%20Hz.%20What%20is%20the%20EMG%20sample%20rate%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Solution%20to%20Exercise%201%0A%0A%20%20%20%20One%20EMG%20sample%20spans%20%240.15%2F150%3D0.001%24%20s%2C%20so%20its%20sampling%20rate%20is%201%2C000%20Hz.%20The%20Cortex%20rate%20and%20EMG%20rate%20are%20different%20quantities.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Read%20the%20original%20sample%20rates%0A%0A%20%20%20%20%60resample%3D%5B0%2C0%5D%60%20preserves%20both%20native%20grids.%20The%20parser%20converts%20EMG%20volts%20to%20millivolts.%20Inertial-channel%20units%20depend%20on%20the%20sensor%2Fexport%20specification%20and%20are%20not%20inferred%20from%20their%20order.%20%60fname2%3D%22%5C%22%60%20disables%20output-file%20writing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cortex_rate%2C%20input_path%2C%20mo)%3A%0A%20%20%20%20df_emg%2C%20df_imu%20%3D%20read_delsys(input_path%2C%20fname2%3D%22%22%2C%20sensors%3DNone%2C%20freq_trc%3Dcortex_rate.value%2C%20emg%3DTrue%2C%20imu%3DTrue%2C%20resample%3D%5B0%2C%200%5D%2C%20show_msg%3DFalse)%0A%20%20%20%20mo.vstack(%5Bdf_emg.head()%2C%20df_imu.head()%5D)%0A%20%20%20%20return%20df_emg%2C%20df_imu%0A%0A%0A%40app.cell%0Adef%20_(df_emg%2C%20df_imu%2C%20np)%3A%0A%20%20%20%20%7B%22EMG%20%5BHz%5D%22%3A%201%20%2F%20np.mean(np.diff(df_emg.index))%2C%20%22IMU%20%5BHz%5D%22%3A%201%20%2F%20np.mean(np.diff(df_imu.index))%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**Exercise%202.**%20What%20is%20a%20voltage%20of%200.002%20V%20in%20millivolts%3F%20Does%20that%20conversion%20change%20its%20sampling%20rate%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Solution%20to%20Exercise%202%0A%0A%20%20%20%20It%20is%202%20mV.%20Multiplying%20amplitudes%20by%201%2C000%20changes%20the%20unit%20scale%20but%20leaves%20timestamps%20and%20sampling%20frequency%20unchanged.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20A%20lower-rate%20EMG%20envelope%20is%20a%20different%20signal%0A%0A%20%20%20%20The%20original%20example%20requested%20150%20Hz%20for%20both%20outputs.%20In%20this%20reader%2C%20requesting%20an%20EMG%20output%20rate%20below%201%2C000%20Hz%20first%20computes%20an%20amplitude%20envelope%20after%20band-pass%20filtering.%20That%20output%20is%20an%20envelope%2C%20not%20raw%20EMG.%20The%20default%2020%E2%80%93450%20Hz%20band%20requires%20a%20sufficiently%20high%20input%20sampling%20rate%3B%20choose%20cutoffs%20for%20the%20actual%20recording.%20Here%20we%20demonstrate%20the%20transformation%20on%20the%20known%20synthetic%20source%20so%20its%20assumptions%20are%20explicit.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(input_path%2C%20plt)%3A%0A%20%20%20%20_demo_path%20%3D%20input_path.parent%20%2F%20%22synthetic_for_envelope.add%22%0A%20%20%20%20_demo_path.write_text(delsys_demo_text()%2C%20encoding%3D%22utf-8%22)%0A%20%20%20%20envelope%2C%20imu_resampled%20%3D%20read_delsys(_demo_path%2C%20fname2%3D%22%22%2C%20freq_trc%3D150%2C%20emg%3DTrue%2C%20imu%3DTrue%2C%20resample%3D%5B150%2C%20150%5D%2C%20show_msg%3DFalse)%0A%20%20%20%20_fig%2C%20_axs%20%3D%20plt.subplots(2%2C%201%2C%20figsize%3D(9%2C%205)%2C%20sharex%3DTrue)%0A%20%20%20%20envelope.plot(ax%3D_axs%5B0%5D%2C%20legend%3DFalse)%0A%20%20%20%20imu_resampled.iloc%5B%3A%2C%20%3A3%5D.plot(ax%3D_axs%5B1%5D)%0A%20%20%20%20_axs%5B0%5D.set_ylabel(%22EMG%20envelope%20%5BmV%5D%22)%0A%20%20%20%20_axs%5B1%5D.set_ylabel(%22Synthetic%20IMU%20%5Ba.u.%5D%22)%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**Exercise%203.**%20Can%20a%20raw%20450%20Hz%20EMG%20component%20be%20represented%20without%20aliasing%20on%20a%20150%20Hz%20grid%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Solution%20to%20Exercise%203%0A%0A%20%20%20%20No.%20The%20new%20Nyquist%20frequency%20is%2075%20Hz.%20The%20example%20computes%20an%20amplitude%20envelope%20before%20resampling%3B%20it%20does%20not%20claim%20to%20preserve%20a%20450%20Hz%20carrier.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Checkpoint%20questions%0A%0A%20%20%20%201.%20Are%20EMG%20and%20IMU%20rows%20necessarily%20synchronous%20one-to-one%3F%0A%0A%20%20%20%202.%20What%20information%20must%20accompany%20uploaded%20data%3F%0A%0A%20%20%20%203.%20Does%20the%20notebook%20overwrite%20the%20input%20file%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Checkpoint%20answers%0A%0A%20%20%20%201.%20No%3B%20compare%20their%20times.%0A%20%20%20%202.%20The%20Cortex%20frame%20rate%2C%20format%20version%2C%20channel%20interpretation%2C%20and%20units.%0A%20%20%20%203.%20No.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Problems%0A%0A%20%20%20%20Try%20these%20independently%3B%20open%20a%20panel%20only%20to%20check%20your%20final%20answer.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**Problem%201.**%20One%20second%20of%20EMG%20at%201%2C000%20Hz%20and%20IMU%20at%20100%20Hz%20contains%20how%20many%20samples%20in%20each%20native%20grid%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Answer%20to%20Problem%201%0A%0A%20%20%20%201%2C000%20EMG%20samples%20and%20100%20IMU%20samples.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**Problem%202.**%20What%20is%20the%20Nyquist%20frequency%20on%20the%20150%20Hz%20output%20grid%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20Answer%20to%20Problem%202%0A%0A%20%20%20%2075%20Hz.%0A%20%20%20%20%2F%2F%2F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Go%20deeper%0A%0A%20%20%20%20Inspect%20the%20%5BEMG%20guide%5D(https%3A%2F%2Fmolab.marimo.io%2Fgithub%2FBMClab%2FBMC%2Fblob%2Fmaster%2Fnotebooks%2Felectromyography.py)%20before%20interpreting%20an%20envelope%20as%20muscle%20activation.%0A%0A%20%20%20%20%23%23%23%20Video%20lectures%20on%20the%20Internet%0A%0A%20%20%20%20%5BDelsys%20video%20resources%5D(https%3A%2F%2Fwww.youtube.com%2F%40DelsysInc)%20demonstrate%20EMG%20acquisition%20and%20sensor%20workflows.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20References%0A%0A%20%20%20%20-%20%5BDelsys%20support%5D(https%3A%2F%2Fdelsys.com%2Fsupport%2F).%0A%0A%20%20%20%20-%20%5BMotion%20Analysis%20Cortex%5D(https%3A%2F%2Fwww.motionanalysis.com%2F).%0A%0A%20%20%20%20-%20%5BSciPy%20resample_poly%5D(https%3A%2F%2Fdocs.scipy.org%2Fdoc%2Fscipy%2Freference%2Fgenerated%2Fscipy.signal.resample_poly.html).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
156304400d83a9c8e980f6913ef6d2cb