52
$\begingroup$

How to build a bvh player that can be used to visualize human motions in Mathematica? Any idea or sample code would be appreciated.

enter image description here

bvh = Import["https://www.dropbox.com/s/vtr0o4aj9spu3cm/sample.bvh?dl=1"]
$\endgroup$
7
  • 10
    $\begingroup$ This seems like a code request without much input on your part. What have you done so far? I suspect that the question as it stands is far too broad and requires a lot of individual components. $\endgroup$ Commented Sep 21, 2014 at 15:35
  • $\begingroup$ @bobthechemist I did some search on the web and made myself familiar to the structure of the bvh file format, and gathered some bvh player's source code on C++. I need some suggestions to start with $\endgroup$
    – Putterboy
    Commented Sep 21, 2014 at 15:56
  • 1
    $\begingroup$ Perhaps you can explain in more detail what the player actually does and how the file format is structured. The more info the better. $\endgroup$
    – Yves Klett
    Commented Sep 21, 2014 at 20:57
  • 3
    $\begingroup$ I have an answer available if this question can be reopened. $\endgroup$ Commented Sep 28, 2014 at 17:08
  • 2
    $\begingroup$ This question has received four down-votes and zero up-votes since I looked at it yesterday. I don't feel that these down-votes are deserved. No, the question does not demonstrate effort but it is interesting enough to have encouraged a very nice answer. Although not a particularly exemplary example it does pass the standard I described here. (Which by the way I see I still haven't completed.) Clearly many people find value in Sjoerd's answer; consider that it would not have been posted without this question. $\endgroup$
    – Mr.Wizard
    Commented Oct 4, 2014 at 16:12

2 Answers 2

104
$\begingroup$

enter image description here


TL;DR: A package (Mathematica v10) can be found at the very bottom of this post.


UPDATES

6: Tiny update: Import can now use the ".bvh" extension to determine the import type. The code that does this is ugly, but I don't see any other way at the moment.

out = Import["C:\\Female1_C03_Run.bvh"]

Mathematica graphics


5: Added error checking and registered the package as an official importer for "BVH" files, so an intermediate importing stage is no longer necessary:

out = Import["C:\\cmuconvert-daz-01-09\\06\\06_08.bvh", "BVH"]

4: A major update. I separated drawing and parsing of the file, increasing speed and user comfort in the process. Bone structure and joint positions are now determined by a separate pass through the parsed data. Separation of structure (which remains the same during the whole movie) and joint positions prepares for the use of GraphicsComplex. I used a few V10 features, but it should be very easy to work around those to get it to work for earlier versions.

I haven't updated the text in original post, but the new code does not deviate so much from the description as to make it fully obsolete, so I leave it there as basic documentation of the code.

The main functionality is now provided in the form of a package (at the very bottom of this post). The main function is BVHGet:

Mathematica graphics

It returns an object BVHData which is not unlike like objects such as FittedModel and TimeSeries:

Mathematica graphics

Getting your BVH file processed now works as follows:

Needs["BVHImporter`"]

bvh = Import["C:\\cmuconvert-daz-01-09\\06\\06_08.bvh", "String"];

out = BVHGet[bvh]

Mathematica graphics

With more details revealed:

Mathematica graphics

Note that parsing takes most time. Frames are processed (on my laptop) at a rate of about 200 frames/sec.

With the output BVHData object assigned to the variable out you can perform a lot of fun tricks.

The trace of all joints through time:

Point /@ out["JointsStack"] // Graphics3D

Mathematica graphics

Same for the bones:

