Skip to content

Commit a8a3634

Browse files
docstrings
1 parent dad431d commit a8a3634

File tree

2 files changed

+128
-15
lines changed

2 files changed

+128
-15
lines changed

mousearm/helper_functions.py

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python
22
"""
3-
Helper functions that "main_script.py" uses for running simulations.
3+
Helper functions that "simulate.py" uses for running simulations.
44
"""
55

66

@@ -23,16 +23,22 @@
2323
dend = 85
2424

2525
# Model and kinematic generation functions:
26-
def generate_scaled_model(reach_dir):
26+
def generate_scaled_model(reach_dir: str):
2727
"""
2828
Uses a known set of kinematics to scale "model_toScale.osim" to the data.
29-
Requires:
30-
a kinematics file, e.g. "kinematics_1.csv" below.
31-
the kinematics should include a paw, wrist, elbow and shoulder set of markers. I will work with you to make it work with a subset, if necessary.
32-
You also need Scale_Setup.xml.
3329
34-
This generates "mouse_tracked.trc", which is the formal opensim requires for its scale tool. it will also generate a new model local to the kinematics.
30+
Args:
31+
reach_dir (str, required): Directory containing kinematics CSV files and Scale_Setup.xml.
32+
33+
Produces:
34+
"mouse_tracked.trc": marker tracking file required for scaling.
35+
"scaled_mouse.osim": scaled model specific to the dataset.
36+
"scaled_mouse_params.xml": XML containing scaling parameters.
3537
38+
Requires:
39+
a kinematics file, e.g. "kinematics_1.csv" below.
40+
the kinematics should include a paw, wrist, elbow and shoulder set of markers. I will work with you to make it work with a subset, if necessary.
41+
Scale_Setup.xml.
3642
"""
3743
trackingFolder = os.path.abspath(reach_dir)
3844

@@ -105,8 +111,14 @@ def generate_scaled_model(reach_dir):
105111

106112
def generate_scaled_kinematics_rigid(reach_dir):
107113
"""
108-
This will enforce joint to joint scaling of kinematics using markers. This is necessary for tracking, as the model has rigid inter-joint distances.
109-
This produces an updated kinematics file.
114+
Adjusts kinematics files to enforce rigid inter-joint distances based on the scaled model.
115+
116+
Args:
117+
reach_dir (str): Directory containing scaled model and raw kinematics CSV files.
118+
119+
Produces:
120+
"adjusted_kinematics_*.csv": kinematics corrected for rigid joint constraints.
121+
"start_time_*" and "end_time_*": files marking ballistic phase clipping.
110122
"""
111123

