%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%22pandas%3E%3D2.2%22%2C%20%22urllib3%3E%3D2.2%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%20and%20write%20Cortex%20ASCII%20files%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%20A%20text%20file%20looks%20readable%2C%20yet%20its%20columns%20are%20meaningless%20until%20you%20know%20the%20units%2C%20sampling%20rate%2C%20and%20layout.%20What%20must%20you%20check%20before%20plotting%20it%3F%0A%0A%20%20%20%20We%20will%20inspect%20a%20TRC%20file%2C%20make%20a%20temporary%20round%20trip%20through%20the%20writer%2C%20and%20relate%20marker%2C%20analog%2C%20calibration%2C%20and%20force%20files.%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%20examples%20use%20the%20public%20%60walk1.trc%60%20and%20%60walk1.forces%60%20files.%20A%20temporary%20output%20and%20a%20download%20button%20leave%20the%20source%20recording%20intact.%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%20Motion%20Analysis%20Corporation%20(MAC%2C%20https%3A%2F%2Fwww.motionanalysis.com%2F)%20builds%20motion%20capture%20systems%20and%20their%20software%20(e.g.%2C%20Cortex)%20generates%20files%20in%20ASCII%20and%20binary%20formats%20for%20the%20different%20signals%20(kinematics%2C%20analog%20data%2C%20force%20plate%20data%2C%20etc.).%20Here%20are%20functions%20for%20reading%20most%20of%20the%20files%20saved%20in%20ASCII%20format.%20These%20files%20have%20headers%20with%20few%20lines%20with%20meta%20data%20and%20the%20signals%20are%20stored%20in%20columns%20and%20the%20rows%20for%20the%20different%20frames%20(instants%20of%20time).%0A%0A%20%20%20%20The%20%22.trc%22%20(Track%20Row%20Column)%20file%20in%20ASCII%20contains%20X-Y-Z%20position%20data%20for%20the%20reflective%20markers%20from%20a%20motion%20capture%20trial.%20The%20position%20data%20for%20each%20marker%20is%20organized%20into%203%20columns%20per%20marker%20(X%2C%20Y%20and%20Z%20position)%20with%20each%20row%20being%20a%20new%20frame.%20The%20position%20data%20is%20relative%20to%20the%20global%20coordinate%20system%20of%20the%20capture%20volume%20and%20the%20position%20values%20are%20in%20the%20units%20used%20for%20calibration.%0A%0A%20%20%20%20The%20%22.anc%22%20(Analog%20ASCII%20Row%20Column)%20file%20contains%20ASCII%20analog%20data%20in%20row-column%20format.%20The%20data%20is%20derived%20from%20%22.anb%22%20analog%20binary%20files.%20These%20binary%20%22.anb%22%20files%20are%20generated%20simultaneously%20with%20video%20%22.vc%22%20files%20if%20an%20optional%20analog%20input%20board%20is%20used%20in%20conjunction%20with%20video%20data%20capture.%0A%0A%20%20%20%20The%20%22.cal%22%20file%20contains%20force%20plate%20calibration%20parameters.%0A%0A%20%20%20%20The%20%22.forces%22%20file%20contains%20force%20plate%20data.%20The%20data%20is%20saved%20based%20on%20the%20%22forcepla.cal%22%20file%20of%20the%20trial%20and%20converts%20the%20raw%20force%20plate%20data%20into%20calibrated%20forces.%20The%20units%20used%20are%20Newtons%20and%20Newton-meters%20and%20each%20line%20in%20the%20file%20equates%20to%20one%20analog%20sample.%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%20current%20reader%20and%20writer%20functions%20are%20included%20below.%20Expand%20the%20code%20cells%20to%20inspect%20their%20handling%20of%20headers%20and%20columns.%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%20pandas%20as%20pd%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%20tempfile%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20from%20notebook_resources%20import%20repo_file%0A%20%20%20%20except%20ModuleNotFoundError%20as%20_error%3A%0A%20%20%20%20%20%20%20%20if%20_error.name%20!%3D%20%22notebook_resources%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%20%20%20%20%20%20%20%20raise%20ModuleNotFoundError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22This%20notebook%20needs%20notebook_resources.py.%20Download%20it%20from%3A%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fraw.githubusercontent.com%2FBMClab%2FBMC%2Fmaster%2Ffunctions%2Fnotebook_resources.py%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Save%20it%20in%20the%20same%20folder%20as%20this%20notebook%2C%20then%20rerun%20this%20cell.%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22notebook_resources%22%2C%0A%20%20%20%20%20%20%20%20)%20from%20None%0A%20%20%20%20return%20(repo_file%2C)%0A%0A%0A%40app.function%0Adef%20read_trc(fname%2C%20fname2%3D''%2C%20units%3D''%2C%20dropna%3DFalse%2C%20na%3D0.0%2C%20df_multi%3DTrue%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Read%20.trc%20file%20format%20from%20Cortex%20MAC.%0A%0A%20%20%20%20This%20function%3A%201.%20Delete%20markers%20(columns)%20of%20empty%20data%3B%202.%20Correct%0A%20%20%20%20number%20of%20markers%20in%20the%20header%20according%20to%20the%20actual%20number%20of%0A%20%20%20%20non-empty%20markers%3B%203.%20Save%20a%20'.trc'%20file%20with%20updated%20information%20and%0A%20%20%20%20data%3B%204.%20Returns%20header%20information%20and%20data.%0A%0A%20%20%20%20The%20.trc%20(Track%20Row%20Column)%20file%20in%20ASCII%20contains%20X-Y-Z%20position%0A%20%20%20%20data%20for%20the%20reflective%20markers%20from%20a%20motion%20capture%20trial.%20The%0A%20%20%20%20position%20data%20for%20each%20marker%20is%20organized%20into%203%20columns%20per%20marker%0A%20%20%20%20(X%2C%20Y%20and%20Z%20position)%20with%20each%20row%20being%20a%20new%20frame.%20The%20position%0A%20%20%20%20data%20is%20relative%20to%20the%20global%20coordinate%20system%20of%20the%20capture%20volume%0A%20%20%20%20and%20the%20position%20values%20are%20in%20the%20units%20used%20for%20calibration.%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%20.trc%20file%20to%20be%20opened.%0A%20%20%20%20fname2%20%3A%20string%20(default%20%3D%20'')%0A%20%20%20%20%20%20%20%20Full%20file%20name%20of%20the%20.trc%20file%20to%20be%20saved%20with%20updated%20information%0A%20%20%20%20%20%20%20%20and%20data%20if%20desired.%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.%0A%20%20%20%20%20%20%20%20If%20fname2%20is%20a%20string%20with%20length%20between%201%20and%203%20(other%20than%20'%3D')%2C%0A%20%20%20%20%20%20%20%20e.g.%2C%20'_2'%2C%20this%20string%20is%20appended%20to%20the%20original%20file%20name.%0A%20%20%20%20units%20%3A%20string%20(default%20%3D%20'')%0A%20%20%20%20%20%20%20%20Change%20the%20units%20of%20the%20data%20if%20desired.%0A%20%20%20%20%20%20%20%20Accepted%20output%20units%20are%20'm'%20or%20'mm'.%0A%20%20%20%20dropna%20%3A%20bool%20(default%20%3D%20False)%0A%20%20%20%20%20%20%20%20True%3A%20Delete%20a%20marker%20only%20if%20all%20three%20XYZ%20columns%20contain%20no%20data.%0A%20%20%20%20%20%20%20%20Partially%20missing%20markers%20keep%20their%20complete%20XYZ%20group%20and%20NaNs.%0A%20%20%20%20%20%20%20%20False%3A%20preserve%20column%20and%20replace%20column%20values%20by%20parameter%20%60na%60%0A%20%20%20%20%20%20%20%20(see%20below)%20if%20inputed%2C%20otherwise%20maintain%20default%20pandas%20value%20(NaN).%0A%20%20%20%20na%20%3A%20float%20or%20None%20(default%20%3D%200.0)%0A%20%20%20%20%20%20%20%20Value%20to%20replace%20(if%20%60dropna%60%20is%20False)%20column%20values%20if%20this%20column%0A%20%20%20%20%20%20%20%20has%20only%20missing%20or%20NaN%20values.%20Input%20None%20to%20maintain%20default%20pandas%0A%20%20%20%20%20%20%20%20value%20for%20this%20case%20(NaN).%0A%20%20%20%20df_multi%20%3A%20bool%20(default%20%3D%20True)%0A%20%20%20%20%20%20%20%20Whether%20to%20output%20data%20as%20pandas%20multilabel%20dataframe%20with%20%22Marker%22%2C%0A%20%20%20%20%20%20%20%20%22Coordinate%22%20and%20%22XYZ%22%2C%20as%20labels%20and%20%22Time%22%20as%20index%20(True)%20or%20simple%0A%20%20%20%20%20%20%20%20pandas%20dataframe%20with%20markerxyz%20as%20labels%20and%20%22Frame%23%22%20and%20%22Time%22%20as%0A%20%20%20%20%20%20%20%20columns%20(False).%0A%20%20%20%20show_msg%20%3A%20bool%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%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20h%20%3A%20Python%20dictionary%20with%20.trc%20header%20information%0A%20%20%20%20%20%20%20%20keys%3A%20header%20(the%20.trc%20full%20header)%2C%20data_rate%20(Hz)%2C%20camera_rate%20(Hz)%2C%0A%20%20%20%20%20%20%20%20nframes%2C%20nmarkers%2C%20markers%20(names)%2C%20xyz%20(X1%2CY1%2CZ1...)%2C%20units.%0A%20%20%20%20data%20%3A%20pandas%20dataframe%0A%20%20%20%20%20%20%20%20Two%20possible%20output%20formats%20according%20to%20the%20%60df_multi%60%20option%3A%0A%20%20%20%20%20%20%20%20Dataframe%20with%20shape%20(nframes%2C%202%2B3*nmarkers)%20with%20markerxyz%20as%20labels%0A%20%20%20%20%20%20%20%20and%20columns%3A%20Frame%23%2C%20time%20and%20position%20data.%0A%20%20%20%20%20%20%20%20Dataframe%20with%20shape%20(nframes%2C%203*nmarkers)%20with%20%22Marker%22%2C%20%22Coordinate%22%0A%20%20%20%20%20%20%20%20and%20%22XYZ%22%20as%20labels%2C%20%22Time%22%20as%20index%2C%20and%20data%20position%20as%20columns.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20os%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D'rt'%2C%20encoding%3D'utf-8'%2C%20newline%3D'')%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%20read%20%3D%20csv.reader(f%2C%20delimiter%3D'%5Ct')%0A%20%20%20%20%20%20%20%20header%20%3D%20%5Bnext(read)%20for%20x%20in%20range(6)%5D%0A%20%20%20%20%20%20%20%20nmarkers%20%3D%20int((len(header%5B3%5D)%20-%202)%20%2F%203)%0A%20%20%20%20%20%20%20%20markers%20%3D%20np.asarray(header%5B3%5D)%5Bnp.arange(2%2C%202%20%2B%203%20*%20nmarkers%2C%203)%5D.tolist()%0A%20%20%20%20%20%20%20%20x%20%3D%20markers%0A%20%20%20%20%20%20%20%20markers%20%3D%20%5Bv%20%2B%20str(x%5B%3Ai%5D.count(v)%20%2B%201)%20if%20x.count(v)%20%3E%201%20and%20x%5B%3Ai%5D.count(v)%20%3E%200%20else%20v%20for%20i%2C%20v%20in%20enumerate(x)%5D%0A%20%20%20%20%20%20%20%20markers3%20%3D%20%5Bm%20for%20m%20in%20markers%20for%20i%20in%20range(3)%5D%0A%20%20%20%20%20%20%20%20markersxyz%20%3D%20%5Ba%20%2B%20b%20for%20a%2C%20b%20in%20zip(markers3%2C%20%5B'x'%2C%20'y'%2C%20'z'%5D%20*%20nmarkers)%5D%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(f%2C%20sep%3D'%5Ct'%2C%20names%3D%5B'Frame%23'%2C%20'Time'%5D%20%2B%20markersxyz%2C%20index_col%3DFalse%2C%20encoding%3D'utf-8'%2C%20engine%3D'c')%0A%20%20%20%20%20%20%20%20if%20dropna%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20keep%20%3D%20%5B0%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20start%20in%20range(2%2C%20df.shape%5B1%5D%2C%203)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20df.iloc%5B%3A%2C%20start%3Astart%20%2B%203%5D.isna().to_numpy().all()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20keep.extend(range(start%2C%20start%20%2B%203))%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.iloc%5B%3A%2C%20keep%5D.copy()%0A%20%20%20%20%20%20%20%20elif%20na%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20col%20in%20df%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20df.loc%5B%3A%2C%20col%5D.isnull().sum()%20%3D%3D%20df.shape%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.loc%5B%3A%2C%20col%5D%20%3D%20na%0A%20%20%20%20%20%20%20%20nmarkers%20%3D%20int((df.shape%5B1%5D%20-%202)%20%2F%203)%0A%20%20%20%20%20%20%20%20if%20header%5B2%5D%5B3%5D%20!%3D%20str(nmarkers)%3A%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'%20Number%20of%20markers%20changed%20from%20%7Bheader%5B2%5D%5B3%5D%7D%20to%20%7Bnmarkers%7D.')%0A%20%20%20%20%20%20%20%20%20%20%20%20header%5B2%5D%5B3%5D%20%3D%20str(nmarkers)%0A%20%20%20%20%20%20%20%20header%5B3%5D%20%3D%20%5B''%20if%20c%5B-1%5D%20in%20%5B'y'%2C%20'z'%5D%20else%20c%5B%3A-1%5D%20if%20c%5B-1%5D%20in%20%5B'x'%5D%20else%20c%20for%20c%20in%20df.columns.values.tolist()%5D%20%2B%20%5B''%5D%0A%20%20%20%20%20%20%20%20markers%20%3D%20np.asarray(header%5B3%5D)%5Bnp.arange(2%2C%202%20%2B%203%20*%20nmarkers%2C%203)%5D.tolist()%0A%20%20%20%20%20%20%20%20n3%20%3D%20np.repeat(range(1%2C%20nmarkers%20%2B%201)%2C%203).tolist()%0A%20%20%20%20%20%20%20%20xyz%20%3D%20%5Ba%20%2B%20str(b)%20for%20a%2C%20b%20in%20zip(%5B'X'%2C%20'Y'%2C%20'Z'%5D%20*%20nmarkers%2C%20n3)%5D%0A%20%20%20%20%20%20%20%20header%5B4%5D%20%3D%20%5B''%2C%20''%5D%20%2B%20xyz%0A%20%20%20%20%20%20%20%20if%20units%20in%20(%22m%22%2C%20%22mm%22)%20and%20units%20!%3D%20header%5B2%5D%5B4%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.astype(%7Bcolumn%3A%20float%20for%20column%20in%20df.columns%5B2%3A%5D%7D)%0A%20%20%20%20%20%20%20%20if%20units%20%3D%3D%20'm'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20header%5B2%5D%5B4%5D%20%3D%3D%20'mm'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.iloc%5B%3A%2C%202%3A%5D%20%3D%20df.iloc%5B%3A%2C%202%3A%5D%20%2F%201000%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%5B2%5D%5B4%5D%20%3D%20'm'%0A%20%20%20%20%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%20%20%20%20%20print('%20Units%20changed%20from%20%7B%7D%20to%20%7B%7D'.format('%22mm%22'%2C%20'%22m%22'))%0A%20%20%20%20%20%20%20%20elif%20units%20%3D%3D%20'mm'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20header%5B2%5D%5B4%5D%20%3D%3D%20'm'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.iloc%5B%3A%2C%202%3A%5D%20%3D%20df.iloc%5B%3A%2C%202%3A%5D%20*%201000%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%5B2%5D%5B4%5D%20%3D%20'mm'%0A%20%20%20%20%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%20%20%20%20%20print('%20Units%20changed%20from%20%7B%7D%20to%20%7B%7D'.format('%22m%22'%2C%20'%22mm%22'))%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%20fname2%20%3D%3D%20'%3D'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fname2%20%3D%20fname%0A%20%20%20%20%20%20%20%20elif%20len(fname2)%20%3C%3D%203%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%20%3D%20name%20%2B%20fname2%20%2B%20extension%0A%20%20%20%20%20%20%20%20write_trc(fname2%2C%20header%2C%20df%2C%20show_msg)%0A%20%20%20%20h%20%3D%20%7B'header'%3A%20header%2C%20'data_rate'%3A%20float(header%5B2%5D%5B0%5D)%2C%20'camera_rate'%3A%20float(header%5B2%5D%5B1%5D)%2C%20'nframes'%3A%20int(header%5B2%5D%5B2%5D)%2C%20'nmarkers'%3A%20int(header%5B2%5D%5B3%5D)%2C%20'markers'%3A%20markers%2C%20'xyz'%3A%20xyz%2C%20'units'%3A%20header%5B2%5D%5B4%5D%2C%20'fname'%3A%20fname%2C%20'fname2'%3A%20fname2%7D%0A%20%20%20%20if%20df_multi%3A%0A%20%20%20%20%20%20%20%20df.drop(labels%3D'Frame%23'%2C%20axis%3D1%2C%20inplace%3DTrue)%0A%20%20%20%20%20%20%20%20df.set_index('Time'%2C%20inplace%3DTrue)%0A%20%20%20%20%20%20%20%20df.index.name%20%3D%20'Time'%0A%20%20%20%20%20%20%20%20cols%20%3D%20%5Bs%5B%3A-1%5D%20for%20s%20in%20df.columns.str.replace('.'%2C%20'_')%5D%0A%20%20%20%20%20%20%20%20df.columns%20%3D%20%5Bcols%2C%20list('XYZ')%20*%20int(df.shape%5B1%5D%20%2F%203)%2C%20xyz%5D%0A%20%20%20%20%20%20%20%20df.columns.set_names(names%3D%5B'Marker'%2C%20'Coordinate'%2C%20'XYZ'%5D%2C%20level%3D%5B0%2C%201%2C%202%5D%2C%20inplace%3DTrue)%0A%20%20%20%20return%20(h%2C%20df)%0A%0A%0A%40app.function%0Adef%20read_anc(fname%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Read%20.anc%20file%20format%20from%20Cortex%20MAC.%0A%0A%20%20%20%20The%20.anc%20(Analog%20ASCII%20Row%20Column)%20file%20contain%20ASCII%20analog%20data%0A%20%20%20%20in%20row-column%20format.%20The%20data%20is%20derived%20from%20*.anb%20analog%20binary%0A%20%20%20%20files.%20These%20binary%20*.anb%20files%20are%20generated%20simultaneously%20with%0A%20%20%20%20video%20*.vc%20files%20if%20an%20optional%20analog%20input%20board%20is%20used%20in%0A%20%20%20%20conjunction%20with%20video%20data%20capture.%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%20.anc%20file%20to%20be%20opened%0A%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20h%20%3A%20Python%20dictionary%0A%20%20%20%20%20%20%20%20.anc%20header%20information%0A%20%20%20%20%20%20%20%20keys%3A%20nbits%2C%20polarity%2C%20nchannels%2C%20data_rate%2C%20ch_names%2C%20ch_ranges%0A%20%20%20%20data%20%3A%20pandas%20dataframe%0A%20%20%20%20%20%20%20%20analog%20data%20with%20shape%20(nframes%2C%20nchannels)%0A%20%20%20%20show_msg%20%3A%20bool%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%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20os%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D'rt'%2C%20encoding%3D'utf-8'%2C%20newline%3D'')%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%20read%20%3D%20csv.reader(f%2C%20delimiter%3D'%5Ct')%0A%20%20%20%20%20%20%20%20header%20%3D%20%5Bnext(read)%20for%20x%20in%20range(11)%5D%0A%20%20%20%20%20%20%20%20h%20%3D%20%7B'nbits'%3A%20int(header%5B3%5D%5B1%5D)%2C%20'polarity'%3A%20header%5B1%5D%5B3%5D%2C%20'nchannels'%3A%20int(header%5B2%5D%5B7%5D)%2C%20'data_rate'%3A%20float(header%5B3%5D%5B3%5D)%2C%20'ch_names'%3A%20header%5B8%5D%2C%20'ch_ranges'%3A%20header%5B10%5D%7D%0A%20%20%20%20%20%20%20%20h%5B'ch_names'%5D%20%3D%20h%5B'ch_names'%5D%5B1%3A-1%5D%0A%20%20%20%20%20%20%20%20h%5B'ch_ranges'%5D%20%3D%20np.asarray(h%5B'ch_ranges'%5D%5B1%3A-1%5D%2C%20dtype%3Dfloat)%0A%20%20%20%20%20%20%20%20data%20%3D%20pd.read_csv(f%2C%20sep%3D'%5Ct'%2C%20names%3Dh%5B'ch_names'%5D%2C%20engine%3D'c'%2C%20usecols%3Dnp.arange(1%2C%201%20%2B%20h%5B'nchannels'%5D))%0A%20%20%20%20%20%20%20%20data%20*%3D%20h%5B'ch_ranges'%5D%20%2F%20(2%20**%20h%5B'nbits'%5D%20%2F%202%20-%202)%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%20return%20(h%2C%20data)%0A%0A%0A%40app.function%0Adef%20read_cal(fname%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Read%20.cal%20file%20format%20from%20Cortex%20MAC.%0A%0A%20%20%20%20The%20.cal%20(force%20plate%20calibration%20parameters)%20file%20in%20ASCII%20contains%3A%0A%0A%20%20%20%20%3Cforceplate%20number%3E%20%7B1%7D%0A%20%20%20%20%3Cscale%3E%20%3Clength%20(cm)%3E%20%3Cwidth%20(cm)%3E%20%7B2%7D%0A%20%20%20%20%3CN%20x%20N%20calibration%20matrix%20(the%20inverse%20sensitivity%20matrix)%3E%20%7B3%7D%0A%20%20%20%20%3Ctrue%20origin%20in%20relation%20to%20the%20geometric%20center%20(cm)%3E%0A%20%20%20%20%3Cgeometric%20center%20in%20relation%20to%20LCS%20origin%20(cm)%3E%0A%20%20%20%20%3C3%20x%203%20orientation%20matrix%3E%0A%20%20%20%20...repeat%20for%20next%20force%20plate...%0A%0A%20%20%20%20%7B1%7D%3A%20for%20a%20Kistler%20force%20plate%2C%20there%20is%20a%20'K'%20after%20the%20number%0A%20%20%20%20%7B2%7D%3A%20the%20scale%20is%20the%20inverse%20of%20the%20gain%0A%20%20%20%20%7B3%7D%3A%20N%20equal%208%20for%20Kistler%20and%20equal%206%20for%20all%20AMTI%20and%20Bertec%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%20.trc%20file%20to%20be%20opened%0A%20%20%20%20show_msg%20%3A%20bool%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%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20forcepla%20%3A%20Python%20dictionary%0A%20%20%20%20%20%20%20%20parameter%20from%20the%20froce%20plate%20calibration%20file%0A%20%20%20%20%20%20%20%20keys%3A%20'fp'%2C%20'scale'%2C%20'size'%2C%20'cal_matrix'%2C%20'origin'%2C%20'center'%2C%20'orientation'%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20fp%2C%20scale%2C%20size%2C%20cal_matrix%2C%20origin%2C%20center%2C%20orientation%20%3D%20(%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%5B%5D%2C%0A%20%20%20%20)%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D%22rt%22%2C%20encoding%3D%22utf-8%22%2C%20newline%3D%22%22)%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%22%22)%0A%20%20%20%20%20%20%20%20reader%20%3D%20(line.split()%20for%20line%20in%20f%20if%20line.strip())%0A%20%20%20%20%20%20%20%20for%20row%20in%20reader%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20force%20plate%20number%0A%20%20%20%20%20%20%20%20%20%20%20%20fp.append(int(row%5B0%5D.removesuffix(%22K%22)))%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20number%20of%20rows%20for%20Kistler%20or%20AMTI%2FBertec%20force%20plate%0A%20%20%20%20%20%20%20%20%20%20%20%20n%20%3D%208%20if%20row%5B0%5D%5B-1%5D%20%3D%3D%20%22K%22%20else%206%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20scale%20(inverse%20of%20the%20gain)%0A%20%20%20%20%20%20%20%20%20%20%20%20scale_size%20%3D%20np.array(next(reader)).astype(float)%0A%20%20%20%20%20%20%20%20%20%20%20%20scale.append(scale_size%5B0%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20force%20plate%20length%20(cm)%20and%20width%20(cm)%0A%20%20%20%20%20%20%20%20%20%20%20%20size.append(scale_size%5B1%3A%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20calibration%20matrix%20(the%20inverse%20sensitivity%20matrix)%0A%20%20%20%20%20%20%20%20%20%20%20%20matrix%20%3D%20%5Bnext(reader)%20for%20x%20in%20range(n)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20cal_matrix.append(np.array(matrix).astype(float))%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20true%20origin%20in%20relation%20to%20the%20geometric%20center%20(cm)%0A%20%20%20%20%20%20%20%20%20%20%20%20origin.append(np.array(next(reader)).astype(float))%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20geometric%20center%20in%20relation%20to%20LCS%20origin%20(cm)%0A%20%20%20%20%20%20%20%20%20%20%20%20center.append(np.array(next(reader)).astype(float))%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%203%20x%203%20orientation%20matrix%0A%20%20%20%20%20%20%20%20%20%20%20%20orienta%20%3D%20%5Bnext(reader)%20for%20x%20in%20range(3)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20orientation.append(np.array(orienta).astype(float))%0A%0A%20%20%20%20%20%20%20%20forcepla%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fp%22%3A%20fp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scale%22%3A%20scale%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22size%22%3A%20size%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22cal_matrix%22%3A%20cal_matrix%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22origin%22%3A%20origin%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22center%22%3A%20center%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22orientation%22%3A%20orientation%2C%0A%20%20%20%20%20%20%20%20%7D%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(%22done.%22)%0A%0A%20%20%20%20return%20forcepla%0A%0A%0A%40app.function%0Adef%20read_forces(fname%2C%20time%3DTrue%2C%20forcepla%3D%5B%5D%2C%20mm2m%3DTrue%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Read%20.forces%20file%20format%20from%20Cortex%20MAC.%0A%0A%20%20%20%20The%20.forces%20file%20in%20ASCII%20contains%20force%20plate%20data.%20The%20data%20is%20saved%0A%20%20%20%20based%20on%20the%20forcepla.cal%20file%20of%20the%20trial%20and%20converts%20the%20raw%20force%0A%20%20%20%20plate%20data%20into%20calibrated%20forces.%20The%20source%20units%20used%20are%20N%2C%20mm%20(COP)%2C%20and%20N%20mm%20(free%20moment)%20and%20each%20line%20in%20the%20file%20equates%20to%20one%20analog%20sample.%0A%0A%20%20%20%20Example%20of%20.forces%20file%20structure%3A%0A%0A%20%20%20%20%5BForce%20Data%5D%0A%20%20%20%20NumberOfForcePlates%3D7%0A%20%20%20%20SampleRate%3D150.000000%0A%20%20%20%20NumberOfSamples%3D150%0A%20%20%20%20%23Sample%20FX1%20FY1%20FZ1%20X1%20Y1%20Z1%20MZ1%20FX2%20...%0A%20%20%20%20...%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%20.forces%20file%20to%20be%20opened%0A%20%20%20%20time%20%3A%20bool%20(default%20%3D%20True)%0A%20%20%20%20%20%20%20%20Whether%20the%20data%20index%20is%20in%20units%20of%20time%20(True)%20or%20not%20(False).%0A%20%20%20%20forcepla%20%3A%20list%20of%20integers%20(default%20%3D%20%5B%5D)%0A%20%20%20%20%20%20%20%20List%20of%20force%20plates%20to%20read.%20An%20empty%20list%20reads%20all%20force%20plates.%0A%20%20%20%20%20%20%20%20Enter%20a%20list%20of%20force%20plate%20numbers%20to%20read.%0A%20%20%20%20mm2m%20%3A%20bool%20(default%20%3D%20True)%0A%20%20%20%20%20%20%20%20Whether%20to%20convert%20COP%20from%20mm%20to%20m%20and%20free%20moment%20from%20N%20mm%20to%20N%20m.%0A%20%20%20%20show_msg%20%3A%20bool%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%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20h%20%3A%20Python%20dictionary%0A%20%20%20%20%20%20%20%20.forces%20header%20information%0A%20%20%20%20%20%20%20%20keys%3A%20name%2C%20nforceplates%2C%20data_rate%2C%20nsamples%2C%20ch_names%0A%20%20%20%20df%20%3A%20pandas%20dataframe%0A%20%20%20%20%20%20%20%20force%20plate%20data%20with%20shape%20(nsamples%2C%207*nforceplates)%0A%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D%22rt%22%2C%20encoding%3D%22utf-8%22%2C%20newline%3D%22%22)%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%22%22)%0A%20%20%20%20%20%20%20%20%23%20get%20header%20information%0A%20%20%20%20%20%20%20%20read%20%3D%20csv.reader(f%2C%20delimiter%3D%22%5Ct%22)%0A%20%20%20%20%20%20%20%20header%20%3D%20%5Bnext(read)%20for%20x%20in%20range(5)%5D%0A%20%20%20%20%20%20%20%20h%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20header%5B0%5D%5B0%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22NumberOfForcePlates%22%3A%20int(header%5B1%5D%5B0%5D.split(%22%3D%22)%5B1%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22SampleRate%22%3A%20float(header%5B2%5D%5B0%5D.split(%22%3D%22)%5B1%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22NumberOfSamples%22%3A%20int(header%5B3%5D%5B0%5D.split(%22%3D%22)%5B1%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ch_names%22%3A%20header%5B4%5D%5B1%3A%5D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%23%20Match%20the%20entire%20numeric%20suffix%2C%20never%20%221%22%20inside%20plate%20%2210%22.%0A%20%20%20%20%20%20%20%20import%20re%0A%20%20%20%20%20%20%20%20channel_pattern%20%3D%20re.compile(r%22%5E(FX%7CFY%7CFZ%7CX%7CY%7CZ%7CMZ)(%5Cd%2B)%24%22)%0A%20%20%20%20%20%20%20%20requested%20%3D%20(%5Bint(forcepla)%5D%20if%20np.isscalar(forcepla)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%5Bint(plate)%20for%20plate%20in%20forcepla%5D)%0A%20%20%20%20%20%20%20%20all_names%20%3D%20h%5B%22ch_names%22%5D%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(f%2C%20sep%3D%22%5Ct%22%2C%20header%3DNone%2C%20usecols%3Drange(1%2C%20len(all_names)%20%2B%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dtype%3Dfloat%2C%20engine%3D%22c%22)%0A%20%20%20%20%20%20%20%20df.columns%20%3D%20all_names%0A%20%20%20%20%20%20%20%20if%20requested%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20plate%20in%20dict.fromkeys(requested)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20names%20%3D%20%5Bname%20for%20name%20in%20all_names%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(match%20%3A%3D%20channel_pattern.fullmatch(name))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20int(match.group(2))%20%3D%3D%20plate%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(names)%20!%3D%207%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22Force%20plate%20%7Bplate%7D%20does%20not%20have%20seven%20channels%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20selected.extend(names)%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.loc%5B%3A%2C%20selected%5D.copy()%0A%20%20%20%20%20%20%20%20%20%20%20%20h%5B%22NumberOfForcePlates%22%5D%20%3D%20len(set(requested))%0A%20%20%20%20%20%20%20%20%20%20%20%20h%5B%22ch_names%22%5D%20%3D%20selected%0A%20%20%20%20%20%20%20%20if%20mm2m%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%20%3D%20%5Bname%20for%20name%20in%20df.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(match%20%3A%3D%20channel_pattern.fullmatch(name))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20match.group(1)%20in%20(%22X%22%2C%20%22Y%22%2C%20%22Z%22%2C%20%22MZ%22)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20df.loc%5B%3A%2C%20cols%5D%20%3D%20df.loc%5B%3A%2C%20cols%5D%20%2F%201000%0A%20%20%20%20%20%20%20%20if%20time%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df.index%20%3D%20df.index%20%2F%20h%5B%22SampleRate%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20df.index.name%20%3D%20%22Time%22%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(%22done.%22)%0A%0A%20%20%20%20return%20h%2C%20df%0A%0A%0A%40app.function%0Adef%20write_trc(fname%2C%20header%2C%20df%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Write%20.trc%20file%20format%20from%20Cortex%20MAC.%0A%0A%20%20%20%20See%20the%20read_forces.py%20function.%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%20.forces%20file%20to%20be%20saved.%0A%20%20%20%20header%20%3A%20list%20of%20lists%0A%20%20%20%20%20%20%20%20header%20for%20the%20.forces%20file%0A%20%20%20%20df%20%3A%20pandas%20dataframe%0A%20%20%20%20%20%20%20%20dataframe%20with%20data%20for%20the%20.forces%20file%20(with%20frame%20and%20time%20columns)%0A%20%20%20%20show_msg%20%3A%20bool%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%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20os%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D'wt'%2C%20encoding%3D'utf-8'%2C%20newline%3D'')%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'Saving%20file%20%22%7Bfname%7D%22%20...%20'%2C%20end%3D'')%0A%20%20%20%20%20%20%20%20f.writelines(('%5Ct'.join(line)%20%2B%20'%5Cn'%20for%20line%20in%20header))%0A%20%20%20%20%20%20%20%20data%20%3D%20df.to_csv(header%3DNone%2C%20index%3DNone%2C%20sep%3D'%5Ct'%2C%20lineterminator%3D'%5Cn'%2C%20float_format%3D'%25.6f')%0A%20%20%20%20%20%20%20%20f.write(data.replace('%5Cn'%2C%20'%5Ct%5Cn'))%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%0A%0A%40app.function%0Adef%20write_v3dtxt(fname%2C%20trc%2C%20forces%2C%20freq%3D0%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Write%20synchronized%20marker%2Fforce%20tables%20in%20Visual3D%20ASCII%20format.%0A%0A%20%20%20%20TRC%20input%20is%20a%20filename%20or%20a%20flat%20DataFrame%20with%20Frame%23%2C%20Time%20and%20complete%0A%20%20%20%20XYZ%20marker%20groups.%20Force%20input%20is%20a%20filename%20or%20a%20DataFrame%20whose%20index%20is%0A%20%20%20%20time%20in%20seconds.%20Empty%20strings%20omit%20either%20stream.%20Filename%20TRC%20data%20are%0A%20%20%20%20converted%20to%20meters%3B%20DataFrame%20inputs%20must%20already%20use%20m%2C%20N%20and%20N%20m.%0A%0A%20%20%20%20Timestamps%20must%20increase%2C%20share%20their%20start%2C%20and%20describe%20the%20same%20recording%0A%20%20%20%20interval%20(within%20timestamp%20rounding).%20The%20output%20uses%20the%20highest%20native%0A%20%20%20%20or%20requested%20%60%60freq%60%60%20in%20Hz.%20Linear%20interpolation%20uses%20actual%20timestamps%0A%20%20%20%20only%20over%20the%20common%20measured%20time%20span%2C%20never%20beyond%20either%20last%20sample.%0A%20%20%20%20Thus%20final%20analog%20samples%20after%20the%20last%20marker%20sample%20are%20omitted.%0A%20%20%20%20Missing%20coordinate%20values%20remain%20missing%3B%20interpolation%20does%20not%20bridge%0A%20%20%20%20gaps.%20Frame%20numbers%20restart%20at%201%2C%20but%20the%20original%20time%20origin%20is%20retained.%0A%0A%20%20%20%20A%20force%20DataFrame%20with%20a%20RangeIndex%20has%20no%20time%20information%3B%20in%20that%20case%0A%20%20%20%20%60%60freq%60%60%20must%20explicitly%20specify%20its%20sampling%20rate.%20For%20a%20time-indexed%0A%20%20%20%20force%20DataFrame%2C%20%60%60freq%60%60%20specifies%20only%20a%20requested%20output%20rate.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20os%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20if%20not%20np.isfinite(freq)%20or%20freq%20%3C%200%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError(%22freq%20must%20be%20a%20finite%2C%20nonnegative%20sampling%20rate%22)%0A%20%20%20%20trc_rate%20%3D%20None%0A%20%20%20%20force_rate%20%3D%20None%0A%20%20%20%20if%20isinstance(trc%2C%20(str%2C%20os.PathLike))%3A%0A%20%20%20%20%20%20%20%20if%20str(trc)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20header%2C%20trc%20%3D%20read_trc(trc%2C%20fname2%3D%22%22%2C%20units%3D%22m%22%2C%20na%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_multi%3DFalse%2C%20show_msg%3Dshow_msg)%0A%20%20%20%20%20%20%20%20%20%20%20%20trc_rate%20%3D%20header%5B%22data_rate%22%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20trc%20%3D%20pd.DataFrame()%0A%20%20%20%20if%20isinstance(forces%2C%20(str%2C%20os.PathLike))%3A%0A%20%20%20%20%20%20%20%20if%20str(forces)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20header%2C%20forces%20%3D%20read_forces(forces%2C%20show_msg%3Dshow_msg)%0A%20%20%20%20%20%20%20%20%20%20%20%20force_rate%20%3D%20header%5B%22SampleRate%22%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20forces%20%3D%20pd.DataFrame()%0A%20%20%20%20if%20trc.empty%20and%20forces.empty%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError(%22Provide%20marker%20or%20force%20data%20with%20timestamps%22)%0A%0A%20%20%20%20def%20timeline(values%2C%20rate%2C%20name)%3A%0A%20%20%20%20%20%20%20%20times%20%3D%20np.asarray(values%2C%20dtype%3Dfloat)%0A%20%20%20%20%20%20%20%20if%20len(times)%20%3C%202%20or%20not%20np.isfinite(times).all()%20or%20np.any(np.diff(times)%20%3C%3D%200)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22%7Bname%7D%20time%20must%20contain%20at%20least%20two%20increasing%20finite%20values%22)%0A%20%20%20%20%20%20%20%20rate%20%3D%20float(rate)%20if%20rate%20is%20not%20None%20else%20(len(times)%20-%201)%20%2F%20(times%5B-1%5D%20-%20times%5B0%5D)%0A%20%20%20%20%20%20%20%20if%20not%20np.isfinite(rate)%20or%20rate%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22%7Bname%7D%20sampling%20rate%20must%20be%20positive%22)%0A%20%20%20%20%20%20%20%20return%20times%2C%20rate%0A%0A%20%20%20%20streams%20%3D%20%5B%5D%0A%20%20%20%20if%20not%20trc.empty%3A%0A%20%20%20%20%20%20%20%20if%20trc.shape%5B1%5D%20%3C%205%20or%20(trc.shape%5B1%5D%20-%202)%20%25%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22TRC%20data%20must%20contain%20Frame%23%2C%20Time%20and%20complete%20XYZ%20marker%20groups%22)%0A%20%20%20%20%20%20%20%20trc_time%2C%20trc_rate%20%3D%20timeline(trc.iloc%5B%3A%2C%201%5D%2C%20trc_rate%2C%20%22TRC%22)%0A%20%20%20%20%20%20%20%20streams.append((trc_time%2C%20trc_rate))%0A%20%20%20%20if%20not%20forces.empty%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(forces.index%2C%20pd.RangeIndex)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20freq%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Force%20data%20need%20a%20time%20index%20or%20an%20explicit%20sampling%20frequency%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20force_values%20%3D%20np.arange(len(forces))%20%2F%20freq%0A%20%20%20%20%20%20%20%20%20%20%20%20force_rate%20%3D%20freq%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20force_values%20%3D%20forces.index%0A%20%20%20%20%20%20%20%20force_time%2C%20force_rate%20%3D%20timeline(force_values%2C%20force_rate%2C%20%22Force%22)%0A%20%20%20%20%20%20%20%20streams.append((force_time%2C%20force_rate))%0A%20%20%20%20if%20len(streams)%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20Native%20sample%20bins%20may%20end%20at%20different%20last%20sample%20timestamps.%0A%20%20%20%20%20%20%20%20%23%20Compare%20their%20exclusive%20ends%2C%20allowing%20the%20precision%20of%20TRC%20text.%0A%20%20%20%20%20%20%20%20tolerance%20%3D%20max(1e-6%2C%200.1%20*%20max(1%20%2F%20rate%20for%20_%2C%20rate%20in%20streams))%0A%20%20%20%20%20%20%20%20if%20not%20np.isclose(trc_time%5B0%5D%2C%20force_time%5B0%5D%2C%20rtol%3D0%2C%20atol%3D1e-6)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22TRC%20and%20force%20time%20origins%20differ%3B%20synchronize%20them%20before%20export%22)%0A%20%20%20%20%20%20%20%20if%20abs((trc_time%5B-1%5D%20%2B%201%20%2F%20trc_rate)%20-%20(force_time%5B-1%5D%20%2B%201%20%2F%20force_rate))%20%3E%20tolerance%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22TRC%20and%20force%20data%20describe%20different%20recording%20intervals%22)%0A%0A%20%20%20%20start%20%3D%20max(times%5B0%5D%20for%20times%2C%20_%20in%20streams)%0A%20%20%20%20end%20%3D%20min(times%5B-1%5D%20for%20times%2C%20_%20in%20streams)%0A%20%20%20%20output_rate%20%3D%20max(%5Bfloat(freq)%5D%20%2B%20%5Brate%20for%20_%2C%20rate%20in%20streams%5D)%0A%20%20%20%20count%20%3D%20int(np.floor((end%20-%20start)%20*%20output_rate%20%2B%201e-8))%20%2B%201%0A%20%20%20%20output_time%20%3D%20np.minimum(start%20%2B%20np.arange(count)%20%2F%20output_rate%2C%20end)%0A%0A%20%20%20%20def%20interpolate(table%2C%20times)%3A%0A%20%20%20%20%20%20%20%20return%20np.column_stack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20np.interp(output_time%2C%20times%2C%20column%2C%20left%3Dnp.nan%2C%20right%3Dnp.nan)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20column%20in%20table.to_numpy(dtype%3Dfloat).T%0A%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20values%20%3D%20%5Bnp.arange(1%2C%20count%20%2B%201)%2C%20output_time%5D%0A%20%20%20%20signal_names%20%3D%20%5B%22TIME%22%5D%0A%20%20%20%20signal_types%20%3D%20%5B%22FRAME_NUMBERS%22%5D%0A%20%20%20%20components%20%3D%20%5B%220%22%5D%0A%20%20%20%20if%20not%20trc.empty%3A%0A%20%20%20%20%20%20%20%20values.extend(interpolate(trc.iloc%5B%3A%2C%202%3A%5D%2C%20trc_time).T)%0A%20%20%20%20%20%20%20%20marker_columns%20%3D%20list(trc.columns%5B2%3A%5D)%0A%20%20%20%20%20%20%20%20for%20start_column%20in%20range(0%2C%20len(marker_columns)%2C%203)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20group%20%3D%20marker_columns%5Bstart_column%3Astart_column%20%2B%203%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20names%20%3D%20%5Bstr(column)%5B%3A-1%5D%20for%20column%20in%20group%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(set(names))%20!%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Marker%20columns%20must%20be%20named%20as%20matching%20x%2Fy%2Fz%20triplets%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20signal_names.extend(names)%0A%20%20%20%20%20%20%20%20signal_types.extend(%5B%22TARGET%22%5D%20*%20len(marker_columns))%0A%20%20%20%20%20%20%20%20components.extend(list(%22XYZ%22)%20*%20(len(marker_columns)%20%2F%2F%203))%0A%20%20%20%20if%20not%20forces.empty%3A%0A%20%20%20%20%20%20%20%20values.extend(interpolate(forces%2C%20force_time).T)%0A%20%20%20%20%20%20%20%20signal_names.extend(forces.columns)%0A%20%20%20%20%20%20%20%20signal_types.extend(%5B%22ANALOG%22%5D%20*%20forces.shape%5B1%5D)%0A%20%20%20%20%20%20%20%20components.extend(%5B%220%22%5D%20*%20forces.shape%5B1%5D)%0A%20%20%20%20data%20%3D%20np.column_stack(values)%0A%20%20%20%20rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B%22%22%5D%20%2B%20%5B%22default%22%5D%20*%20len(signal_names)%2C%0A%20%20%20%20%20%20%20%20%5B%22%22%5D%20%2B%20signal_names%2C%0A%20%20%20%20%20%20%20%20%5B%22%22%5D%20%2B%20signal_types%2C%0A%20%20%20%20%20%20%20%20%5B%22%22%5D%20%2B%20%5B%22ORIGINAL%22%5D%20*%20len(signal_names)%2C%0A%20%20%20%20%20%20%20%20%5B%22ITEM%22%5D%20%2B%20components%2C%0A%20%20%20%20%5D%0A%20%20%20%20with%20open(file%3Dfname%2C%20mode%3D%22wt%22%2C%20encoding%3D%22utf-8%22%2C%20newline%3D%22%22)%20as%20stream%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'Saving%20file%20%22%7Bfname%7D%22%20...%20'%2C%20end%3D%22%22)%0A%20%20%20%20%20%20%20%20writer%20%3D%20csv.writer(stream%2C%20delimiter%3D%22%5Ct%22)%0A%20%20%20%20%20%20%20%20writer.writerows(rows)%0A%20%20%20%20%20%20%20%20writer.writerows(data)%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(%22done.%22)%0A%0A%0A%40app.function%0Adef%20grf_moments(data%2C%20O%2C%20show_msg%3DTrue)%3A%0A%20%20%20%20%22%22%22Calculate%20force%20plate%20moments%20around%20its%20origin%20given%0A%20%20%20%203%20forces%2C%202%20COPs%2C%201%20free%20moment%2C%20and%20its%20geometric%20position.%0A%0A%20%20%20%20Parameters%0A%20%20%20%20----------%0A%20%20%20%20data%20%3A%20Numpy%20array%20(n%2C%207)%0A%20%20%20%20%20%20%20%20array%20with%20%5BFx%2C%20Fy%2C%20Fz%2C%20COPx%2C%20COPy%2C%20COPz%2C%20Tz%5D.%0A%20%20%20%20O%20%3A%20Numpy%20array-like%20or%20list%0A%20%20%20%20%20%20%20%20origin%20%5Bx%2Cy%2Cz%5D%20of%20the%20force%20plate%20in%20the%20motion%20capture%20coordinate%20system%20%5Bin%20meters%5D.%0A%20%20%20%20show_msg%20%3A%20bool%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%0A%20%20%20%20Returns%0A%20%20%20%20-------%0A%20%20%20%20grf%20%3A%20Numpy%20array%20(n%2C%206)%0A%20%20%20%20%20%20%20%20array%20with%20%5BFx%2C%20Fy%2C%20Fz%2C%20Mx%2C%20My%2C%20Mz%5D%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20csv%0A%20%20%20%20import%20os%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20values%20%3D%20np.asarray(data%2C%20dtype%3Dfloat)%0A%20%20%20%20origin%20%3D%20np.asarray(O%2C%20dtype%3Dfloat)%0A%20%20%20%20force%20%3D%20values%5B%3A%2C%20%3A3%5D%0A%20%20%20%20lever%20%3D%20values%5B%3A%2C%203%3A6%5D%20-%20origin%0A%20%20%20%20moment%20%3D%20np.cross(lever%2C%20force)%0A%20%20%20%20moment%5B%3A%2C%202%5D%20%2B%3D%20values%5B%3A%2C%206%5D%0A%20%20%20%20return%20np.column_stack((force%2C%20moment))%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%20Inspect%20a%20marker%20recording%0A%0A%20%20%20%20Read%20a%20flat%20table%20first%20so%20frame%20number%20and%20time%20remain%20explicit%20columns.%20The%20header%20reports%20positions%20in%20millimetres%3B%20requesting%20metres%20converts%20the%20numeric%20positions%20and%20updates%20the%20unit%20field%20together.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(repo_file)%3A%0A%20%20%20%20h%2C%20df%20%3D%20read_trc(repo_file(%22data%2Fwalk1.trc%22)%2C%20fname2%3D%22%22%2C%20units%3D%22m%22%2C%20df_multi%3DFalse)%0A%20%20%20%20df.head()%0A%20%20%20%20return%20df%2C%20h%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.**%20Why%20is%20editing%20a%20header%20label%20from%20mm%20to%20m%20without%20changing%20the%20numbers%20incorrect%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%20A%20coordinate%20of%201%2C000%20mm%20represents%201%20m.%20Relabelling%201%2C000%20as%20metres%20changes%20the%20physical%20position%20by%20a%20factor%20of%201%2C000.%20Convert%20the%20values%20and%20the%20metadata%20together.%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%20Write%20a%20copy%20and%20read%20it%20back%0A%0A%20%20%20%20A%20round%20trip%20is%20a%20practical%20check%20that%20writing%20preserves%20the%20recorded%20values%20and%20labels.%20The%20generated%20file%20is%20kept%20in%20a%20temporary%20directory%20and%20is%20offered%20as%20a%20download.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20df%2C%20h%2C%20mo%2C%20np%2C%20tempfile)%3A%0A%20%20%20%20_folder%20%3D%20Path(tempfile.mkdtemp(prefix%3D%22bmc-trc-%22))%0A%20%20%20%20_copy%20%3D%20_folder%20%2F%20%22walk1_metres.trc%22%0A%20%20%20%20write_trc(str(_copy)%2C%20h%5B%22header%22%5D%2C%20df%2C%20show_msg%3DFalse)%0A%20%20%20%20_roundtrip_h%2C%20_roundtrip%20%3D%20read_trc(_copy%2C%20fname2%3D%22%22%2C%20df_multi%3DFalse%2C%20show_msg%3DFalse)%0A%20%20%20%20np.testing.assert_allclose(_roundtrip.to_numpy()%2C%20df.to_numpy()%2C%20atol%3D1e-6)%0A%20%20%20%20trc_bytes%20%3D%20_copy.read_bytes()%0A%20%20%20%20mo.download(trc_bytes%2C%20filename%3D%22walk1_metres.trc%22%2C%20label%3D%22Download%20the%20converted%20copy%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**Exercise%202.**%20Why%20compare%20values%20with%20a%20tolerance%20after%20saving%20a%20decimal%20text%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%20Solution%20to%20Exercise%202%0A%0A%20%20%20%20The%20writer%20rounds%20to%20a%20finite%20number%20of%20decimal%20places.%20Exact%20binary%20equality%20is%20too%20strict%20after%20that%20formatting%20step%3B%20a%20tolerance%20tied%20to%20the%20written%20precision%20checks%20the%20intended%20numerical%20accuracy.%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%20Inspect%20the%20force%20table%0A%0A%20%20%20%20Each%20force%20plate%20contributes%20force%20components%2C%20a%20centre-of-pressure%20position%2C%20and%20a%20free%20moment.%20The%20supplied%20Cortex%20export%20uses%20N%2C%20mm%2C%20and%20N%C2%B7mm%20before%20conversion.%20%60mm2m%3DTrue%60%20converts%20the%20three%20COP%20coordinates%20and%20the%20free%20moment%20to%20m%20and%20N%C2%B7m.%20Match%20the%20convention%20to%20your%20acquisition%20software%20before%20using%20a%20different%20file.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(repo_file)%3A%0A%20%20%20%20force_header%2C%20forces%20%3D%20read_forces(repo_file(%22data%2Fwalk1.forces%22)%2C%20mm2m%3DTrue%2C%20show_msg%3DFalse)%0A%20%20%20%20forces.head()%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%20Transfer%20the%20moment%20to%20another%20origin%0A%0A%20%20%20%20For%20a%20point%20of%20application%20P%20and%20target%20origin%20O%2C%20the%20moment%20is%20%24(P-O)%5Ctimes%20F%24%20plus%20the%20free%20moment.%20Coordinate%20origins%20and%20force%20directions%20must%20share%20one%20frame.%20The%20included%20correction%20uses%20this%20cross%20product%20explicitly%2C%20including%20nonzero%20COP%20height.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np)%3A%0A%20%20%20%20grf_moments(np.array(%5B%5B5.%2C%206.%2C%207.%2C%202.%2C%203.%2C%204.%2C%208.%5D%5D)%2C%20%5B1.%2C%201.%2C%201.%5D%2C%20show_msg%3DFalse)%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.**%20For%20a%20vertical%20force%20%60(0%2C0%2C100)%60%20N%20at%20%60(0.2%2C0%2C0)%60%20m%2C%20with%20origin%20%60(0%2C0%2C0)%60%20and%20no%20free%20moment%2C%20find%20the%20moment.%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%20Using%20the%20right-hand%20cross%20product%2C%20%24(0.2%2C0%2C0)%5Ctimes(0%2C0%2C100)%3D(0%2C-20%2C0)%24%20N%C2%B7m.%20The%20negative%20Y%20component%20is%20fixed%20by%20the%20chosen%20right-handed%20axes.%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%20Other%20Cortex%20text%20formats%0A%0A%20%20%20%20The%20retained%20%60read_anc%60%20reader%20interprets%20analog-converter%20counts%20using%20header%20ranges.%20%60read_cal%60%20reads%20force-plate%20geometry%20and%20calibration%20matrices.%20%60write_v3dtxt%60%20combines%20marker%20and%20force%20data%20for%20Visual3D%3B%20use%20it%20only%20after%20checking%20acquisition%20time%20alignment%20and%20resampling%20assumptions.%20The%20helper%20definitions%20remain%20available%20above%2C%20while%20the%20%5Bpaired%20TRC%2Fforce%20guide%5D(https%3A%2F%2Fmolab.marimo.io%2Fgithub%2FBMClab%2FBMC%2Fblob%2Fmaster%2Fnotebooks%2Fread_trc_forces_files.py)%20demonstrates%20alignment%20on%20two%20recorded%20trials.%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.%20Is%20a%20readable%20text%20file%20automatically%20self-describing%3F%0A%0A%20%20%20%202.%20Does%20a%20round%20trip%20prove%20the%20physical%20units%20were%20correct%3F%0A%0A%20%20%20%203.%20Why%20subtract%20the%20target%20origin%20from%20the%20point%20position%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%20a%20schema%20and%20units%20are%20still%20required.%0A%20%20%20%202.%20No%3B%20it%20checks%20serialization%2C%20not%20physical%20interpretation.%0A%20%20%20%203.%20The%20cross%20product%20needs%20the%20lever%20arm%20relative%20to%20the%20target%20origin.%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.**%20Convert%202%2C000%20N%C2%B7mm%20to%20N%C2%B7m.%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%202%20N%C2%B7m.%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.**%20Give%20the%20moment%20of%20force%20%60(0%2C10%2C0)%60%20N%20applied%20at%20%60(0%2C0%2C0.3)%60%20m%20about%20the%20origin%2C%20with%20zero%20free%20moment.%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%20%60(%E2%88%923%2C%200%2C%200)%60%20N%C2%B7m.%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%20Consult%20the%20export%20documentation%20for%20the%20software%20version%20that%20produced%20a%20file.%0A%0A%20%20%20%20%23%23%23%20Video%20lectures%20on%20the%20Internet%0A%0A%20%20%20%20%5BOpenSim%20video%20tutorials%5D(https%3A%2F%2Fwww.youtube.com%2F%40OpenSimVideos)%20illustrate%20how%20labelled%20motion%20and%20force%20data%20enter%20a%20model.%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%5BMotion%20Analysis%5D(https%3A%2F%2Fwww.motionanalysis.com%2F).%0A%0A%20%20%20%20-%20%5BOpenSim%20TRC%20format%5D(https%3A%2F%2Fopensimconfluence.atlassian.net%2Fwiki%2Fspaces%2FOpenSim%2Fpages%2F53089972%2FMarker%2B.trc%2BFiles).%0A%0A%20%20%20%20-%20%5BCortex%20helper%20source%5D(https%3A%2F%2Fgithub.com%2FBMClab%2FBMC%2Fblob%2Fmaster%2Ffunctions%2Fio_cortexmac.py).%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
806a5e114751eb581f00be99f17fcdc2