Graphics3D[
 MapIndexed[
  {Opacity[0.1], Hue[#2[[1]]/out["FrameCount"]], GraphicsComplex[#1, Line /@ out["Bones"]]} &,
  out["JointsStack"]
  ]
 ]

Mathematica graphics

Easily generate a Manipulate:

out["Manipulate"]

Mathematica graphics

Make an animated GIF:

out["AnimatedGIF", "C:\\dribble.gif"]

enter image description here

Or even generate a 3D Graph (a V10 functionality).

Manipulate[
 GraphPlot3D[
  Graph[
   Range[out["JointCount"]],
   DirectedEdge @@@ out["Bones"]
   ],
  VertexCoordinateRules -> out["JointsStack"][[i]]
  ], {i, 1, out["FrameCount"], 1}
 ]

Mathematica graphics

There are several To-Do items, particularly some options to fine-tune things would be useful. Error handling (trying to parse non-BVH files etc.) would be nice too.


The movie above was taken from the cgspeed site which contains BVH conversions of the 2500-motion Carnegie-Mellon motion capture dataset.


3: Generalized the code to work with BVH files that use different rotation orderings.


2: Fixed another bug that caused the code to run much too slow


1: Found a bug (which derived from the vague BVH format description that I used), so the OP's demo file now runs correctly:

enter image description here

I also re-rendered the movies at the bottom of this post, which look much better now.


Original text


BVH files

First, I use the free motion cap databases that you can find on the Advanced Computing Center for the Arts and Design website. I tried the 'female1' dataset. After unzipping to a working directory I import one of the BVH files contained in it as follows:

bvh = Import["c:\\Female1_bvh\\Female1_C03_Run.bvh", "String"];

For those who want to see how such a file looks like, check this short example file. Basically, a BVH file consists of a body description part with several nested segments, with fixed offsets with respect to the connecting joints and rotations that are read from the data fork of the file which contains the motion capture data. The following code separates these two parts:

bvhHierarchy = StringReplace[bvh, ___ ~~ "HIERARCHY" ~~ start__ ~~ "MOTION" ~~ ___ :> start];
bvhMotion = StringReplace[bvh, ___ ~~ "MOTION" ~~ end___ :> end];

Pre-processing

A one-liner to tokenize the elements in the file:

tokenize[code_String] := StringSplit[code, (" " | "\n" | "\r" | "\t") ..]

Get the number of frames, frame time and mocap data section:

{frames, frameTime, data} =  
  tokenize[bvhMotion] /. {___, "Frames:", frs_, "Frame", "Time:", ft_, data__} :> 
                         {ToExpression@frs, 
                          ToExpression@ft, 
                          Partition[Internal`StringToDouble /@ {data},
                                    Length[{data}]/ToExpression[frs]
                          ]};

Notice the use of Internal`StringToDouble to convert the data in strings to reals. I don't like the use of this undocumented function, but ToExpression doesn't handle the occasional number with exponential notation (1.0e3) correctly and the V10 function Interpreter["Number"] is incredibly slow.

tokenize@bvhHierarchy

Mathematica graphics

Parsing

I define a couple of patterns to be used in later parsing:

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

bracketPattern is a pattern to find the innermost pair of matching curly brackets. channelPattern is a pattern to find the CHANNEL keyword and the corresponding parameters (usually three rotation angles, though an additional offset can be prepended). offsetPattern is a pattern to find the OFFSET keyword and the corresponding parameters. The parameters mentioned above are actually just 'slots' that are to be filled in with data from the data section.

Recursive code to actually transform CHANNEL and OFFSET keywords to functions:

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];

I used these functions so that I would be able to address those pieces by their head later on. I needed that in early versions of the code, but it might be unnecessary with some rewriting.

Now the code to recursively built the body tree:

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

Example result:

parseBrackets@parse@tokenize@bvhHierarchy /. expr -> Sequence /. parseBrackets[a:___] -> a

Mathematica graphics

The replacements at the end of the code above remove the scaffolding that remains at the end of the building process. As you can see most of the unnecessary details are gone now (such as the names of the nodes).

In tree form, and removing a few details, it looks like this:

parseBrackets@parse@tokenize@bvhHierarchy 
   /. {expr -> Sequence, channel[___] -> c, offset[___] -> off} /. 
      parseBrackets[a : ___] -> a // TreeForm

Mathematica graphics

A few utility functions

As I wrote above the BVH file is full of 'Zrotation', 'Xrotation' stuff that are merely placeholders. It is not really clear from the few not-too-precise BVH descriptions that I read, but I assume that they are to be matched with elements in each data line in order of appearance. So, I have to replace those placeholders with something that indicates their original position if they are moved around. The following piece of code does that and also takes care of the possibly varying order of rotations:

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
  ]

The rotation matrices. The order of rotations is dealt with in makeSlots:

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

Applying the actual rotations is delayed until late in the process. This is done by the undefined functions xr,yr, and zr in makeSlots. The following utility function takes care of activation when needed.

channelToRotation[c_channel] := Dot @@ (c /. {xr -> xRot, yr -> yRot, zr -> zRot})

Tree processing

Since the tree is a recursive construction it can be handled with a recursive function:

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]]}]