112124
# you need to run generate_scaled_model() before calling this.
@@ -266,8 +278,16 @@ def butter_lowpass_filter(data, cutoff, fs, order):
266278
# save the dataframe as a csv file
267279
DF.to_csv(os.path.join(reach_dir, "adjusted_" + os.path.basename(file)),index=False,header=False)
268280
def cart2sph(x,y,z):
269-
"""
270-
Helper for getting 3d angles from cartesians.
281+
"""
282+
Converts Cartesian coordinates to spherical coordinates.
283+
284+
Args:
285+
x (float): X-coordinate.
286+
y (float): Y-coordinate.
287+
z (float): Z-coordinate.
288+
289+
Returns:
290+
tuple: (r, elevation, azimuth) corresponding to radius, polar angle, and azimuthal angle.
271291
"""
272292
XsqPlusYsq = x**2 + y**2
273293
r = m.sqrt(XsqPlusYsq + z**2) # r
@@ -279,6 +299,14 @@ def cart2sph(x,y,z):
279299
def generate_initial_pose(reach_dir):
280300
"""
281301
Constraining the joint angles to their solved initial values is hugely helpful for accuracy in the "full" simulation, so I recommend performing this step. You can skip end pose if you want to.
302+
303+
Estimates initial joint angles for each reach using adjusted kinematics.
304+
305+
Args:
306+
reach_dir (str): Directory containing "adjusted_kinematics_*.csv" files.
307+
308+
Produces:
309+
- "initpose_*.csv": initial joint angles suitable for full simulation.
282310
"""
283311

284312
# Itterate through the adjusted kinematic files that come from generate_scaled_kinematics_rigid(), then simulate them to get approximate starting joint angles.
@@ -360,7 +388,16 @@ def generate_initial_pose(reach_dir):
360388
def generate_final_pose(reach_dir):
361389
"""
362390
This may or may not be a useful constraint for the full simulation.
391+
392+
Estimates final joint angles for each reach using adjusted kinematics.
393+
394+
Args:
395+
reach_dir (str): Directory containing "adjusted_kinematics_*.csv" files.
396+
397+
Produces:
398+
- "endpose_*.csv": final joint angles for simulation constraints.
363399
"""
400+
364401
for file in glob.glob(os.path.join(reach_dir, "adjusted_kinematics*")):
365402
DATA = genfromtxt(file, delimiter=',')
366403
T = DATA[:,0]
@@ -438,6 +475,14 @@ def generate_final_pose(reach_dir):
438475
DF.to_csv(os.path.join(reach_dir, "endpose_" + os.path.basename(file)),index=False,header=False)
439476

440477
def addCoordinateActuator(model, coordName, optForce):
478+
"""
479+
Adds a torque actuator to a specified coordinate in the model.
480+
481+
Args:
482+
model (osim.Model): OpenSim model object.
483+
coordName (str): Name of the coordinate to actuate.
484+
optForce (float): Optimal force for the actuator.
485+
"""
441486
coordSet = model.updCoordinateSet()
442487
actu = osim.CoordinateActuator()
443488
actu.setName('tau_' + coordName)
@@ -449,8 +494,15 @@ def addCoordinateActuator(model, coordName, optForce):
449494

450495
def getTorqueDrivenModel(model_filename):
451496
"""
452-
This loads the biophysical model, destroys the muscles, then adds coordiante actuators at every joint.
497+
Loads a model, removes muscles, and adds coordinate actuators to all joints.
498+
453499
This is a useful tool for examining joint angles and torques alone, but obviously won't provide any information about muscle activation.
500+
501+
Args:
502+
model_filename (str): Path to the OpenSim model file.
503+
504+
Returns:
505+
osim.Model: Model configured for torque-driven simulations.
454506
"""
455507
# Load the base model.
456508
model = osim.Model(model_filename)
@@ -466,6 +518,16 @@ def getTorqueDrivenModel(model_filename):
466518
return model
467519

468520
def synth_reach_torques(reach_dir):
521+
"""
522+
Generates torque-based simulation solutions for all adjusted kinematics.
523+
524+
Args:
525+
reach_dir (str): Directory containing adjusted kinematics CSV files and scaled model.
526+
527+
Produces:
528+
- "torque_solution_*.sto": optimal torque solution for each reach.
529+
- "torque_kinematics_*.csv": corresponding kinematics extracted from simulation.
530+
"""
469531
ii = 0
470532
for file in glob.glob(os.path.join(reach_dir, "adjusted_kinematics*")):
471533
ii += 1
@@ -549,7 +611,14 @@ def synth_reach_torques(reach_dir):
549611

550612
def print_kin(path,model,states):
551613
"""
552-
This was better as an independent method. It just prints the kinematics that results from simulation to a file.
614+
This was better as an independent method.
615+
616+
Saves the kinematics resulting from a simulation to a CSV file.
617+
618+
Args:
619+
path (str): Output CSV file path.
620+
model (osim.Model): Simulation model.
621+
states (osim.StatesTrajectory): States trajectory from simulation.
553622
"""
554623
model.initSystem()
555624
statesTraj = osim.StatesTrajectory.createFromStatesTable(model, states)
@@ -566,6 +635,15 @@ def print_kin(path,model,states):
566635
osim.STOFileAdapterVec3.write(markerTrajectories,path)
567636

568637
def synth_reach_inverse(reach_dir):
638+
"""
639+
Performs inverse dynamics to compute joint torques from kinematics.
640+
641+
Args:
642+
reach_dir (str): Directory containing adjusted kinematics CSV files and torque solutions.
643+
644+
Produces:
645+
- "inverse_Solution_*.sto": inverse dynamics solutions for each reach.
646+
"""
569647
for file in glob.glob(os.path.join(reach_dir, "adjusted_kinematics*")):
570648
filename = os.path.join(reach_dir, "torque_solution_" + os.path.basename(file))
571649
filename = filename.replace('.csv','.sto')
@@ -598,6 +676,16 @@ def synth_reach_inverse(reach_dir):
598676
inverseSolutionUnsealed.write(inverse_filename)
599677

600678
def synth_reach_mu(reach_dir):
679+
"""
680+
Generates muscle-driven simulations using Moco for all reaches.
681+
682+
Args:
683+
reach_dir (str): Directory containing adjusted kinematics CSV files and scaled model.
684+
685+
Produces:
686+
- "muscle_solution_*.sto": optimal muscle activations and states.
687+
- "muscle_kinematics_*.csv": kinematics extracted from muscle-driven simulation.
688+
"""
601689
ii = 0;
602690
for file in glob.glob(os.path.join(reach_dir, "adjusted_kinematics*")):
603691

@@ -681,6 +769,15 @@ def synth_reach_mu(reach_dir):
681769
print_kin(os.path.join(reach_dir, "muscle_kinematics_" + os.path.basename(file)),model,states)
682770

683771
def getMuscleDrivenModel(model_filename):
772+
"""
773+
Loads a model and configures it for muscle-driven simulations.
774+
775+
Args:
776+
model_filename (str): Path to the OpenSim model file.
777+
778+
Returns:
779+
osim.Model: Muscle-driven model with modified muscles for Moco optimization.
780+
"""
684781
# Load the base model.
685782
model = osim.Model(model_filename)
686783
model.finalizeConnections()
@@ -714,6 +811,17 @@ def getMuscleDrivenModel(model_filename):
714811
return model
715812

716813
def synth_reach_mu_corrected(reach_dir,jj, basename = None):
814+
"""
815+
Corrects muscle-driven simulations for a specific reach index.
816+
817+
Args:
818+
reach_dir (str): Directory containing adjusted kinematics and initial/final poses.
819+
jj (int): Reach index to correct.
820+
basename (str, optional): Initial guess file for the solver. Defaults to None.
821+
822+
Produces:
823+
- Corrected "muscle_solution_*.sto" and corresponding kinematics CSV files.
824+
"""
717825
ii = 0;
718826
for file in glob.glob(os.path.join(reach_dir, "adjusted_kinematics_" + str(jj) + "*")):
719827
print("="*30)

mousearm/simulate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111

1212
from . import helper_functions as helpers
1313
import os
14-
def run_simulation(folderNameSchema="Test Reaches", nReachSets=6):
14+
def run_simulation(folderNameSchema: str, nReachSets: int):
15+
"""
16+
Args:
17+
folderNameSchema (str, required): Name of the folder.
18+
nReachSets (int, required): Number of reachset folders present.
19+
"""
1520
for i in range(nReachSets): #python is 0-indexed
1621
reach_loc = os.path.join(folderNameSchema, f"reachset_{i+1}")
1722
helpers.generate_scaled_model(reach_loc)
@@ -24,4 +29,4 @@ def run_simulation(folderNameSchema="Test Reaches", nReachSets=6):
2429
# After this, you'll have the synthetic kinematics and muscle excitations in the same folder as your initial emg and kinematics. This will take several hours to run.
2530

2631
if __name__ == "__main__":
27-
run_simulation()
32+
run_simulation()

0 commit comments

Comments
 (0)