26
$\begingroup$

Anatomically correct human walk is quite useful and interesting to showcase. We seemingly have all we need to do this.

1. Natural gate

Can we attach the walking gait from e.g. Wolfram demonstration Human Walking Animation or similar. Or the actual data like BVH files (thanks to @C.E. for comment about THIS POST). The BVH (Biovision Hierarchical Data) file format is commonly used in motion capture for representing 3D skeletal data and animation. Developed by Biovision, BVH files encode motion data and a hierarchical structure of a skeleton, defining how body parts (bones/joints) are connected and animated over time.

enter image description here

2. Anatomical structure:

To human skeleton (e.g. a previous question that is similar but not really what I am asking):

enter image description here

3. We can move bones partly:

See example in docs: AnatomyPlot3D >> Neat Examples >> 3rd Example:

enter image description here

There is also this post: Biomechanical model of joint movements in human body

THE TASK

Perhaps there are folks here who specialize in anatomy and who already worked with Wolfram anatomy data. It would be interesting to attach a natural human walking gait to anatomic skeleton and make it walk in place computing 1 period of motion. So it can be looped in a video or animation. Any approaches are OK.

$\endgroup$
2
  • 1
    $\begingroup$ Dem bones, dem bones gonna walk around... $\endgroup$
    – Michael E2
    Commented Nov 2 at 23:19
  • 4
    $\begingroup$ This question reminds me of How to build a BVH player. $\endgroup$
    – C. E.
    Commented Nov 3 at 1:35

1 Answer 1

31
+500
$\begingroup$

Edit: Mapping BVH Data.

enter image description here

BVH data points are mapped to graph vertices, which are then manually assigned to corresponding bones.

Using the BVH archive from https://accad.osu.edu/sites/accad.osu.edu/files/Female1_bvh.zip

Preparing BVH data points

Borrowing Sjoerd C. de Vries's parsing code from How to build a bvh player:

filename = "Female1_B01_StandToWalk.bvh"; 
bvh = Import[NotebookDirectory[] <> filename, "String"];
bvhHierarchy = 
  StringReplace[
   bvh, ___ ~~ "HIERARCHY" ~~ start__ ~~ "MOTION" ~~ ___ :> start];
bvhMotion = StringReplace[bvh, ___ ~~ "MOTION" ~~ end___ :> end];