Note that so far the tree contains the passive keyword strings "ROOT", "JOINT" and "END". The above definitions do nothing until the moment I replace those strings with the actual function heads.

The pure body function

The non-defined slot function is replaced by Slot so as to turn the body complex into a pure function that can be applied on the lines of mocap data successively.

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

Creating frames from data

With all this in place the actual processing of the separate frames can be done in a simple loop. Replacing the string keywords with their defined function names activates the interpretation process.

motion = 
Monitor[
   Table[temp = 
         bodyFunction  @@ data[[i]] /. 
         {offset -> List, c_channel -> channelToRotation[c]} /. 
         {"ROOT" -> ROOT, "JOINT" -> JOINT, "End" -> END} /. 
         {parseBrackets[a : ___] -> a} // 
      Graphics3D, {i, Length@data}
   ], 
   Row[{i, temp}]
];

Mathematica graphics

Displaying results

boundingBox = 
  {Min /@ Transpose[(Min /@ (PlotRange /. AbsoluteOptions[#, PlotRange])) & /@ motion], 
   Max /@ Transpose[(Max /@ (PlotRange /. AbsoluteOptions[#, PlotRange])) & /@ motion]};

Manipulate[
 Show[
  motion[[i]],
  ViewVertical -> {0, 1, 0},
  ViewPoint -> {7, 2, -7},
  PlotRange -> Transpose[boundingBox],
  ImageSize -> 500,
  FaceGrids -> {{0, -1, 0}},
  Boxed -> False
  ], {i, 1, Length@data, 1}]

Mathematica graphics

Exporting as movies

Export["C:\\WalkTurnAround180.gif",
 Table[
  Show[
   motion[[i]],
   ViewVertical -> {0, 1, 0},
   ViewPoint -> {7, 2, -7},
   PlotRange -> Transpose[boundingBox],
   ImageSize -> 500,
   FaceGrids -> {{0, -1, 0}},
   Boxed -> False
   ], {i, 1, Length@data, 1}],
 "DisplayDurations" -> frameTime,
 AnimationRepetitions -> Infinity
 ]

enter image description here enter image description here enter image description here enter image description here


The Package


BeginPackage["BVHImporter`"];

BVHGet::usage = "BVHGet[BVHcode] parses the string BVHcode that contains the contents of a BVH file. It returns a BVHData object that can be interrogated for various aspects of the result.";
BVHData::usage="BVHData is the output object of BVHGet. It knows the following methods:\n\n\"BoundingBox\" - Get the bounding box containing all the movie frames\n\"JointsStack\" - Get the stack of movie frames. Each frame consists of a list of joint positions\n\"Bones\" - Get the bone structure (as a list of joint pairs)\n\"FrameTime\" - Time allocated for the display of a single move frame\n\"FrameCount\" - Number of frames in the movie\n\"JointCount\" - Number of joints in the object structure\n\"BoneCount\" - Get the number of bones\n\"ParseTime\" - Get the total time needed to parse the BVH file and to prepare calculations\n\"FrameProcessingTime\" - Get the total time to process all frames in the movie\n\"Manipulate\" - Generate a Manipulate containing the movie\n\"AnimatedGIF\" - Generates a animated GIF movie. Needs the file name as the second argument.\n\"Properties\" - Get the list of properties";

Begin["`Private`"];

tokenize[code_String] := 
  StringSplit[code, (" " | "\n" | "\r" | "\t") ..];

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
   ];

bracketPattern = PatternSequence["{", Except["{" | "}"] .., "}"];
channelPattern = 
  PatternSequence["CHANNELS", n_, 
    channels : Alternatives[_slot, _xr, _yr, _zr] ..] /; 
   Length[{channels}] == ToExpression[n] && 
    Mod[ToExpression[n], 3] == 0; 
(* note: the above Alternatives part -added to improve file syntax \
checking- requires makeSlots is executed before parse *)
offsetPattern = PatternSequence["OFFSET", x_, y_, z_];

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];

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

ROOT[os_List, d_List, r_List, rest___] := (Sow[os + d]; i = 2; 
   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] := (Sow[os + r.p[[1]]]; 
   ROOT[os + r.p[[1]], r.p[[2]], Sequence @@ p[[3 ;;]]]);
ROOT[os_List, r_List, p_END] := Sow[os + r.p[[1]]];

ROOT2[os_List, d_List, r_List, rest___] := (i = 1; ROOT2[1, rest]);
ROOT2[start_Integer, p : PatternSequence[__JOINT, ___END]] := 
  ROOT2[start, #] & /@ {p} /; Length[{p}] > 1;
ROOT2[start_Integer, p_JOINT] := (Sow[{start, ++i}]; 
   ROOT2[i, Sequence @@ p[[3 ;;]]]);
ROOT2[start_Integer, p_END] := Sow[{start, ++i}];

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});

BVHGet::kwd = "This file does not seem to be a BVH file because one or more expected keywords are missing";
BVHGet::enc = "Non-ASCII codes found in file";
BVHGet::root = "File contains more than one ROOT node, which the current implementation does not handle";
BVHGet::syntax = "The file seems to contain a syntax error. It could not be parsed correctly";
BVHGet::version = "This package requires Mathematica version 10 or higher";

BVHGet[bvh_String] :=
  Module[{bvhHierarchy, bvhMotion, frames, frameTime, data, 
    parseResult, bodyFunction, bones, jointsStack, jointCount, 
    boundingBox, boneCount, parseTime, frameProcessingTime},

   If[$VersionNumber < 10, Message[BVHGet::version]; Return[$Failed]];

   If[
    AnyTrue[{"HIERARCHY", "MOTION", "ROOT", "JOINT", "END", "OFFSET", 
      "CHANNELS", "Zrotation", "Xrotation", "Yrotation", "Xposition", 
      "Yposition", "Zposition"}, 
     StringFreeQ[bvh, #, IgnoreCase -> True] &],
    Message[BVHGet::kwd]; Return[$Failed]
    ];

   If[
    StringCount[bvh, "ROOT", IgnoreCase -> True] > 1,
    Message[BVHGet::root]; Return[$Failed]
    ];

   If[
    Max[ToCharacterCode[bvh]] > 127,
    Message[BVHGet::enc]; Return[$Failed]
    ];

   bvhHierarchy = 
    StringReplace[
     bvh, ___ ~~ "HIERARCHY" ~~ start__ ~~ "MOTION" ~~ ___ :> start]; 
   bvhMotion = StringReplace[bvh, ___ ~~ "MOTION" ~~ end___ :> end];

   {frames, frameTime, data} = 
    tokenize[
      bvhMotion] /. {___, "Frames:", frs_, "Frame", "Time:", ft_, 
       data__} :> {ToExpression@frs, ToExpression@ft, 
       Partition[Internal`StringToDouble /@ {data}, 
        Length[{data}]/ToExpression[frs]]};

   parseTime =
    AbsoluteTiming[
      (parseResult = 
         parseBrackets@parse@makeSlots@tokenize@bvhHierarchy;)
      ] // First;

   If[
    \[Not] 
     FreeQ[parseResult, "CHANNELS" | "OFFSET" | expr[_?NumberQ ..]],
    Message[BVHGet::syntax]; Return[$Failed]
    ];

   bodyFunction = 
    Function[
     parseResult /. {expr -> Sequence, slot -> Slot} // Evaluate];

   If[
    \[Not] SyntaxQ[ToString@bodyFunction],
    Message[BVHGet::syntax]; Return[$Failed]
    ];

   bones =
    Reap[bodyFunction @@ data[[1]] /. {offset -> List, 
         c_channel -> channelToRotation[c]} /. {"ROOT" -> ROOT2, 
        "JOINT" -> JOINT, "End" -> END} /. {parseBrackets[a : ___] -> 
        a}];

   If[
    \[Not] FreeQ[bones, _ROOT2 | _JOINT | _END],
    Message[BVHGet::syntax]; Return[$Failed]
    ];

   bones = bones // Last // Last;

   frameProcessingTime =
    AbsoluteTiming[
      (jointsStack =
         Table[
          Reap[bodyFunction @@ data[[i]] /. {offset -> List, 
                 c_channel -> channelToRotation[c]} /. {"ROOT" -> 
                 ROOT, "JOINT" -> JOINT, 
                "End" -> END} /. {parseBrackets[a : ___] -> a}] // 
            Last // Last,
          {i, frames}];)
      ] // First;

   jointCount = Length[jointsStack[[1]]];
   boneCount = Length[bones];

   boundingBox = {Min /@ Transpose[Join @@ jointsStack], 
     Max /@ Transpose[Join @@ jointsStack]};

   BVHData[<|"JointsStack" -> jointsStack, "Bones" -> bones, 
     "BoundingBox" -> boundingBox, "FrameTime" -> frameTime, 
     "FrameCount" -> frames, "JointCount" -> jointCount, 
     "BoneCount" -> boneCount, "ParseTime" -> parseTime, 
     "FrameProcessingTime" -> frameProcessingTime|>]
   ];

BVHData /: Format[b:BVHData[a_Association]] :=
  RawBoxes[
   BoxForm`ArrangeSummaryBox["BVHData", b, 
    Graphics3D[
     GraphicsComplex[a[["JointsStack", a["FrameCount"]/2 // Round]], 
      Line /@ a["Bones"]], ViewVertical -> {0, 1, 0},
     ViewPoint -> {7, 2, -7}, ImageSize -> 20, 
     FaceGrids -> {{0, -1, 0}}, Boxed -> False, PlotRangePadding -> 0
     ],
    {
     BoxForm`MakeSummaryItem[{"Frame count: ", a["FrameCount"]}, 
      StandardForm],
     BoxForm`MakeSummaryItem[{"Joint count: ", a["JointCount"]}, 
      StandardForm]
     },
    {
     BoxForm`MakeSummaryItem[{"Bone count: ", a["BoneCount"]}, 
      StandardForm],
     BoxForm`MakeSummaryItem[{"Bounding box: ", a["BoundingBox"]}, 
      StandardForm],
     BoxForm`MakeSummaryItem[{"Frame display time (s): ", 
       a["FrameTime"]}, StandardForm],
     BoxForm`MakeSummaryItem[{"Parse time (s): ", a["ParseTime"]}, 
      StandardForm],
     BoxForm`MakeSummaryItem[{"Frame processing time (s): ", 
       a["FrameProcessingTime"]}, StandardForm]
     }, StandardForm
    ]
   ];

BVHData[a_Association]["BoundingBox"] := a["BoundingBox"];
BVHData[a_Association]["JointsStack"] := a["JointsStack"];
BVHData[a_Association]["Bones"] := a["Bones"];
BVHData[a_Association]["FrameTime"] := a["FrameTime"];
BVHData[a_Association]["FrameCount"] := a["FrameCount"];
BVHData[a_Association]["JointCount"] := a["JointCount"];
BVHData[a_Association]["BoneCount"] := a["BoneCount"];
BVHData[a_Association]["ParseTime"] := a["ParseTime"];
BVHData[a_Association]["FrameProcessingTime"] := 
  a["FrameProcessingTime"];
BVHData[a_Association]["Properties"] := {"BoundingBox", "JointsStack",
    "Bones", "FrameTime", "FrameCount", "JointCount", "BoneCount", 
   "ParseTime", "FrameProcessingTime", "Manipulate", "AnimatedGIF", 
   "Properties"};

BVHData[a_Association]["Manipulate"] :=
  Manipulate[
   Show[
    Graphics3D[
     GraphicsComplex[a["JointsStack"][[i]], Line /@ a["Bones"]]],
    ViewVertical -> {0, 1, 0},
    ViewPoint -> {7, 2, -7},
    PlotRange -> Transpose[a["BoundingBox"]],
    ImageSize -> 500,
    FaceGrids -> {{0, -1, 0}},
    Boxed -> False, PlotRangePadding -> 0
    ], {{i, 1, "Frame"}, 1, a["FrameCount"], 1, 
    Appearance -> "Labeled"}];

BVHData[a_Association]["AnimatedGIF", fileName_String] :=
  Export[fileName,
   Table[
    Graphics3D[
     GraphicsComplex[a["JointsStack"][[i]], Line /@ a["Bones"]],
     ViewVertical -> {0, 1, 0},
     ViewPoint -> {7, 2, -7},
     PlotRange -> Transpose[a["BoundingBox"]],
     ImageSize -> 200,
     FaceGrids -> {{0, -1, 0}},
     Boxed -> False
     ], {i, 1, a["FrameCount"], 1}],
   "DisplayDurations" -> a["FrameTime"],
   AnimationRepetitions -> Infinity
   ];

ImportExport`RegisterImport["BVH", BVHImporter`BVHImport];
BVHImporter`BVHImport[filename_String] := BVHImporter`BVHGet[Import[filename, "String"]];
Unprotect[Import];
Import[name_String, opts___?OptionQ] := Import[name, "BVH", opts] /; FileExtension[name] === "bvh";
(* The code in this question (http://mathematica.stackexchange.com/q/51192/57) did not work. Neither did the answer. Wolfram support could not provide a more elegant solution so far. We use a trick here. In fact we don't have any options, but we need to add the option part to the argument template to be slightly more specific overall than an existing one that would also match. In this way we get to be evaluated before the other one, otherwise we'd be shadowed. *)
Protect[Import];

End[ ];
EndPackage[ ];
$\endgroup$
32
  • 24
    $\begingroup$ This is highly insane. I approve! $\endgroup$
    – RunnyKine
    Commented Sep 29, 2014 at 21:40
  • 9
    $\begingroup$ Very nice! The next step: human climbing on Plot3D :) $\endgroup$
    – ybeltukov
    Commented Sep 29, 2014 at 21:59
  • 2
    $\begingroup$ Just curious: Is this your ultimate procrastination refinement or are you planning doing something useful with it? (+1 of course) $\endgroup$ Commented Sep 30, 2014 at 12:53
  • 15
    $\begingroup$ @belisarius I'm afraid I won't be doing anything with this. I believe I fell victim to an extreme case of nerd sniping. $\endgroup$ Commented Sep 30, 2014 at 13:08
  • 2
    $\begingroup$ @MikeHoneychurch Thought about it and that's one of the things I'd like to have done better. The result of the current importer is a moving point cloud. To use the anatomy data I'd need to be able to replace the bones in the file with the differently sized ones in AnatomyData. As it is now that's not easy. Another problem might be to get the rotational state of the anatomy the same as the bones in the first frame of the movie. If I have my arm stretched out in a certain direction I still have some rotational freedom along its main axes. That needs to be matched with the one in the movie. $\endgroup$ Commented Nov 20, 2015 at 7:08
13
$\begingroup$

Sometimes, the file size and number of frames of a bvh file can be very large, one might skip every other frames to reduce the file size and cut half of the number of frames. The example below, has 3602 frames and 2,559KB in size, one can cut it into half with the following codes:

srcFile = "https://www.dropbox.com/s/j4k5f4vfqb592tw/Dance01.bvh?dl=1";
cpyToFile = "c:\\images\\Dance_chop.bvh";

bvh2 = Import[srcFile, "Lines"];
MotionPos = Position[bvh2, "MOTION"] // Last // Last

bvh2[[MotionPos + 1]] = 
   "Frames: " <> ToString@Length[bvh2[[MotionPos + 3 ;; -1 ;; 2]] ];

Export[ cpyToFile, Catenate[ { bvh2[[1 ;; MotionPos + 2]],
   bvh2[[MotionPos + 3 ;; -1 ;; 2]] }], "Table", "FieldSeparators" -> "\n" ]

The result file Dance_chop.bvh now has 1801 frames and 1,283KB in size.

enter image description here

And sometimes, we might prefer a continuous motion by looping back from the end to the beginning, the code below: skip every other frames first then append the reverse afterward to obtain a loop-back motion.

srcFile = "c:\\images\\Dance_chop.bvh";
cpyToFile2 = "c:\\images\\Dance_Loopback.bvh";

bvh2 = Import[srcFile, "Lines"];
MotionPos = Position[bvh2, "MOTION"] // Last // Last

bvh2[[MotionPos + 1]] = "Frames: " <> ToString[Length@
  bvh2[[MotionPos + 3 ;; -1 ;; 2]] +
   Length@bvh2[[-1 ;; MotionPos + 3 ;; -2]] ];

Export[cpyToFile2, Catenate[ { bvh2[[1 ;; MotionPos + 2]],
bvh2[[MotionPos + 3 ;; -1 ;; 2]], bvh2[[-1 ;; MotionPos + 3 ;; -2]] }] ,
 "Table", "FieldSeparators" -> "\n" ]

The GIF below was created by using Sjoerd C. de Vries' code after chopping the original bvh file twice and applied looping-back once:

enter image description here

Code for creating the GIF file above:

bvh = Import["c:\\images\\Dance_Loopback2.bvh"];
gifFile = "C:\\images\\dance02.gif";
out = BVHGet[bvh] ;

Export[ gifFile,
 Table[
  Show[
   Graphics3D[
    GraphicsComplex[
     out["JointsStack"][[i]],
     Line /@ out["Bones"]]
    ],
   ViewVertical -> {0, 1, 0},
   ViewPoint -> {-7, 2, -7},
   PlotRange -> Transpose[boundingBox],
   ImageSize -> 400,
   FaceGrids -> {{0, -1, 0}},
   Boxed -> False],
  {i, 1, 902, 1}],
 "DisplayDurations" -> 1./12,
 AnimationRepetitions -> Infinity]

Some of the bvh files include details of the fingers movements of the object. Like the one below, these can be deleted if one doesn't want them:

enter image description here

The figure can be shown as a Graph:

srcFile = "https://www.dropbox.com/s/d8wv4zurzaputvc/Look.bvh?dl=1";
bvh = Import[ srcFile, "String"];
out = BVHGet[bvh];

DirEdg = DirectedEdge @@@ out["Bones"];
bonesNo = Length@out["Bones"];

Graph[
 Range[out["JointCount"]],
  DirEdg,
  VertexLabels -> "Name",
  VertexLabelStyle -> Directive[Red, Italic, 10] 
 ] 

enter image description here

And use Manipulate to delete the unwant-fingers and/or bones:

enter image description here

Manipulate[
 Module[{x},
  col = ConstantArray[Blue, bonesNo ] ;
  sel = ConstantArray[True, bonesNo ] ;
  Dynamic@Graph[Range[bonesNo],
    EventHandler[#,
       {
        "MouseDown" :> (col[[#2]] = 
           col[[#2]] /. {Red -> Blue, Blue -> Red};
          sel[[#2]] = sel[[#2]] /. {True -> False, False -> True} 
          ) 
        }] & @@@ Thread[{DirEdg, Range[bonesNo]}], 
    EdgeStyle -> (Rule[#, #2] & @@@ 
       Thread@{  
         DirEdg, {Thickness[0.02], col[[#]]} & /@ Range[bonesNo]    }) 
    ]],
   Row[{Style["Click to toggle : ", Bold, 12], Style[Red], 
   Style["-> Unwant,   "], Style[Blue], Style[" -> Keep ", 12]}],
   Button["Confirm Change", NewBones = Pick[out["Bones"], sel] ;]
 ]

And change it into a Match-dancer with the latest Tube command.

enter image description here

NewLst = Union@Flatten@Pick[out["Bones"], sel];
forMap = MapIndexed[#1 -> k[#2 // Last] &, NewLst];
NewBonesLng = Length@NewLst;
bckMap = k[#] -> # & /@ Range[NewBonesLng];
NewBones = NewBones /. forMap /. bckMap;
NewJointsStack = 
  Table[Extract[out["JointsStack"][[i]], #] & /@ NewLst, {i, 1, out["FrameCount"]}];

Manipulate[
 GraphPlot3D[
  Graph[
   Range[NewBonesLng],
   DirectedEdge @@@ NewBones 
   ],
  VertexCoordinateRules -> NewJointsStack[[i]],
  VertexRenderingFunction -> ({Black, Sphere[#1, 3.5]} &),
  EdgeRenderingFunction -> (Tube[ #1 , 2] &),
  ViewVertical -> {0, 1, 0},
  ViewPoint -> {vx, 1, vy},
  Boxed -> False,
  ImageSize -> 300,
  PlotRange -> Transpose[out["BoundingBox"]],
  FaceGrids -> {{0, -1, 0}} 
  ],
  Grid[{{
    Control[{{i, 1, "Frame"}, 1, out["FrameCount"], 1, 
      ImageSize -> Tiny, Appearance -> "Labeled"}],
    Control[{{vx, -7, "xAngle"}, -10, 10, 1, ImageSize -> Tiny, 
      Appearance -> "Labeled"}],
    Control[{{vy, 1, "yAngle"}, -5, 5, 1, ImageSize -> Tiny, 
      Appearance -> "Labeled"} ]}
   }]
 ]

At last, I have to confess that most of the codes above were stolen from Sjoerd C. de Vries' answer to this question. And I would like to say thanks to him for his generous sharing. Studying his codes and surrounded with the inviting coffee aroma at one of the local Starbuck made my last few Saturday afternoons so enjoyable.

$\endgroup$

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.