%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%22urllib3%3E%3D2.2%22%2C%20%22c3d%3E%3D0.6%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%20Open%20C3D%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%20How%20can%20a%20binary%20file%20preserve%20marker%20trajectories%2C%20force%20channels%2C%20and%20the%20information%20needed%20to%20interpret%20them%3F%0A%0A%20%20%20%20We%20will%20inspect%20a%20header%2C%20read%20metadata%20and%20calibrated%20samples%2C%20and%20browse%20a%203D%20marker%20trajectory%20without%20a%20desktop-only%20animation%20backend.%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%20example%20is%20%60data%2FGait.c3d%60%2C%20distributed%20with%20this%20repository.%20Local%20files%20take%20precedence%3B%20otherwise%20the%20same%20public%20recording%20is%20downloaded.%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%20pure-Python%20%60c3d%60%20package%20decodes%20the%20file%2C%20NumPy%20handles%20arrays%2C%20pandas%20labels%20them%2C%20and%20Matplotlib%20plots%20them.%20The%20readers%20are%20included%20in%20this%20notebook%20so%20a%20single-file%20cloud%20session%20can%20run%20it.%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%0A%20%20%20%20return%20mo%2C%20np%2C%20plt%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%20load_c3d(path)%3A%0A%20%20%20%20%22%22%22Return%20metadata%2C%20marker%20coordinates%2C%20and%20calibrated%20analog%20samples.%0A%0A%20%20%20%20Times%20are%20seconds%20relative%20to%20the%20first%20recorded%20point%20frame.%20Negative%0A%20%20%20%20residuals%20mark%20invalid%20points%3B%20a%20zero%20coordinate%20is%20a%20valid%20measurement.%0A%20%20%20%20Analog%20scaling%20is%20performed%20once%20by%20c3d.Reader.%20Analog%20channels%20are%20not%0A%20%20%20%20assumed%20to%20be%20force%20components%20or%20rotated%20in%20arbitrary%20groups%20of%20three.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20c3d%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20def%20normalized_labels(reader%2C%20group%2C%20used)%3A%0A%20%20%20%20%20%20%20%20%23%20USED%20defines%20data%20width%3B%20LABELS%20can%20legally%20be%20shorter%20or%20longer.%0A%20%20%20%20%20%20%20%20labels%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20suffix%20%3D%20%22%22%0A%20%20%20%20%20%20%20%20while%20len(labels)%20%3C%20used%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20parameter%20%3D%20reader.get(f%22%7Bgroup%7D%3ALABELS%7Bsuffix%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20parameter%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20labels.extend(parameter.string_array)%0A%20%20%20%20%20%20%20%20%20%20%20%20suffix%20%3D%202%20if%20suffix%20%3D%3D%20%22%22%20else%20suffix%20%2B%201%0A%20%20%20%20%20%20%20%20labels%20%3D%20%5Bstr(label).strip(%22%20%5Ct%5Cr%5Cn%5Cx00%22)%20for%20label%20in%20labels%5B%3Aused%5D%5D%0A%20%20%20%20%20%20%20%20labels.extend(%5B%22%22%5D%20*%20(used%20-%20len(labels)))%0A%20%20%20%20%20%20%20%20reserved%20%3D%20set(labels)%0A%20%20%20%20%20%20%20%20seen%20%3D%20set()%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20index%2C%20label%20in%20enumerate(labels)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20base%20%3D%20label%20or%20f%22%7Bgroup%7D_%7Bindex%20%2B%201%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20candidate%20%3D%20base%0A%20%20%20%20%20%20%20%20%20%20%20%20number%20%3D%202%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20candidate%20in%20seen%20or%20(candidate%20!%3D%20label%20and%20candidate%20in%20reserved)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20candidate%20%3D%20f%22%7Bbase%7D_%7Bnumber%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20number%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20result.append(candidate)%0A%20%20%20%20%20%20%20%20%20%20%20%20seen.add(candidate)%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20with%20open(path%2C%20%22rb%22)%20as%20handle%3A%0A%20%20%20%20%20%20%20%20reader%20%3D%20c3d.Reader(handle)%0A%20%20%20%20%20%20%20%20frames%20%3D%20list(reader.read_frames())%0A%20%20%20%20%20%20%20%20if%20not%20frames%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22The%20C3D%20file%20contains%20no%20data%20frames%22)%0A%20%20%20%20%20%20%20%20point_labels%20%3D%20normalized_labels(reader%2C%20%22POINT%22%2C%20reader.point_used)%0A%20%20%20%20%20%20%20%20analog_labels%20%3D%20normalized_labels(reader%2C%20%22ANALOG%22%2C%20reader.analog_used)%0A%20%20%20%20%20%20%20%20units%20%3D%20reader.get(%22POINT%3AUNITS%22)%0A%20%20%20%20%20%20%20%20event_used%20%3D%20reader.get(%22EVENT%3AUSED%22)%0A%20%20%20%20%20%20%20%20events%20%3D%20reader.get(%22EVENT%3ALABELS%22)%0A%20%20%20%20%20%20%20%20event_times%20%3D%20reader.get(%22EVENT%3ATIMES%22)%0A%20%20%20%20%20%20%20%20info%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22point_rate%22%3A%20float(reader.point_rate)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22analog_rate%22%3A%20float(reader.analog_rate)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22first_frame%22%3A%20int(reader.first_frame)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22frames%22%3A%20len(frames)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22point_units%22%3A%20units.string_value.strip().strip(%22%5Cx00%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20units%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20%22unspecified%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22analog_units%22%3A%20list(reader.get(%22ANALOG%3AUNITS%22).string_array)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20reader.get(%22ANALOG%3AUNITS%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22parameter_groups%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%5Bkey%20for%20key%2C%20_%20in%20group.param_items()%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20name%2C%20group%20in%20reader.group_items()%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22events%22%3A%20list(events.string_array)%20if%20events%20else%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22event_times%22%3A%20event_times.float_array.tolist()%20if%20event_times%20else%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22event_count%22%3A%20int(event_used.int16_value)%20if%20event_used%20else%200%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20all_points%20%3D%20np.stack(%5Bpoints%20for%20_%2C%20points%2C%20_%20in%20frames%5D)%0A%20%20%20%20%20%20%20%20xyz%20%3D%20all_points%5B%3A%2C%20%3A%2C%20%3A3%5D.copy()%0A%20%20%20%20%20%20%20%20xyz%5Ball_points%5B%3A%2C%20%3A%2C%203%5D%20%3C%200%5D%20%3D%20np.nan%0A%20%20%20%20%20%20%20%20columns%20%3D%20pd.MultiIndex.from_product(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bpoint_labels%2C%20list(%22XYZ%22)%5D%2C%20names%3D%5B%22Marker%22%2C%20%22Coordinate%22%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20points%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%20%20%20%20xyz.reshape(len(frames)%2C%20-1)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcolumns%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3Dnp.arange(len(frames))%20%2F%20reader.point_rate%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20reader.analog_used%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20samples%20%3D%20np.concatenate(%5Banalog%20for%20_%2C%20_%2C%20analog%20in%20frames%5D%2C%20axis%3D1).T%0A%20%20%20%20%20%20%20%20%20%20%20%20analog%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20samples%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20columns%3Danalog_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3Dnp.arange(len(samples))%20%2F%20reader.analog_rate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20analog%20%3D%20pd.DataFrame()%0A%20%20%20%20points.index.name%20%3D%20analog.index.name%20%3D%20%22Time%20%5Bs%5D%22%0A%20%20%20%20return%20info%2C%20points%2C%20analog%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%20Text%2C%20bytes%2C%20and%20the%20C3D%20structure%0A%0A%20%20%20%20All%20files%20are%20bytes.%20A%20text%20format%20interprets%20bytes%20with%20an%20encoding%20such%20as%20UTF-8%3B%20understanding%20its%20columns%20still%20requires%20a%20schema%20and%20units.%20A%20binary%20format%20specifies%20numeric%20representation%2C%20byte%20order%2C%20and%20layout.%20Binary%20storage%20can%20be%20compact%2C%20but%20size%20and%20speed%20depend%20on%20the%20format%20and%20data.%0A%0A%20%20%20%20C3D%20stores%20synchronized%203D-point%20and%20analog%20data%2C%20standard%20and%20custom%20parameters%2C%20and%20optional%20events.%20Its%20sections%20use%20512-byte%20blocks%3A%20a%20header%2C%20a%20parameter%20section%2C%20and%20data%20blocks.%20The%20parameter%20section%20identifies%20the%20processor%20representation%2C%20so%20a%20parser%20must%20not%20assume%20that%20every%20file%20uses%20the%20host%20machine's%20byte%20order.%0A%0A%20%20%20%20Let%20us%20inspect%20the%20supplied%20fixture's%20header.%20The%20short%20example%20below%20reads%20integer%20fields%20using%20the%20declared%20byte%20order.%20Floating-point%20representation%20also%20varies%20by%20processor%3B%20%60c3d.Reader%60%20handles%20that%20conversion%20for%20the%20complete%20file.%20The%20supplied%20recording%20uses%20DEC%20representation.%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%20import%20struct%0A%20%20%20%20with%20open(repo_file(%22data%2FGait.c3d%22)%2C%20%22rb%22)%20as%20_file%3A%0A%20%20%20%20%20%20%20%20_header%20%3D%20_file.read(512)%0A%20%20%20%20%20%20%20%20_parameter_block%2C%20_key%20%3D%20struct.unpack(%22BB%22%2C%20_header%5B%3A2%5D)%0A%20%20%20%20%20%20%20%20if%20_key%20!%3D%2080%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Missing%20C3D%20header%20key%22)%0A%20%20%20%20%20%20%20%20_file.seek(512%20*%20(_parameter_block%20-%201)%20%2B%203)%0A%20%20%20%20%20%20%20%20_processor%20%3D%20_file.read(1)%5B0%5D%0A%20%20%20%20%20%20%20%20if%20_processor%20not%20in%20(84%2C%2085%2C%2086)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Unknown%20C3D%20processor%20representation%22)%0A%20%20%20%20%20%20%20%20_byteorder%20%3D%20%22%3E%22%20if%20_processor%20%3D%3D%2086%20else%20%22%3C%22%0A%20%20%20%20%20%20%20%20header_summary%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22points%22%3A%20struct.unpack_from(_byteorder%20%2B%20%22H%22%2C%20_header%2C%202)%5B0%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22first_frame%22%3A%20struct.unpack_from(_byteorder%20%2B%20%22H%22%2C%20_header%2C%206)%5B0%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22last_frame%22%3A%20struct.unpack_from(_byteorder%20%2B%20%22H%22%2C%20_header%2C%208)%5B0%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22processor%22%3A%20%7B84%3A%20%22Intel%22%2C%2085%3A%20%22DEC%22%2C%2086%3A%20%22MIPS%22%7D%5B_processor%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22point_rate_bytes%22%3A%20_header%5B20%3A24%5D.hex()%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20header_summary%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%201.**%20The%20first%20frame%20is%201%20and%20the%20last%20is%20487.%20How%20many%20frames%20are%20stored%3F%20Why%20specify%20byte%20order%20in%20the%20numeric%20unpacking%20format%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%20There%20are%20%24487-1%2B1%3D487%24%20frames.%20An%20explicit%20byte%20order%20avoids%20an%20accidental%20dependence%20on%20the%20computer%20running%20the%20example.%20The%20file's%20processor%20byte%20determines%20that%20choice.%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%20complete%20recording%0A%0A%20%20%20%20The%20old%20notebook%20used%20platform-specific%20BTK%20binaries%20for%20Python%202%20and%20skipped%20most%20examples%20when%20those%20binaries%20were%20unavailable.%20The%20pure-Python%20reader%20now%20executes%20the%20examples%20in%20all%20supported%20editors.%20It%20exposes%20parameter%20groups%2C%20channel%20labels%2C%20point%20units%2C%20analog%20units%2C%20and%20optional%20events.%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%20info%2C%20pt%2C%20an%20%3D%20load_c3d(repo_file(%22data%2FGait.c3d%22))%0A%20%20%20%20info%0A%20%20%20%20return%20an%2C%20info%2C%20pt%0A%0A%0A%40app.cell%0Adef%20_(info%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22**%7Binfo%5B'frames'%5D%7D%20frames**%2C%20point%20rate%20**%7Binfo%5B'point_rate'%5D%3Ag%7D%20Hz**%2C%20analog%20rate%20**%7Binfo%5B'analog_rate'%5D%3Ag%7D%20Hz**.%20This%20file%20has%20**%7Binfo%5B'event_count'%5D%7D%20parameter-defined%20events**.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(an%2C%20info%2C%20pt)%3A%0A%20%20%20%20%7B%22markers%22%3A%20list(pt.columns.get_level_values(%22Marker%22).unique())%2C%20%22analog_channels%22%3A%20list(an.columns)%2C%20%22parameter_groups%22%3A%20info%5B%22parameter_groups%22%5D%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.**%20The%20file%20has%2033%20points%20and%20487%20frames.%20What%20is%20the%20shape%20of%20its%20XYZ%20coordinate%20array%20if%20rows%20are%20frames%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%20array%20shape%20is%20%60(487%2C%2033%2C%203)%60.%20A%20flat%20table%20has%20shape%20%60(487%2C%2099)%60.%20Invalid%20points%20are%20marked%20from%20their%20residuals%2C%20not%20by%20replacing%20every%20zero.%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%20calibrated%20force%20channels%0A%0A%20%20%20%20The%20reader%20applies%20C3D%20analog%20offsets%20and%20scale%20factors.%20The%20sample%20file%20labels%20F1Z%20and%20F2Z%20in%20newtons.%20Their%20sign%20follows%20the%20file's%20convention%3B%20identify%20the%20coordinate%20system%20before%20interpreting%20a%20force%20as%20upward%20or%20downward.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(an%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(9%2C%203))%0A%20%20%20%20an%5B%5B%22F1Z%22%2C%20%22F2Z%22%5D%5D.plot(ax%3D_ax)%0A%20%20%20%20_ax.set_ylabel(%22Force%20%5BN%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.**%20If%20an%20analog%20channel%20is%20already%20calibrated%20in%20newtons%2C%20should%20we%20divide%20it%20by%20its%20stored%20scale%20factor%20again%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.%20Scaling%20a%20second%20time%20changes%20the%20calibrated%20values%20and%20produces%20an%20incorrect%20force.%20Inspect%20units%20and%20reader%20documentation%20before%20applying%20a%20conversion.%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%20Browse%20the%20marker%20motion%0A%0A%20%20%20%20Move%20the%20frame%20control%20to%20inspect%20the%20recording.%20Fixed%20axis%20limits%20make%20displacement%20visible%20across%20frames.%20The%20slider%20replaces%20the%20original%20separate-window%20animation%20and%20automatic%20video-file%20writes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20pt)%3A%0A%20%20%20%20frame_control%20%3D%20mo.ui.slider(start%3D0%2C%20stop%3Dlen(pt)-1%2C%20step%3D1%2C%20value%3D130%2C%20label%3D%22Frame%20offset%22%2C%20show_value%3DTrue)%0A%20%20%20%20frame_control%0A%20%20%20%20return%20(frame_control%2C)%0A%0A%0A%40app.cell%0Adef%20_(frame_control%2C%20info%2C%20np%2C%20plt%2C%20pt)%3A%0A%20%20%20%20_xyz%20%3D%20pt.to_numpy().reshape(len(pt)%2C%20-1%2C%203)%0A%20%20%20%20_frame%20%3D%20frame_control.value%0A%20%20%20%20_fig%20%3D%20plt.figure(figsize%3D(7%2C%205))%0A%20%20%20%20_ax%20%3D%20_fig.add_subplot(projection%3D%223d%22)%0A%20%20%20%20_ax.scatter(*_xyz%5B_frame%5D.T)%0A%20%20%20%20for%20_coordinate%2C%20_set%20in%20enumerate((_ax.set_xlim%2C%20_ax.set_ylim%2C%20_ax.set_zlim))%3A%0A%20%20%20%20%20%20%20%20_set(np.nanmin(_xyz%5B%3A%2C%20%3A%2C%20_coordinate%5D)%2C%20np.nanmax(_xyz%5B%3A%2C%20%3A%2C%20_coordinate%5D))%0A%20%20%20%20_ax.set(xlabel%3D%22X%20%5Bmm%5D%22%2C%20ylabel%3D%22Y%20%5Bmm%5D%22%2C%20zlabel%3D%22Z%20%5Bmm%5D%22%2C%20title%3Df%22Time%20%7B_frame%20%2F%20info%5B'point_rate'%5D%3A.2f%7D%20s%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%23%23%20Checkpoint%20questions%0A%0A%20%20%20%201.%20Does%20a%20binary%20file%20explain%20its%20units%20without%20metadata%3F%0A%0A%20%20%20%202.%20Why%20preserve%20zero%20coordinates%3F%0A%0A%20%20%20%203.%20Does%20absence%20of%20event%20labels%20mean%20that%20no%20gait%20events%20occurred%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.%0A%20%20%20%202.%20Zero%20can%20be%20a%20valid%20coordinate.%0A%20%20%20%203.%20No%3B%20the%20events%20may%20simply%20not%20have%20been%20annotated.%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.**%20At%20100%20Hz%2C%20what%20relative%20time%20belongs%20to%20zero-based%20frame%20offset%20130%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.30%20s.%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.**%20How%20many%20bytes%20occupy%20three%20C3D%20blocks%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%201%2C536%20bytes.%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%0Adef%20_(mo%2C%20repo_file)%3A%0A%20%20%20%20mo.accordion(%7B%22Original%20walking%20animation%22%3A%20mo.image(src%3Drepo_file(%22images%2Fwalking.gif%22)%2C%20width%3D500)%7D%2C%20expanded%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%23%23%20Go%20deeper%0A%0A%20%20%20%20The%20companion%20%5Btable-based%20reader%5D(https%3A%2F%2Fmolab.marimo.io%2Fgithub%2FBMClab%2FBMC%2Fblob%2Fmaster%2Fnotebooks%2Fread_c3d.py)%20focuses%20on%20pandas%20selection.%20Consult%20the%20C3D%20manual%20before%20extending%20the%20minimal%20header%20demonstration%20to%20other%20processor%20types%20or%20custom%20parameters.%0A%0A%20%20%20%20%23%23%23%20Video%20lectures%20on%20the%20Internet%0A%0A%20%20%20%20%5BOpenSim%20webinar%20archive%5D(https%3A%2F%2Fopensim.stanford.edu%2Fsupport%2Fevent_details.php%3Fid%3D30)%20provides%20motion-analysis%20context.%20%5BMIT%20Signals%20and%20Systems%20lectures%5D(https%3A%2F%2Focw.mit.edu%2Fcourses%2Fres-6-007-signals-and-systems-spring-2011%2Fvideo_galleries%2Fvideo-lectures%2F)%20explain%20the%20sampling-rate%20distinction%20used%20here.%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%5BC3D%20technical%20user%20guide%5D(https%3A%2F%2Fc3d.org%2Fdocs%2FC3D_User_Guide.pdf).%0A%0A%20%20%20%20-%20%5BPython%20c3d%20reader%5D(https%3A%2F%2Fgithub.com%2FEmbodiedCognition%2Fpy-c3d).%0A%0A%20%20%20%20-%20%5BC3D%20example%20data%5D(https%3A%2F%2Fwww.c3d.org%2Fsampledata.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
202e028a435c09980ac8e2e1dfac52b5