perfanalytics / pose2sim

Markerless kinematics with any cameras — From 2D Pose estimation to 3D OpenSim motion
BSD 3-Clause "New" or "Revised" License
230 stars 44 forks source link

OpenSim Python pipeline demo #105

Closed sarkadava closed 4 days ago

sarkadava commented 3 months ago

Heya, do you by any chance have already some pipeline that goes through OpenSim procedure (scaling, IK) in Python, instead of GUI? I am able to go through it in GUI, but am not entirely sure how to achieve the same via code.

I tried the codes ScaleTool and InverseKinematics but from the source code it is not clear to me what are the arguments that this needs. I checked the OpenSim documentation but that is a bit overwhelming, not sure where to start. Would appreciate any help on this, I just basically want to continue after augmentation to scale the model and get IK from it.

Thanks for any help with the start!

davidpagnon commented 3 months ago

Hi, this is definitely something I want to add to the pipeline, maybe next week or the one after if I find time.

In the meantime, have you checked this part of the tutorial? https://github.com/perfanalytics/pose2sim?tab=readme-ov-file#command-line

sarkadava commented 3 months ago

Hey @davidpagnon , yes, I am just now in it, combining the opensim documentation and this, baby steps but works!

j-ullrich commented 3 months ago

Hi @sarkadava

I have a python script that runs the skaling, Inverse Kinematics, Analyze Tool, and the eqivalent from Pose2Sim.

If you still need help, I'd be happy to help out.

davidpagnon commented 3 months ago

This is great @j-ullrich ! Don't hesitate to post it here so that other users can benefit from it, or even to do a pull request if you think your code is production-ready (and then you'll be automatically counted as a contributor).

j-ullrich commented 3 months ago

Sure, I can do that.

But I first need to revert my code back to the version that better fits the way pose2sim handles its configurations.

I'll try to do some work in the coming days and come back to you on Sunday or Monday.

sarkadava commented 3 months ago

Hey @j-ullrich @davidpagnon that's great! I had actually made a great progress and have a pipeline that goes from openpose - pose2sim - opensim too :) still figuring out the folder structures and dependencies but already have some exciting data :)

related to that, @davidpagnon is there planned augmentation also for the 135 model (according to documentation now only for 25 and 25b model). And, do you know if one can somehow within a pipeline edit the config.toml file? The idea is that I have different sessions with two participants, everyone of course their own height/mass information, and when I am creating the repo for pose2sim/opensim pipeline, I would like to copy the general config file to session folder, then update the weight and height, and copy it to participant and trial folder. But the augmenatiton throws an error on this edited document so I assume there is some problem with encoding or overwriting the original config file. Thanks in advance :)

davidpagnon commented 3 months ago

Hi both,

That sounds awesome! Just in case you want a platform to discuss it more easily, here is the link of the discord server for contributors :) https://discord.com/invite/4mXUdSFjmt

Concerning body_135, it can be used but for now it will just give you the hand motion in addition to the other angles. If you want finger movements, you can add the points in skeletons.py and they will be triangulated. But if you want angles with biomechanical constraints (no abduction/adduction nor internal/external rotation), you will need to add a hand model to the standard OpenSim one. It should not be too complicated but I won't have time to do it myself 😥

davidpagnon commented 3 months ago

Concerning the config file, try to add a print(config_dict) in your script to see what's wrong. I suspect that you will need to do something like