tokenize[code_String] := 
 StringSplit[code, (" " | "\n" | "\r" | "\t") ..]
{frames, frameTime, data} = 
  tokenize[
    bvhMotion] /. {___, "Frames:", frs_, "Frame", "Time:", ft_, 
     data__} :> {ToExpression@frs, ToExpression@ft, 
     Partition[N[#, 6] & /@ {data}, Length[{data}]/ToExpression[frs]]};

ClearAll[channelPattern, offsetPattern, bracketPattern]
bracketPattern = PatternSequence["{", Except["{" | "}"] .., "}"];
channelPattern = 
  PatternSequence["CHANNELS", n_, channels__] /; 
   Length[{channels}] == ToExpression[n];
offsetPattern = PatternSequence["OFFSET", x_, y_, z_];

ClearAll[parse]
parse[left___, cp : channelPattern, right___] := 
 expr[parse[left], offset @@ ({cp}[[3 ;; 5]]), 
   channel @@ ({cp}[[6 ;;]]), parse[right]] /; Length[{cp}] == 8
parse[left___, cp : channelPattern, right___] := 
 expr[parse[left], channel @@ ({cp}[[3 ;;]]), parse[right]] /; 
  Length[{cp}] == 5
parse[left___, op : offsetPattern, right___] := 
  expr[parse[left], offset @@ ({op}[[2 ;;]]), parse[right]];
parse[{tokens___}] := parse[tokens];
parse[tokens___] := expr[tokens];

ClearAll[parseBrackets, a]
parseBrackets[left___, h1_, h2_, bp : bracketPattern, right___] := 
  parseBrackets[left, h1 @@ ({bp}[[2 ;; -2]]), right];

makeSlots[tokens_] := 
 Module[{i = 1}, 
  Which[MatchQ[#, 
      Alternatives["Xposition", "Yposition", "Zposition"]], 
     slot[i++], # === "Xrotation", xr[slot[i++]], # === "Yrotation", 
     yr[slot[i++]], # === "Zrotation", zr[slot[i++]], 
     Head[#] == String && StringMatchQ[#, NumberString], 
     ToExpression[#], True, #] & /@ tokens]

xRot[x_] := RotationMatrix[x Degree, {1, 0, 0}];
yRot[y_] := RotationMatrix[y Degree, {0, 1, 0}];
zRot[z_] := RotationMatrix[z Degree, {0, 0, 1}];

channelToRotation[c_channel] := 
 Dot @@ (c /. {xr -> xRot, yr -> yRot, zr -> zRot})
ClearAll[ROOT, JOINT]
ROOT[os_List, d_List, r_List, rest___] := ROOT[os + d, r, rest]
ROOT[os_List, r_List, p : PatternSequence[__JOINT, ___END]] := 
 ROOT[os, r, #] & /@ {p} /; Length[{p}] > 1
ROOT[os_List, r_List, p_JOINT] := {Line[{os, os + r . p[[1]]}], 
  ROOT[os + r . p[[1]], r . p[[2]], Sequence @@ p[[3 ;;]]]}
ROOT[os_List, r_List, p_END] := Line[{os, os + r . p[[1]]}]

bodyFunction = 
  Function[
   parseBrackets@
      parse@makeSlots@tokenize@bvhHierarchy /. {expr -> Sequence, 
      slot -> Slot} // Evaluate];

Aligning BHV data points with the skeleton

ClearAll[align] (* manually adjusted *)
align[point_] := Threaded[{300, 200, -1100}] + 
    ScalingMatrix[{-10, 10, 10}] . 
     RotationMatrix[90 Degree, {1, 0, 0}] . # &@point

ClearAll[bvhPoints]
bvhPoints[i_] := DeleteDuplicates@Flatten[List @@@ Flatten[
    bodyFunction @@ (ToExpression /@ data[[i]]) /. 
    {offset -> List, c_channel :> channelToRotation[c]} /. 
    {"ROOT" -> ROOT, "JOINT" -> JOINT, "End" -> END} /. 
    {parseBrackets[a : ___] :> a}], 2]

ClearAll[center]
center[1] = Identity;
center[i_] := FindGeometricTransform[{bvhPoints[1][[1]]}, {bvhPoints[i][[1]]} ][[2]]

ClearAll[graphPoints, scaledPoints]
graphPoints[i_] := <|MapIndexed[Rule[#1, First@#2] &]@bvhPoints[i]|>
scaledPoints[i_] := <|MapIndexed[Rule[First@#2, #1] &]@(align@*center[i] /@ bvhPoints[i])|>

ClearAll[graph, f]
graph[i_] := Flatten[f @@@ Flatten[
    bodyFunction @@ (ToExpression /@ data[[i]]) /. 
    {offset -> List, c_channel :> channelToRotation[c]} /. 
    {"ROOT" -> ROOT, "JOINT" -> JOINT, "End" -> END} /. 
    {parseBrackets[a : ___] :> a}], 2] /. 
    f[List[x_, y_]] :> UndirectedEdge[graphPoints[i][x], graphPoints[i][y]] 

Show[Graph3D[graph[1], 
    VertexCoordinates -> (align@*center[1] /@ bvhPoints[1])
     , VertexShapeFunction -> (Sphere[#1, 1] &) 
     , VertexLabels -> Automatic, EdgeStyle -> Blue]
   , AnatomyPlot3D[ {Opacity[.3], 
     Entity["AnatomicalStructure", "SetOfBones"]}]
      , Axes -> True, ViewPoint -> #] & /@ {Front, Left}

enter image description here

Transforming functions

ClearAll[component, transform]
component[from_, to_, iteration_] := component[from, to, iteration] = 
   {{scaledPoints[1][from], scaledPoints[1][to]}
   , {scaledPoints[iteration][from], scaledPoints[iteration][to]}}

transform[{from_, to_}][structure_] := 
 Module[{fromVector, toVector, rotationAxis, rotationAngle, 
   rotationMatrix, translationVector, translated, rotated},
  
  fromVector = Subtract @@ from;
  toVector = Subtract @@ to;
  
  rotationAxis = Cross[fromVector, toVector];
  rotationAngle = VectorAngle[fromVector, toVector];
  rotationMatrix = RotationMatrix[rotationAngle, rotationAxis];
  
  translationVector = First[to] - rotationMatrix . First[from];
  
  rotated = GeometricTransformation[structure, rotationMatrix];
  translated = TransformedRegion[rotated, TranslationTransform[translationVector]]
  ]

Mapping & frames generation

(* long runner ~5' on my machine - 16 Cores *)

frames = ParallelTable[
    Graphics3D[
    {Directive[EdgeForm[]
      , Lighting -> {{"Ambient", RGBColor[0.35, 0.35, 0.35]}
        , {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{2, 0, 2}]}
        , {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{2, 2, 2}]}
        , {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{0, 2, 2}]}}
      , Specularity[GrayLevel[0.5], 1.5], RGBColor[0.88, 0.847, 0.748]]

     , transform[component[23, 24, n]][
      Entity["AnatomicalStructure", "LeftFemur"]["MeshRegion"]] 
     , transform[component[24, 25, n]][
      Entity["AnatomicalStructure", "LeftTibia"]["MeshRegion"]] 
     , transform[component[24, 25, n]][
      Entity["AnatomicalStructure", "LeftFibula"]["MeshRegion"]] 
     , transform[component[25, 27, n]][
      Entity["AnatomicalStructure", "SkeletonOfLeftFoot"][
       "MeshRegion"]] 
     
     , transform[component[18, 19, n]][
      Entity["AnatomicalStructure", "RightFemur"]["MeshRegion"]] 
     , transform[component[19, 20, n]][
      Entity["AnatomicalStructure", "RightTibia"]["MeshRegion"]] 
     , transform[component[19, 20, n]][
      Entity["AnatomicalStructure", "RightFibula"]["MeshRegion"]] 
     , transform[component[20, 22, n]][
      Entity["AnatomicalStructure", "SkeletonOfRightFoot"][
       "MeshRegion"]] 
     
     , transform[component[14, 15, n]][
      Entity["AnatomicalStructure", "LeftHumerus"]["MeshRegion"]] 
     , transform[component[15, 16, n]][
      Entity["AnatomicalStructure", "LeftRadius"]["MeshRegion"]] 
     , transform[component[15, 16, n]][
      Entity["AnatomicalStructure", "LeftUlna"]["MeshRegion"]] 
     , transform[component[16, 17, n]][
      Entity["AnatomicalStructure", "SkeletonOfLeftHand"][
       "MeshRegion"]] 
     
     , transform[component[9, 10, n]][
      Entity["AnatomicalStructure", "RightHumerus"]["MeshRegion"] ] 
     , transform[component[10, 11, n]][
      Entity["AnatomicalStructure", "RightRadius"]["MeshRegion"]] 
     , transform[component[10, 11, n]][
      Entity["AnatomicalStructure", "RightUlna"]["MeshRegion"]]
     , transform[component[11, 12, n]][
      Entity["AnatomicalStructure", "SkeletonOfRightHand"][
       "MeshRegion"]] 
     
     , transform[component[18, 23, n]][
      Entity["AnatomicalStructure", "HipBone"] ["MeshRegion"]] 
     , transform[component[18, 23, n]][
      Entity["AnatomicalStructure", "Sacrum"] ["MeshRegion"]] 
     
     , transform[component[3, 5, n]][
      Entity["AnatomicalStructure", "Vertebra"]["MeshRegion"]] 
     
     , transform[component[13, 14, n]][
      Entity["AnatomicalStructure", "LeftScapula"]["MeshRegion"]] 
     , transform[component[8, 9, n]][
      Entity["AnatomicalStructure", "RightScapula"]["MeshRegion"] ] 
     , transform[component[13, 14, n]][
      Entity["AnatomicalStructure", "LeftClavicle"]["MeshRegion"] ] 
     , transform[component[8, 9, n]][
      Entity["AnatomicalStructure", "RightClavicle"]["MeshRegion"] ] 
     
     , transform[component[3, 5, n]][
      Entity["AnatomicalStructure", "RibCage"]["MeshRegion"]] 
     
     , transform[component[6, 7, n]][
      Entity["AnatomicalStructure", "Skull"]["MeshRegion"]] 
     
     }
    , SphericalRegion -> True
    , Boxed -> False
    , ViewPoint -> {0.99, -1.61, -0.02}]
   , {n, 2, Length@data -1, 1}];

Export["walking.gif", frames, "GIF"]

Original Post:

An attempt, perhaps as a starting point for something more sophisticated.

rightFoot = 
  Intersection[ 
   EntityList @EntityClass["AnatomicalStructure", "Bones"] , 
   Entity["AnatomicalStructure", "RightFoot"]["ConstitutionalParts"]];
leftFoot = 
  Intersection[ 
   EntityList @EntityClass["AnatomicalStructure", "Bones"] , 
   Entity["AnatomicalStructure", "LeftFoot"] ["ConstitutionalParts"]];
rightHand = 
  Intersection[ 
   EntityList @EntityClass["AnatomicalStructure", "Bones"] , 
   Entity["AnatomicalStructure", "RightHand"]["ConstitutionalParts"]];
leftHand = 
  Intersection[ 
   EntityList @EntityClass["AnatomicalStructure", "Bones"] , 
   Entity["AnatomicalStructure", "LeftHand"]["ConstitutionalParts"]];
{x, y, z } = {0, -96, 845};

Manipulate[
 AnatomyPlot3D[
  {Select[EntityList@EntityClass["AnatomicalStructure", "Bones"] 
    , FreeQ[{
       Entity["AnatomicalStructure", "LeftFemur"]
       , Entity["AnatomicalStructure", "LeftTibia"]
       , Entity["AnatomicalStructure", "LeftFibula"]
       , Entity["AnatomicalStructure", "LeftPatella"]
       , Entity["AnatomicalStructure", "RightFemur"]
       , Entity["AnatomicalStructure", "RightTibia"]
       , Entity["AnatomicalStructure", "RightFibula"]
       , Entity["AnatomicalStructure", "RightPatella"]
       , Splice@rightFoot
       , Splice@leftFoot
       , Entity["AnatomicalStructure", "LeftHumerus"]
       , Entity["AnatomicalStructure", "LeftUlna"]
       , Entity["AnatomicalStructure", "LeftRadius"]
       , Entity["AnatomicalStructure", "RightHumerus"]
       , Entity["AnatomicalStructure", "RightUlna"]
       , Entity["AnatomicalStructure", "RightRadius"]
       , Splice@rightHand
       , Splice@leftHand
       , Entity["AnatomicalStructure", "Coccyx"]}, #] &]
    , Rotate[{
         Entity["AnatomicalStructure", "RightHumerus"]
       , Entity["AnatomicalStructure", "RightUlna"]
       , Entity["AnatomicalStructure", "RightRadius"]
       , Splice@rightHand}
      , Dynamic[20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z + 455}]
    , Rotate[{
         Entity["AnatomicalStructure", "LeftHumerus"]
       , Entity["AnatomicalStructure", "LeftUlna"]
       , Entity["AnatomicalStructure", "LeftRadius"]
       , Splice@leftHand}
      , Dynamic[-20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z + 455}]
   , Rotate[{
         Entity["AnatomicalStructure", "RightFemur"]
       , Entity["AnatomicalStructure", "RightTibia"]
       , Entity["AnatomicalStructure", "RightFibula"]
       , Entity["AnatomicalStructure", "RightPatella"]
       , Splice@rightFoot}
      , Dynamic[-20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z}]
   , Rotate[{
         Entity["AnatomicalStructure", "LeftFemur"]
       , Entity["AnatomicalStructure", "LeftTibia"]
       , Entity["AnatomicalStructure", "LeftFibula"]
       , Entity["AnatomicalStructure", "LeftPatella"]
       , Splice@leftFoot}
      , Dynamic[20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z}]
   }
  , SphericalRegion -> True
  , ViewPoint -> {0.99, -1.61, -0.02}
  , PlotRangePadding -> 20], {t, -40, 40, 2}
 , SynchronousUpdating -> False]

enter image description here

$\endgroup$
5
  • 4
    $\begingroup$ Nice (+1). It will be better if his/her knees bend :-) $\endgroup$
    – A. Kato
    Commented Nov 4 at 2:11
  • 2
    $\begingroup$ @A.Kato Thank you for the upvote! Yes, bending the knees would definitely make it more realistic... $\endgroup$
    – vindobona
    Commented Nov 4 at 15:24
  • 2
    $\begingroup$ (+1) very very nice, thank you. Though I agree with @A.Kato about bending knees :-) still I would like to note that the original question goes a bit beyond. It is about attaching a realistic model of walking from models or data. For example the one demo I linked in question or maybe from data ( see on comment on my question about BV ). $\endgroup$ Commented Nov 4 at 17:20
  • 2
    $\begingroup$ @VitaliyKaurov Thank you for the upvote! Of course, I'm fully aware that there's still a long way to go to meet the question's main requirements; nevertheless, I decided to post it, as it might help with further developments. $\endgroup$
    – vindobona
    Commented Nov 4 at 18:20
  • $\begingroup$ @VitaliyKaurov Of course, I've sent you my contact details. $\endgroup$
    – vindobona
    Commented Nov 18 at 10:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.