Matplotlib figure of specific frame #213
Replies: 3 comments 3 replies
-
I think I understand what you want to do, but I want to be sure. Are you trying to superimpose different static poses on a same figure? If this is what you need, then I see three different ways of doing it, without having to modify the Player class. 1. Probably your best bet: by using multiple TimeSeriesFor this solution, I wrote a small sample code that could inspire you. It's based on the website sample code: https://kineticstoolkit.uqam.ca/doc/player.html The trick is to build as many TimeSeries as there are postures to plot, and then to plot those all at once. The main difficulty with this is that we have to make every TimeSeries unique, with unique marker names, and do the same for the interconnections. Here, I plot every second of a sample TimeSeries: # %% Load sample data
import kineticstoolkit.lab as ktk
# Download and read markers from a sample C3D file
filename = ktk.doc.download("kinematics_tennis_serve.c3d")
markers = ktk.read_c3d(filename)["Points"]
# %% Define the interconnections
interconnections = dict() # Will contain all segment definitions
interconnections["LLowerLimb"] = {
"Color": [0, 0.5, 1], # In RGB format (here, greenish blue)
"Links": [ # List of lines that span lists of markers
["Derrick:LTOE", "Derrick:LHEE", "Derrick:LANK", "Derrick:LTOE"],
["Derrick:LANK", "Derrick:LKNE", "Derrick:LASI"],
["Derrick:LKNE", "Derrick:LPSI"],
],
}
interconnections["RLowerLimb"] = {
"Color": [0, 0.5, 1],
"Links": [
["Derrick:RTOE", "Derrick:RHEE", "Derrick:RANK", "Derrick:RTOE"],
["Derrick:RANK", "Derrick:RKNE", "Derrick:RASI"],
["Derrick:RKNE", "Derrick:RPSI"],
],
}
interconnections["LUpperLimb"] = {
"Color": [0, 0.5, 1],
"Links": [
["Derrick:LSHO", "Derrick:LELB", "Derrick:LWRA", "Derrick:LFIN"],
["Derrick:LELB", "Derrick:LWRB", "Derrick:LFIN"],
["Derrick:LWRA", "Derrick:LWRB"],
],
}
interconnections["RUpperLimb"] = {
"Color": [1, 0.5, 0],
"Links": [
["Derrick:RSHO", "Derrick:RELB", "Derrick:RWRA", "Derrick:RFIN"],
["Derrick:RELB", "Derrick:RWRB", "Derrick:RFIN"],
["Derrick:RWRA", "Derrick:RWRB"],
],
}
interconnections["Head"] = {
"Color": [1, 0.5, 1],
"Links": [
["Derrick:C7", "Derrick:LFHD", "Derrick:RFHD", "Derrick:C7"],
["Derrick:C7", "Derrick:LBHD", "Derrick:RBHD", "Derrick:C7"],
["Derrick:LBHD", "Derrick:LFHD"],
["Derrick:RBHD", "Derrick:RFHD"],
],
}
interconnections["TrunkPelvis"] = {
"Color": [0.5, 1, 0.5],
"Links": [
["Derrick:LASI", "Derrick:STRN", "Derrick:RASI"],
["Derrick:STRN", "Derrick:CLAV"],
["Derrick:LPSI", "Derrick:T10", "Derrick:RPSI"],
["Derrick:T10", "Derrick:C7"],
["Derrick:LASI", "Derrick:LSHO", "Derrick:LPSI"],
["Derrick:RASI", "Derrick:RSHO", "Derrick:RPSI"],
[
"Derrick:LPSI",
"Derrick:LASI",
"Derrick:RASI",
"Derrick:RPSI",
"Derrick:LPSI",
],
[
"Derrick:LSHO",
"Derrick:CLAV",
"Derrick:RSHO",
"Derrick:C7",
"Derrick:LSHO",
],
],
}
# =============================
# THIS IS WHERE THE WORK BEGINS
# =============================
# %% See every frame at 0, 1, 2, 3, 4, 5, 6, 7 seconds
timeseries_to_plot = [] # List of TimeSeries
all_interconnections = {}
for t in [0, 1, 2, 3, 4, 5, 6, 7]:
# -------------------------------------------
# Build a TimeSeries of one frame at time = t
# -------------------------------------------
# Cut everything before t
temp_ts = markers.get_ts_after_time(t, inclusive=True)
# Keep only one index
temp_ts = temp_ts.get_ts_before_index(1)
# Set this time to 0 so that every TimeSeries ends at t=0
temp_ts.shift(-t, in_place=True)
# -------------------------------------------
# Modify the marker names so that everything is unique
# -------------------------------------------
# Do this for the TimeSeries
marker_names = list(temp_ts.data.keys())
for marker_name in marker_names:
temp_ts.rename_data(marker_name, f"{marker_name}_{t}", in_place=True)
timeseries_to_plot.append(temp_ts)
# Do this for the interconnections
# Go through every body segment
for body_name in interconnections:
all_interconnections[f"{body_name}_{t}"] = dict()
all_interconnections[f"{body_name}_{t}"]["Color"] = interconnections[
body_name
]["Color"]
all_interconnections[f"{body_name}_{t}"]["Links"] = []
# Go through every link of this segment
for i_link, link in enumerate(interconnections[body_name]):
all_interconnections[f"{body_name}_{t}"]["Links"].append(
[
f"{s}_{t}"
for s in interconnections[body_name]["Links"][i_link]
]
)
# %% Plot all these TimeSeries in a single Player window
ktk.Player(*timeseries_to_plot, interconnections=all_interconnections, up="z") The result: Obviously you will have lots of editing to make this work for your data, but it gives you the idea. 2. Hacking the Player's figure dataThis is not what I'd recommend for long term use because it means that you play in the undocumented implementation and it may break at any time. But in case option 1 does not work well for you, you can access the inner objects created by a player using its private player = ktk.Player(*timeseries_to_plot, interconnections=all_interconnections, up="z")
player.objects
The figure and its contents is directly accessible as 3. Not using the PlayerThe Player is built as a way to visualize animations, and lots of work has been done in getting an acceptable performance with Matplotlib. As such, it is not easily extendable. But if animations is not what you want, then you could also use the Matplotlib's 3d plot functions directly. It would give you total control on the look of your output. In any case, I'm curious which solution you will use, and also if I understand your problem well. I'd be happy if you made me a follow up. Best, Félix |
Beta Was this translation helpful? Give feedback.
-
Thanks for the very quick reply! From the code you wrote, it seems you did understand exactly what I was thinking about. Also, from what you wrote, it seems like My code is below. I think everything here is pretty self-explanatory except that:
I just brute-forced this. It seems to me it would be elegant to add a function to Anyway here is my code, followed by the figure it generated. Will be glad for any further thoughts.
|
Beta Was this translation helpful? Give feedback.
-
I really like where this discussion is going, and this may be the starting point for something I envisioned long ago, but that wasn't on the priority list yet: A scripting API for the PlayerInstead of having loads of options on the constructor, I think an API with well documented rw/ro properties would be still more versatile and helpful. This also means more work, but in the long term it will be great. The first step is on my side: I need to cleanup the Player's code before we start fiddling with it. It was one of the first modules I programmed in KTK and it can be simplified a lot. The second step (that can be done in parallel) is to establish the API and feature set. Obviously you can help me there, because you already do. The third step will be to program it. At this point, I'll be happy if you want to fork and participate, and I could work on it also. I'm all in for this scripting API, therefore I create an issue from this discussion, where we can move on to do it. |
Beta Was this translation helpful? Give feedback.
-
I want to make a cloud of multiple versions of a specific pose in order to communicate the variance in the different joints. This could build on existing code if the Player() object had an option that didn't automatically create an animation figure (off by default) and also had a
plot_frame()
method that could take an matplotlib axis object as one of its optional inputs.I don't mind trying to make this happen in a fork if you give me a couple of pointers of what and how to change things.
Thanks!
Opher
Beta Was this translation helpful? Give feedback.
All reactions