config_dict.get("project").update({"project_dir":session_dir})`.
with open('config.toml', 'w') as f:
    toml.dump(config_dict, f)

session_dict is usually implicitly defined from the folder from which you launch Pose2Sim, so it may be what's missing.

If this is not it, feel free to post the error message so that I have a better idea of what's going on!

sarkadava commented 3 months ago

hey @davidpagnon , so the issue is not that it is not detected, but it lies here:

I want to update the config file, namely the mass and height, for each participant folder I am looping over

for i in folderstotrack:

    # copy to session folder
    if not os.path.exists(i+'/Config.toml'):
            shutil.copy(source1, i + '/')

    input_toml = load_toml(i+'/Config.toml')

    # update the p0 info
    new_height_p0 = input('Enter the height of the participant_p0 in meters: ')
    new_mass_p0 = input('Enter the mass of the participant_p0 in kg: ')
    updated_toml = update_participant_info(input_toml, new_height_p0, new_mass_p0)

      # update p1 info
    new_height_p1 = input('Enter the height of the participant_p1 in meters: ')
    new_mass_p1 = input('Enter the mass of the participant_p1 in kg: ')
    updated_toml_p1 = update_participant_info(input_toml, new_height_p1, new_mass_p1)

    # save the updated TOML data
    save_toml(updated_toml_p0, i+'/P0/Config.toml')
    save_toml(updated_toml_p1, i+'/P1/Config.toml')

    p0_source = i+'/P0/Config.toml'
    p1_source = i+'/P1/Config.toml'

    # # copy to participant folder
    # if not os.path.exists(i+'P1/Config.toml'):
    #         shutil.copy(source1, i + '/P1/')

    # if not os.path.exists(i+'P2/Config.toml'):
    #         shutil.copy(source1, i + '/P2/')

    for j in pcnfolders:
        if 'P0' in j:
            if not os.path.exists(j+'/Config.toml'):
                shutil.copy(p0_source, j + '/')

            print('source = ' + source1 + ' to destination: ' + j+'/')

        if 'P1' in j:
            if not os.path.exists(j+'/Config.toml'):
                shutil.copy(p1_source, j + '/')

            print('source = ' + source1 + ' to destination: ' + j+'/')

        # if not os.path.exists(j+'/Config.toml'):
        #     shutil.copy(p0_source, j + '/')

        #print('source = ' + source1 + ' to destination: ' + j+'/')

    if not os.path.exists(i+'/P1/opensim/'):
            shutil.copytree(source2, i+'/P1/opensim/')

    print('source = ' + source2 + ' to destination: ' + i+'/P1/opensim/')

    if not os.path.exists(i+'/P2/opensim/'):
            shutil.copytree(source2, i+'/P2/opensim/')

    print('source = ' + source2 + ' to destination: ' + i+'/P2/opensim/')

the code later works , until augmentation (when it has to access the m&h) and this is the error File "track_pose2_sim_batches.py", line 213, in <module> Pose2Sim.markerAugmentation() File "C:\Users\kadava\Documents\GitHub\FLESH_3Dtracking_new\Pose2Sim\Pose2Sim.py", line 486, in markerAugmentation augmentTRC(config_dict) File "C:\Users\kadava\Documents\GitHub\FLESH_3Dtracking_new\Pose2Sim\markerAugmentation.py", line 165, in augmentTRC norm2_trc_data_data = norm2_trc_data_data / subject_height[p] TypeError: ufunc 'true_divide' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

I can see that the config file looks quite different, but don't know how to edit it without changing its format

davidpagnon commented 3 months ago

Hi, sorry for the delay. I think your height and mass are stored as strings, so you need to convert them to float like so:

updated_toml_p1 = update_participant_info(input_toml, float(new_height_p1), float(new_mass_p1))
sarkadava commented 3 months ago

@davidpagnon thanks a lot, that helped!:)

@j-ullrich can you please help me with something?

i have this code

for sessionID in sessionIDs:
    # get the session path of the first session
    sessionpath = os.path.join(projectdata, 'Session_' + sessionID + '_1')
    p0 = os.path.join(sessionpath, 'P0')
    p1 = os.path.join(sessionpath, 'P1')
    participants = [p0, p1]

    for p in participants:
        print(p)

        # get the weight from txt file mass
        weightfile = os.path.join(p, 'mass.txt')
        with open(weightfile, 'r') as f:
            weight = f.read()
        new_mass = weight
        # get the path to the input model
        new_model_file = os.path.join(curfolder, 'Pose2Sim\OpenSim_Setup\Model_Pose2Sim_Body25b.osim')
        print(new_model_file)
        # get the path to the marker file
        # find folder that contains tpose in its name
        tposefolder = glob.glob(os.path.join(p, '*tpose*'))[0]
        print(tposefolder)

        # get trc marker file
        trcfolder = os.path.join(tposefolder, 'pose-3d')
        trcfiles = glob.glob(os.path.join(trcfolder, '*.trc'))
        # keep only the one with 'butterworth' in its name
        new_marker_file = [trc for trc in trcfiles if 'butterworth' in trc][0]
        print(new_marker_file)

        # construct path for output model file
        participant = p.split('\\')[-1]
        scaled_model_name = 'Model_Pose2Sim_Body25b_scaled_' + sessionID + '_' + participant + '.osim'
        new_output_model_file = os.path.join(p, 'opensim/' + scaled_model_name)
        print(new_output_model_file)

        # update the xml file
        new_scalefile_name = 'Scaling_Setup_Pose2Sim_Body25b_FLESH_' + sessionID + '_' + participant + '.xml'
        new_scalefile = os.path.join(p, new_scalefile_name)
        update_xml_file(scalefile, new_scalefile, new_mass, new_model_file, new_marker_file, new_output_model_file)

        print(new_scalefile)
        #scalepath = r'new_scalefile'
        # now we scale with this new file
        opensim.ScaleTool(new_scalefile).run()

it runs perfectly without no error but there is no model output. when I run the same XML file in opensim, the model output is there. Is there something I am forgetting?

davidpagnon commented 3 months ago

This is strange. Do you get any output in the console?

You don't have to, but if you're up for it, for such conversations/projects it might be easier to switch to the discord channel: https://discord.com/invite/4mXUdSFjmt

davidpagnon commented 4 days ago

This issue has been marked as stale. Please feel free to reopen it if needed!